Checklist
How to keep API keys out of AI-generated code
Short answer. Give the agent names of environment variables, not the secret values. Publishable keys may ship in the client when the vendor says so. Secret keys, service-role keys, and webhook signing secrets stay on the server. If a value landed in a chat or a commit, rotate it before you redeploy.
Updated 21 Oct 2026
This is a practical checklist, not a guarantee. It is not a penetration test, a compliance audit, or proof that an app is safe to launch.
The frontend checklist is how to search the bundle after the app is built. This page is how to keep the secret out of the code an agent writes. They overlap. Do both.
Hand the agent a name, not a value
Agents inline a key because the request fails without it. Put the value in the host's secret store, or in a local file that git and the agent ignore, and tell the agent the variable name.
Cursor's help center says .env files are ignored by default, and that .cursorignore blocks Agent from extra paths you list. It also says a terminal command or an MCP tool can still read an ignored file. Lovable's secrets docs say VITE_ values belong in .env because they are public build-time values, and that API keys for Stripe, Resend, or a model belong in Secrets, which Edge Functions read. Bolt's secrets docs say the same shape: server functions read the secret, and the value should not be plain text in the code.
Do not paste a live key into the chat to "make it work." The transcript is another copy.
Sort the key before the agent touches it
| Key | Vendor's instruction | Where it may live |
|---|---|---|
Stripe pk_ | Publishable. Safe in frontend code. It cannot create charges or read account data. | Client |
Stripe sk_ or rk_ | Not safe to expose. Stripe says a restricted key is the one to give an agent, with only the permissions that job needs. | Server environment |
Stripe whsec_ | Webhook signing secret. Verify the raw body with it. It is separate from the API key. | Server environment |
| Supabase publishable or legacy anon | Safe in the browser only with Row Level Security and least-privilege grants. | Client |
Supabase secret or service_role | Bypasses Row Level Security. Never in the client. | Server environment |
| Firebase API key restricted to Firebase APIs | Public by design. Security Rules authorize the data. | Client config |
| A model, email, or database URL with a password | Treat as secret unless that vendor's docs call it publishable. | Server environment |
Stripe's key docs also say not to put sensitive keys in source or in files you commit, and not to share them in chat or email. Restricted keys are drop-in replacements for secret keys on the server. An unrestricted secret key is the one Stripe says not to start new work with.
Stop the file from being written
- Add
.env,.env.*,*.pem, and downloaded credential files to.gitignorebefore the first agent run. - In Cursor, add the same patterns to
.cursorignoreif they are not already covered. The setup is in how to secure a Cursor-built app. - In Lovable, reject a
VITE_name for a secret. Their product blocks that prefix in the Secrets UI and tells you to use.envonly for browser values. See how to secure a Lovable app. - In Bolt, add the value under the database Secrets tab when the agent asks, instead of into a source file. See how to secure a Bolt.new app.
- On the host, mark the variable as a secret where the host has that switch. Vercel, Netlify, and Render all document a place for this. A
NEXT_PUBLIC_orVITE_prefix still ships the value to the browser on those hosts.
Aikido's free plan lists secrets detection in the IDE, in CI, and in git, including pre-commit blocking. That is a scanner, not a substitute for the ignore file. The product comparison is Snyk vs Socket vs Aikido.
If the secret is already in the code
Rotate it at the vendor first. Assume the old value was copied the moment it was on screen or in git. Then delete it from the working tree, from any committed .env, and from chat logs you control. A new deploy that still contains the old line publishes the replacement.
Search the built JavaScript as well as the source. The steps are the frontend checklist. Supabase's dashboard can issue a new secret key. Stripe's dashboard can roll a key and a webhook secret. Do the rotation in the dashboard, and put only the new value in the server environment.
FAQ
Can I give Cursor my Stripe secret key so it can test payments?
Give it a restricted test key, in an environment variable, with only the permissions that test needs. Stripe's restricted-key docs say those keys are the ones to hand an AI agent, and that an unrestricted secret key can do anything in the account. Do not put either key in a file the agent will commit.
Is the Supabase anon key safe in generated client code?
Supabase says the publishable key, and the older anon key, may be in the browser when Row Level Security and grants are set. The secret key and the service-role key bypass those policies. Generated apps often copy the wrong one because both are on the same settings screen. The setup is row level security for an AI-built app.
What if the agent encoded the key so it is not a plain string?
That is still the secret, split or encoded. Stripe's guidance is to keep sensitive keys out of source. Decoding a string in the client does not make it a server secret. Move the call to a server route and delete the encoded value.
Does ignoring .env fix a key that was already committed?
No. Ignore rules stop a later add. They do not remove a commit that already contains the value, and they do not rotate it. Rotate at the vendor, then remove the value from the tree you deploy.
Sources
Checked 21 Oct 2026.
Newsletter
A short weekly note on new tools and security checklists. No spam, unsubscribe anytime.