Security
Vibe Coding API Key Exposed: How It Happens and How to Fix It Today
You built the app. It works. Users are signing up, or you are about to start charging them. Then someone mentions that a “vibe coding API key exposed” situation is one of the most common ways an AI-built app gets wrecked, and you realize you are not sure where your own keys live. In the frontend bundle? In the repo you pushed to GitHub on day two? In a .env file you never thought twice about?
A non-technical builder who spent around a hundred hours getting an inbox cleanup tool working in Lovable and then Claude Code admitted they had no idea what an env file was until they learned, the hard way, that it is not something you show the public. A founder with a working used-car analysis site asked, before turning on payments, whether it was silly to proceed without understanding the security risks. Neither was careless. Nobody in the room had the job of asking about secrets.
What this problem looks like
The pattern that shows up again and again in builder forums is not one mistake but a family of them.
The first is secrets in frontend code. An engineer who audits AI-built apps described a Next.js project where the AI fetched the app’s configuration from the database and passed the whole object into a client component. The page rendered perfectly. It also serialized every API key into HTML that any visitor could read with view-source. Nothing looked broken, so a vibe check would never have caught it.
The second is the .env file committed to the repo. The builder above discovered this after the fact; a commenter immediately told them to rotate every environment variable. Once a key has been in a public commit, even briefly, assume it has been scraped.
The third is keys that are never rotated. Someone who runs a team cleaning up AI-built apps said secrets in the code is the first of seven problems they find in nearly every codebase. Their advice: grep your own project for the usual key prefixes and words like secret and password, and if anything real shows up, rotate it that day rather than putting it on the post-launch list.
The cost side is where founders feel it. One commenter warned the used-car analysis founder that the most likely way to lose money is a leaked AI API key that an attacker runs up until you notice and shut off billing. Another pointed out how lopsided the trade is: a leak that any basic secret scanner would spot almost immediately can erase what the app earned over months. A founder collecting user data in the EU argued that a breach caused by a hardcoded key may be treated as negligence, with fines and civil claims as possible consequences. That is a question for a lawyer, and one we cover in taking payments and user data with a vibe-coded app.
Why API keys get exposed when you vibe code
This is a mechanism problem, not a character flaw.
AI coding tools optimize for the happy path. When you ask Claude Code, Cursor or Lovable to “connect the app to OpenAI”, the fastest working answer is to put the key where the call is made. If the call happens in the browser, the key ends up in the browser. The model is not weighing a public key (safe to ship to the client, like a Stripe publishable key) against a secret key (which grants billing or admin powers). It is producing code that runs.
Framework boundaries make this worse. Frameworks like Next.js blur the line between server and client code; anything passed into a client component ships to the browser. The AI knows the syntax but rarely reasons about which side of the boundary a value belongs on, which is how the config-object leak above happened.
Git is invisible to the tool. Most AI assistants happily create a .env file and never mention that it must be in .gitignore (the file that tells git which files never to commit). One commenter summarized the whole discipline in two lines: keep secrets in .env, and do not upload .env.
One commenter also noted that models spot this far more reliably when reviewing someone else’s code than they avoid it when writing their own, plausibly because the training data is full of public code doing the wrong thing. That is why the fix is a review loop, not a better first prompt.
How to find, fix and prevent exposed secrets
Work through this in order: find, rotate, then refactor.
Step 1: Find every secret you have right now
Open a fresh Claude Code session in your project (fresh matters; a model that just wrote the code tends to defend it) and ask it to act as a security reviewer hunting for hardcoded credentials. Have it search for patterns such as sk-, api_key, secret, password and token, list every file where a real-looking value appears, and check git history too, because a key deleted from the current code still lives in old commits. The same request works in Cursor, Lovable and Replit.
Then open your deployed site, view the page source, and search for the same strings.
Step 2: Rotate before you refactor
Every key that appeared in the frontend, in a commit, or in a public bundle is compromised. Log in to each provider, generate a new key, revoke the old one, today.
Step 3: Move secret calls to the server
Secret keys belong on the backend, the part of the app that runs on a server you control, not in the user’s browser. In Claude Code, use plan mode so nothing is edited yet and ask it to find every client-side call that uses a secret key and propose moving each one behind a server route that reads the key from an environment variable. Review the plan, then let it execute, and add rate limiting to those routes, because a server endpoint anyone can call is a billing hole with extra steps. This is the same principle behind why the UI cannot be your only security layer.
Step 4: Lock the repo
Add .env and its variants to .gitignore, commit a .env.example with placeholder values, and put real values in your host’s secret manager (Vercel and Supabase both have one). Then install a secret scanner as a pre-commit hook so a leaked key is blocked before it reaches GitHub; Claude Code hooks can run the same scanner after every edit.
Step 5: Make the rule permanent
Add a short section to your CLAUDE.md, the file Claude Code reads at the start of every session: never place secret keys in client-side code, always read secrets from environment variables, never commit .env, and flag any variable that looks like a credential before writing it. Cursor rules files and Lovable project instructions serve the same purpose. Then make the fresh-session attacker’s-view review a habit, and see how to audit a vibe-coded app’s security for the fuller checklist.
How a Red Corner CTO would have prevented this problem
Before the first prompt, a CTO would have spent twenty minutes on a boring conversation: which services will this app talk to, which of those keys are public by design, which are secret, and where will each one live. The answer would have gone into the CLAUDE.md before any code existed, with a .gitignore and a .env.example in the very first push. In week one they would have insisted on a secret scanner in the pre-commit hook, so the “oops, I pushed .env” moment could not physically happen.
During the build, the CTO would have caught the config-object leak in the first code review. They would have looked at every place data crosses from server to client and asked one question per boundary: what is inside this object, and does the browser need all of it? They would have pushed every third-party call that costs money behind a server route with a rate limit, not because the AI wrote it wrong but because that is the default shape of a safe app, and the AI does not know your defaults until you set them.
Before launch, they would have run the attacker’s-view review in a clean session and then again with a different model, treating a green first pass as a starting point rather than a verdict. They would have asked to see the billing alerts and spend caps on every API account, because a key will eventually leak somewhere, and the difference between a bad afternoon and a ruinous weekend is a spending limit. Before you accepted payments, they would have asked whether you know which data you store, why, and who can read it, and pointed you to a lawyer for the parts that are not engineering.
After launch, they would put rotation on a calendar, keep the scanner running on every commit, and review each new integration the same way. None of this is heroic. It is judgment applied at the moments where it is cheap, which is the point of having a CTO in the room rather than calling one after the fire.
Frequently asked questions
Is it safe to put an API key in the frontend of my app?
Only if the provider explicitly designed that key to be public, such as a Stripe publishable key or a Supabase anon key paired with row-level security rules. Any key that can spend money or read private data must live on a server and be read from an environment variable. If unsure, treat it as secret.
I already committed my .env file to GitHub. What do I do?
Rotate every key in that file first, because deleting the file from the repo does not remove it from history. Then add .env to .gitignore, commit a .env.example with placeholders, and move real values into your host’s secret manager.
How do I make Claude Code stop putting secrets in my code?
Add explicit rules to your CLAUDE.md about where secrets live, ask for a plan before any integration that needs a key, and run a review pass in a fresh session before you merge. A pre-commit secret scanner is the backstop for the times the model ignores the rules.
Get a CTO in your corner
Every builder in the threads above was smart, motivated and shipping. None of them had a senior engineer asking the right question at the right moment. Red Corner puts real CTOs in your corner to review your code, answer your questions and guide you from first prompt to launch. Visit redcorner.io and get a CTO in your corner.