To stop Cursor from hardcoding API keys into code that reaches GitHub, keep secrets in a git-ignored .env file, instruct the assistant to read from environment variables rather than inline literals, and add a pre-commit secret scanner plus GitHub push protection so a key cannot be committed by accident. If a key already reached GitHub, rotate it immediately, because it should be treated as compromised, then purge it from the repository history with a tool like BFG Repo Cleaner. Deleting the file in a new commit is not enough, since the key remains in the git history.
Short answer
Stop the leak at two levels: prevent hardcoding, and contain a key that slipped through. To prevent it, store secrets in a .env file listed in .gitignore, tell Cursor to use environment variables instead of literal keys, and add a pre-commit scanner such as gitleaks plus GitHub's push protection. If a key already reached GitHub, first rotate it, since automated scanners find exposed keys within minutes, then remove it from history with BFG Repo Cleaner or git filter-repo and force-push. Deleting the file alone does not help, because the secret stays in the commit history until you rewrite it.
Why Cursor hardcodes API keys
An AI assistant hardcodes a key because the quickest way to satisfy a prompt like connect to this service is to write the literal value directly into the code. The model is optimizing for working code in the moment, not for secret hygiene, so unless you steer it, it may place the key inline where it is easy to use and easy to leak.
This is not unique to Cursor; any code assistant can do it. The fix is to make the safe path the default by telling the assistant how you handle secrets and by having guardrails that catch a hardcoded key before it ever reaches a commit. Treat the assistant as a fast junior developer who needs your conventions made explicit.
Stop it before it happens: .env configuration
The foundation is a .env file that holds your secrets and is excluded from git. Add .env, and variants like .env.local and .env.production, to your .gitignore so the file is never committed, and load the values through your framework's environment access, such as process.env, rather than writing them in source. Commit a .env.example with placeholder values instead, so the structure is shared without the secrets.
Then make the assistant follow this. In Cursor, a rules file that states secrets must come from environment variables and never be inlined pushes generated code toward reading from .env rather than pasting a literal. Combined with the .gitignore, this means the normal output of the assistant keeps keys out of both your code and your commits.
If a key already hit GitHub: rotate first
If a key has already been pushed, rotate it before anything else. Treat any secret that reached a repository, especially a public one, as compromised, because automated bots scan GitHub for exposed keys continuously and can find and use one within minutes. Revoke the exposed key at the provider and issue a new one, so that even if it was scraped, it no longer works.
Rotation is the step that actually protects you, and it comes first for a reason. Cleaning the git history matters, but it does not undo exposure that already happened; a key that was public for even a short time may already be in someone else's hands. Only after you have rotated should you move on to removing the secret from history.
Purge it from history with BFG Repo Cleaner
Once the key is rotated, remove it from the repository history so it is not handed to everyone who clones the repo. BFG Repo Cleaner is a fast, purpose-built tool for this: it rewrites history to strip a file or replace a secret across all past commits, and it is simpler for this task than raw git commands. You point it at the repo, tell it to delete the secret file or replace the secret text, then run git's garbage collection and force-push the cleaned history.
The modern alternative is git filter-repo, which the GitHub documentation recommends for rewriting history. Either way, the operation rewrites commit hashes, so every collaborator must re-clone or reset to the new history, and any open pull requests may need to be recreated. Coordinate the rewrite with your team so no one reintroduces the old history from a stale clone.
Why deleting the file is not enough
Deleting the key in a new commit feels like a fix, but it only removes the secret from the current version. Git keeps every past commit, so the key still exists in the history and is visible to anyone who inspects earlier commits or clones the repo. A casual remove API key commit actually advertises exactly where to look for it.
This is why the two real steps are rotation and history rewriting, not deletion. Rotation makes the exposed key useless, and rewriting removes it from the past commits where it would otherwise live indefinitely. Deleting the file without doing either leaves a working key sitting in your history for anyone to find.
Prevention layers
Good prevention is layered, so that if one control misses a key, another catches it. The table below shows how the common layers fit together and what each one stops.
| Layer | What it does | What it catches |
|---|---|---|
| .gitignore plus .env | Keeps secret files out of commits | A committed .env file |
| Assistant rules | Tell Cursor to use env vars, not literals | Inlined keys in generated code |
| Pre-commit scanner | Scans staged changes for secrets | A secret before it is committed |
| GitHub push protection | Blocks pushes containing known secrets | A secret before it reaches GitHub |
| Secret scanning | Alerts on secrets already in the repo | A secret that was pushed |
The point of stacking them is coverage. The .gitignore stops secret files, assistant rules stop inlined keys, a pre-commit scanner catches what the first two miss, and GitHub push protection and secret scanning act as a final net at the remote. No single layer is perfect, but together they make a committed key unlikely.
Emergency response checklist
If a key is already exposed, act in order rather than at random. The checklist below puts the steps in the sequence that actually protects you.
| Step | Action | Done? |
|---|---|---|
| Rotate | Revoke the leaked key and issue a new one | [ ] |
| Assess | Check the provider's logs for any misuse | [ ] |
| Purge history | Remove it with BFG Repo Cleaner or git filter-repo | [ ] |
| Force-push | Push the cleaned history; have collaborators re-clone | [ ] |
| Prevent recurrence | Add a pre-commit scanner and push protection | [ ] |
The order matters more than anything. Rotating first neutralizes the exposed key, assessing tells you whether it was already misused, and purging history plus adding prevention stops it from recurring. Skipping straight to history cleanup without rotating leaves you exposed to a key that may already be compromised.
Scan the build too
Repository hygiene handles your source and its history, but a secret can still reach users if it was compiled into the app itself, for example an assistant inlining a key that then ships in the build. That copy is not in a .env or a commit you can rewrite; it is in the artifact your users download.
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 catch a key that made it into the shipped app. To be clear about the boundary: PTKD does not rewrite your git history, rotate your keys, or configure Cursor. It checks the built artifact for the exposed secrets that repository cleanup alone cannot reach.
What to take away
- Cursor hardcodes keys because inlining a literal is the quickest way to satisfy a prompt; make the safe path the default with rules and guardrails.
- Keep secrets in a git-ignored .env, load them from environment variables, and commit only a placeholder .env.example.
- If a key reached GitHub, rotate it first, since bots find exposed keys within minutes, and treat it as compromised.
- Remove the key from history with BFG Repo Cleaner or git filter-repo, then force-push and have collaborators re-clone; deleting the file alone does not help.
- Add a pre-commit scanner and GitHub push protection, and scan your build with PTKD.com for secrets that reached the shipped app.



