You cannot truly hide an API key that ships to the client, so hiding a key in a Cursor project really means keeping it server-side and out of anything the browser or app downloads. In practice that means storing secrets in a git-ignored .env file, reading them only in server code through environment variables, and never placing a secret in a frontend environment variable, since those are bundled into the client. In Cursor specifically, add .env to a .cursorignore file and keep Privacy Mode on so the editor does not index or retain your secrets. For production, a secrets manager is a better home than a plain file.
Short answer
Hiding an API key means keeping it where users cannot reach it, which is the server, not the client. Store secrets in a .env file listed in .gitignore, and read them only in backend code via environment variables, never in a frontend variable that gets bundled into the app. In Cursor, add .env to a .cursorignore file so the editor does not index it, per Cursor's ignore-files docs, and keep Privacy Mode on so sent code is not stored. For production, use a secrets manager rather than a plain file. A key that runs in the client cannot be hidden at all, only proxied through your server.
Where API keys can actually be hidden
The core rule is that a secret is only hidden if it lives somewhere the user's device never receives it, which in practice means your server. Anything that ships to the browser or the installed app, including frontend environment variables and hardcoded strings, can be read by anyone who inspects the client, so those locations do not hide a key at all. This is the frontend and backend boundary, and it decides everything about where a key belongs.
So the first question for any key is where it runs. A key used to call a third-party API from your backend can be genuinely hidden, because only your server sees it. A key the client needs to call a service directly cannot be hidden; the right pattern there is to keep the secret on your server and have the client call your server, which then calls the service. Deciding this early is what makes the rest of the setup meaningful. Retrofitting a proxy after you have built a feature around a client-side key is far more work than getting the boundary right the first time.
.env setup for Cursor projects
The standard way to hold secrets during development is a .env file that is excluded from git. Create a .env, put each secret as a name and value line, and add .env, along with variants like .env.local and .env.production, to your .gitignore so it is never committed. Load the values in your code through your framework's environment access, such as process.env, rather than writing the literal value anywhere in source.
Share the shape without the secrets. Commit a .env.example that lists the variable names with placeholder values, so anyone setting up the project knows what to provide without ever seeing the real keys. This keeps the setup reproducible while keeping the actual secrets local, and it is the baseline every Cursor project should start from before any code is written.
Cursor privacy settings and .cursorignore
Cursor adds two settings worth getting right. First, add your .env and any secret files to a .cursorignore file, which excludes them from the editor's indexing and context, so the assistant does not pull your secrets into what it sends to the model. This is the Cursor-specific equivalent of keeping secrets out of version control.
Second, keep Privacy Mode enabled. With it on, code sent to fulfill a request is not stored on Cursor's servers or used for training, so anything that does get sent for a legitimate request is not retained. The two settings work together: .cursorignore limits what is sent, and Privacy Mode limits what happens to whatever is sent. Neither replaces keeping secrets server-side, but both reduce the chance of a secret leaking through the editor.
What hiding a key does not mean
Hiding a key does not mean obscuring it in the client. Putting a key in a frontend environment variable, encoding it, splitting it across files, or tucking it behind a build step does not protect it, because all of that still ships to the user, who can read the running app. Obfuscation raises effort slightly but does not make a client-side secret actually secret.
The honest consequence is that some designs simply cannot keep a key on the client. If a third-party service requires a secret key and you were calling it from the browser, the fix is architectural: move that call to your server. Accepting that early avoids a lot of wasted effort trying to conceal something that, by its position in the app, cannot be concealed. A short server endpoint that forwards the request is almost always simpler than any scheme to hide a key on the client.
Approaches compared
Different places to put a key offer very different protection. The table below compares them so you can choose by whether users can reach the key.
| Location | Hidden from users? | Use it for |
|---|---|---|
| Server-side environment variable | Yes | Secret keys and backend API calls |
| Git-ignored .env, local | From the repository | Local development secrets |
| Frontend environment variable | No, it is bundled | Only non-secret configuration |
| Hardcoded in client code | No | Never |
| Secrets manager | Yes | Production secrets |
The pattern is that server-side storage and a secrets manager genuinely hide a key, a git-ignored .env protects it in your repository during development, and anything client-side does not hide it at all. Choose the location by whether users can reach it, not by what is convenient in the moment.
Setup checklist
A short setup gets this right from the start. The checklist below covers it in order.
| Check | Action | Done? |
|---|---|---|
| .env and .gitignore | Store secrets in a git-ignored .env file | [ ] |
| .cursorignore | Exclude .env and secret files from Cursor indexing | [ ] |
| Privacy Mode | Keep Cursor Privacy Mode enabled | [ ] |
| Server-side only | Keep secret keys off the client entirely | [ ] |
| Secrets manager | Use one for production secrets | [ ] |
The two that matter most are keeping secret keys strictly server-side and never placing them in a frontend variable, because those decide whether a key is hidden at all. The Cursor settings and the secrets manager strengthen a setup that is already architecturally sound; they cannot rescue a key that ships to the client.
Verify with a scan
Setting things up correctly is most of the work, but it is worth confirming that no secret slipped into what you actually ship, since an assistant can inline a key or a frontend variable can bundle one despite your intentions.
A scanner like PTKD.com analyzes your app build and reports findings ordered by severity and mapped to OWASP MASVS, including secrets embedded in the binary, so you can verify a key did not reach the client. To be clear about the boundary: PTKD does not configure Cursor, write your .env, or move a call to your server. It confirms whether the build you ship actually keeps secrets out, which is the check that tells you your setup worked.
What to take away
- Hiding an API key means keeping it server-side; anything that ships to the client cannot be hidden, only proxied through your server.
- Store secrets in a git-ignored .env, read them through environment variables, and commit only a placeholder .env.example.
- In Cursor, add .env to .cursorignore and keep Privacy Mode on so the editor does not index or retain your secrets.
- Never put a secret in a frontend environment variable or hardcode it in the client, since obfuscation does not make it secret.
- For production use a secrets manager, and verify with PTKD.com that no secret reached the shipped app.




