BusinessCodeCross Team
Secrets audit for an AI-built app (2026)
Find keys in the live bundle, public env prefixes, and git history. Rotate what shipped. Cap OpenAI and Claude spend. Turn on GitHub push protection.
Business
13 min
- Find
- Three places
- Rotate
- New then off
- Cap
- Two shops
Bundle, prefixes, history
The old key is still a door
OpenAI and Claude spend
After you launch an app built with an artificial intelligence builder, search the live JavaScript the browser downloads for keys. Search names that start with NEXT_PUBLIC_ or VITE_. Search every past commit, not just the latest files. Rotate any key that shipped. Turn on GitHub secret scanning and push protection. Cap spend on OpenAI and Anthropic keys. A leaked key is a live door until you turn it off.
CodeCross wrote this guide. We are an Austin, Texas studio at 1606 Headway Cir STE 9212. Our engineers work on US Central hours. Start at Austin app development company. We also build phone apps. Method and numbers live on proof. Short builder pages live on vibe coding.
This page is the keys card you keep after a stranger can already open the app. It is not the restore card. That sits on backup and restore an AI-built app. It is not the row-rules card. That sits on Supabase hardening after an AI builder. It is not the repo-ownership card. That sits on GitHub handoff after an AI builder. It is not the watch list. That sits on watch an AI-built app after launch. The people list sits on hire a developer for an AI-built app. This page asks: which keys shipped, and which ones can a stranger still use tonight?
Who this is for
You shipped from Lovable, Bolt, Replit, v0, Cursor, or another AI builder. A live host answers. People who are not you can create an account. The chat pasted a payment key, a database key, or an OpenAI key into a file. You want to know what a stranger can read before you buy ads.
You are not this page if the app still lives only on a laptop, if only the founder can log in, or if you cannot take the live URL down tonight. Those are harden and people jobs. Open the hire page if the only login is a contractor inbox. Open the backup page if you need a secrets list you can restore. Open the Supabase page if you need row rules, not a key hunt.
What a secrets audit means
A secret is a string that unlocks a shop: a payment key, a database master key, an OpenAI or Claude key, a mail key. An env var is a named setting the host injects at run time. It is not a line you paste into chat. A client bundle is the JavaScript file the browser downloads. Anyone can open it. Git history is every past commit, not just the latest files. Rotation means you issue a new key and turn the old one off.
An audit is those four searches plus one rotate plus one spend cap. OWASP’s secrets cheat sheet says teams still hard-code API keys and database passwords in source and config. OWASP’s DevSecOps note is blunter. Once a secret reaches a git repository it must be treated as compromised. History is hard to scrub. Repos get cloned. Bots scan public commits within seconds of a push.
GitGuardian’s 2026 State of Secrets Sprawl counted 28,649,024 new secrets in public GitHub commits in 2025. That is up 34 percent from the year before. AI-service secrets hit 1,275,105, up 81 percent. Commits helped by AI tools leaked secrets at twice the public-GitHub baseline. Of valid secrets first seen in 2022, 64 percent were still live in January 2026. Detection is not the hard part. Turning the old key off is.
Six places a founder actually looks
A master database key in client code
A Supabase secret or service-role key skips every row rule. It never belongs in the browser.
Host env the whole team can read
Vercel Config values stay readable to people with project access. Secret values are write-only after save.
Chat, tickets, and notes
GitGuardian says 28 percent of incidents start outside the repo. A Slack paste is a leak.
Git history
Every past commit on every branch. Deleting the file today does not erase last Tuesday.
Public env prefixes
Names that start with NEXT_PUBLIC_ or VITE_ are copied into that bundle at build time.
Live browser bundle
The JavaScript a stranger can download. Open it. Search for sk_, eyJ, sb_secret_, and service_role.
Keys in the live bundle
Open the live URL in a private window. View source. Open the largest .js file. Search for sk_, eyJ, sb_secret_, service_role, OPENAI, ANTHROPIC, and STRIPE. If a builder put a server key in a React file, it is in that download. You do not need a pentest shop for this step. You need a find box.
OpenAI’s key-safety guide is plain. Never deploy the key in a browser or a phone app. A stranger who copies it can spend on your account. Route the call through your own server. Anthropic treats a Claude key like a card number. If someone else uses it, they spend on your bill. Add it as an encrypted secret on the host. Never drop it into source or a config file the builder can commit.
A publishable analytics id can live in the bundle. A secret cannot. If the builder named a Stripe secret, an OpenAI key, or a Supabase master key with a public prefix, treat it as shipped. Rotate it before the next deploy. Then rebuild. A rotate without a rebuild leaves the old value in the last JavaScript file.
Public env prefixes
Next.js copies any name that starts with NEXT_PUBLIC_ into the JavaScript the browser gets. The value is frozen at next build. Changing the host later does not change an old deploy. Vite does the same for names that start with VITE_. Vite says those names should not hold API keys. Put the real secret on a server or an edge function.
Netlify lists the same prefixes: NEXT_PUBLIC_ for Next, REACT_APP_ for Create React App, GATSBY_ for Gatsby. It says do not copy sensitive values into the built site. Anyone who inspects the files can read them. Vercel stores env vars encrypted at rest. Config values stay readable to people with project access. Secret values are write-only after you save. A change applies only to new deploys. The last public build still holds the old NEXT_PUBLIC_ string.
Supabase’s key guide shows the split in one table. Anything you ship — browser, phone app, CLI — uses the publishable key. Anything you control — server, Edge Function, cron — uses the secret key. Their sample .env uses NEXT_PUBLIC_ for the publishable key and a name with no public prefix for the secret. If the builder wrote NEXT_PUBLIC_SUPABASE_SERVICE_ROLE or VITE_OPENAI_API_KEY, that is the leak. The prefix is the ship order.
Looks hidden vs actually secret
Looks hidden
Key in a .env on Vercel
Name starts with NEXT_PUBLIC_. The last build already shipped it.
File deleted today
Yesterday’s commit still has the string. Clones and forks still have it.
Private GitHub repo
GitGuardian finds internal repos six times more likely to hold a hardcoded secret.
Anon key in the browser
Fine only when row rules and grants are tight. A service-role key is never fine there.
If the right column is empty on any row, treat the key as live and rotate it. ↓If the right column is empty on any row, treat the key as live and rotate it.
Actually secret
Server-only name
No NEXT_PUBLIC_ or VITE_ prefix. Read only in a server route or function.
Rotated after any ship
New key in the host. Old key off at the shop. New deploy after the swap.
Push protection on the repo
User-level protection covers public repos. Repo-level protection is a separate switch.
Publishable key plus row rules
Supabase publishable or anon key, with RLS on and grants cut back.
Keys in git history
Builders love a committed .env. Next.js says you almost never want those files in the repo. Anthropic says add .env to .gitignore if you use dotenv. That stops the next commit. It does not clean the last one.
GitHub’s secret scanning walks the entire git history on all branches. It looks for API keys, passwords, tokens, and other known types. It also scans issues, pull requests, discussions, wikis, and secret gists. Public repositories get this for free. Private org repos need GitHub Secret Protection. When it finds a key, GitHub says rotate it at once. Rewriting history is extra work and often unnecessary once the key is dead.
GitHub’s history guide puts rotation first. If the secret is a password, token, or key, revoke or rotate it before you touch git. A dead key cannot be used. Then decide whether a rewrite is worth the side effects. A force-push does not erase clones or forks. Cached views and pull-request diffs can still hold the string. A teammate who pulls and pushes the old clone puts the leak back. GitHub Support will help purge cached views only when rotation cannot close the risk.
Search history yourself if scanning is off. git log -p and git grep across all commits will do. Look for sk_live, sk-ant, eyJ, sb_secret_, and SERVICE_ROLE. If you find one, rotate first. Then add the filename to .gitignore. Then turn on push protection so the next paste never lands.
New secrets in public GitHub commits
millions2025
29M
Exact 2025 count: 28,649,024. Largest single-year jump GitGuardian has published.
New secrets detected, millions
Sourced from GitGuardian State of Secrets Sprawl 2026. Yearly bars as published: 11M, 14M, 18M, 21M, 29M. 2025 exact count: 28,649,024. AI-service secrets in 2025: 1,275,105, up 81 percent. Of valid 2022 secrets, 64 percent were still live in January 2026.
Supabase anon, service-role, and row rules
Most AI-built apps sit on Supabase. The shop now ships a publishable key and a secret key. The old names were anon and service_role. Supabase is deprecating those old JWT keys by the end of 2026. Creating the new keys does not turn the old ones off. You must disable the legacy keys in Settings, API Keys as a separate step.
A service-role key is the master key for the project. The matching Postgres role has BYPASSRLS. Row level security is a Postgres rule that limits which rows a role can see. Policies never apply to a secret key. Supabase’s RLS guide says the secret key is for admin work that must skip those rules. Securing your data is blunt. Secret and service-role keys are never safe to expose. Use them only on the backend.
The publishable key, or the old anon key, is meant to ship. It only reaches what row rules and table grants allow. Write policies for the anon and authenticated roles. New tables on older projects often grant every verb to those roles. Adding a policy does not take the grant back. That proof lives on the Supabase hardening page. This page only asks: is the master key in the browser?
Supabase now blocks a secret key from a browser User-Agent and returns 401. An attacker can still call the API from a script. If the key leaked, follow their rotate steps. Create a new secret key. Replace it everywhere. Confirm every job uses the new one. Then delete the old secret key. For a legacy service_role JWT, deactivate the legacy keys in the same dashboard. Deactivation can be reversed if you missed a client. Deletion of a new secret key cannot.
Rotate, then restrict
Rotation is a new key plus a dead old key. A new line in Vercel without killing the old key at the shop is a rename. The leaked string still works.
OpenAI wants a unique key per person. Sharing keys is against their terms. Create project keys with an expiry. Before a key expires, make a replacement, point the app at it, then revoke the old one. Admins can set a maximum lifetime so new keys cannot live forever. Never put the key in a client. Never commit it. Prefer an env var named OPENAI_API_KEY. If you think it leaked, rotate it from the API Keys page the same day.
Anthropic asks for the same shape. Rotate on a clock, for example every 90 days, by creating a new key and turning the old one off. Use different keys for development, test, and production so a leaked lab key does not open the live shop. If you suspect a leak, delete the key from the Claude Console API keys page. Anthropic also partners with GitHub. If a Claude key lands in a public repository, GitHub notifies Anthropic and Anthropic deactivates the key, then emails you.
Scoped keys limit the blast. OpenAI lets you assign permissions on a key and restrict which IPs may call. A leaked key that can only hit one model from your host is a smaller fire than a leaked owner key. Anthropic wants separate keys per job. Supabase wants a separate secret key per backend component so one leak is one rotate. Do not hand a hired person the only master key in chat. Invite a role. Keep the company as the account holder.
Secret scanning and push protection
Scanning finds a leak after the commit. Push protection stops the push. You want both.
GitHub push protection blocks a secret before it reaches the repo. It watches command-line pushes, the GitHub web editor, file uploads, REST API writes, and public-repo MCP calls. There are two switches. Push protection for users is on by default on GitHub.com and stops you pushing secrets to public repos. Push protection for repositories needs GitHub Secret Protection. It is off by default. A repo admin, org owner, or security manager must turn it on. User-level protection does not cover a private company repo.
Anthropic tells you to turn on the provider’s scanner and to run a tool such as Gitleaks in CI. OpenAI tells you not to commit the key. GitHub tells you to add .env to .gitignore, avoid git add ., and enable push protection. None of that replaces a rotate for a key that already shipped.
Spend caps on AI keys
A leaked OpenAI or Claude key spends until a person or a cap stops it. Scanning does not stop a charge.
OpenAI splits alerts from hard limits. A spend alert mails you and traffic continues. A hard spend limit returns HTTP 429 with organization_spend_limit_exceeded or project_spend_limit_exceeded. Enforcement is not instant, so spend can go a little past the number. Set the cap on the organization or the project. The key-safety guide says enable hard enforcement if you want a leaked key to stop. Alerts alone will not.
Anthropic says a leaked key is a card number. For custom-limit orgs, set spend limits on the Billing page and workspace rate limits on the Rate limits page. Those limits are the brake after a leak or a runaway script. Their rate-limits page also publishes monthly spend caps by tier: 500 dollars on Start, 1,000 dollars on Build, 200,000 dollars on Scale. When the cap hits, requests return 429 until 00:00 UTC on the first day of the next month, unless you raise the limit. Auto-reload keeps the card charging. Treat that as a risk if a key leaked.
Do this before ads, not after the first 400-dollar night. Open the OpenAI limits page. Turn on a hard monthly cap you can afford to lose. Open the Claude billing page. Set a workspace cap below the org cap. Then watch usage once a day in the first live week. A spike you did not cause is a leak until you prove it is not.
What to do this week
Keep the week small. You do not need a security team. You need a search, a rotate, and a cap.
- Open the live bundle. Private window. Largest .js file. Search for sk_, eyJ, sb_secret_, service_role, OPENAI, ANTHROPIC, STRIPE.
- List every public prefix. NEXT_PUBLIC_, VITE_, REACT_APP_, GATSBY_. Any secret on those names is already in the last build.
- Search git history, not just the latest tree. Turn on GitHub secret scanning. Public repos already have it.
- Rotate what shipped. New key at the shop. New value on the host. Old key off. New deploy. Confirm the old key fails.
- Cap OpenAI and Claude spend with a hard limit, not only an alert.
- Turn on repo push protection if the repo is private. User-level protection does not cover that repo.
- Write the secrets list on the backup card. Name who can rotate tonight. Do not paste the only key into chat.
If a door is still red — master key in the bundle, no spend cap, history still holding sk_live — stop buying ads and finish the keys. Pointing a new domain at the app on Monday does not turn off a leaked key.
This week, in order
01 →
Search the bundle and prefixes
Live JavaScript plus NEXT_PUBLIC_ and VITE_ names. Write every hit.
02 →
Search history and chat
Every commit. Slack and tickets too. GitGuardian says 28 percent of incidents start outside the repo.
03 →
Rotate, then cap
New key. Old key off. New deploy. Hard spend limit on OpenAI and Claude.
04
Scan and block the next paste
Secret scanning on. Repo push protection on. Secrets list on the backup card.
Next steps
Search the live bundle. Search public prefixes. Search history. Rotate what shipped. Cap spend. Turn on scanning and push protection. Write who can rotate tonight. Customers should not be the first people who notice a stranger is generating on your OpenAI bill.
CodeCross is a product studio. The week after an AI-built launch is a live host, keys a stranger cannot spend, and a rotate you can run without the builder chat. If a master key is still in the bundle and you want a second pair of eyes, book a conversation.
FAQ
If I delete the key from the latest files, is the leak gone?
No. GitHub scans the entire history on every branch. Their history guide says rotate first. A deleted line does not kill the string in last Tuesday’s commit, in a fork, or in a pull-request diff. A dead key is the fix. A rewrite is optional and easy to undo if a teammate pushes an old clone.
Is a Supabase publishable key a leak if it is a key your site shows to every visitor?
No. Supabase says the publishable key, and the old anon key, are meant to ship. They only reach what row rules and grants allow. Rotate if you also shipped a secret key or a service-role key, or if row rules are off. The harden page is where you prove a stranger cannot read another user’s row.
Does GitHub push protection on my user account cover a private company repo?
No. GitHub says user-level push protection is on by default and only stops secrets going to public repositories. Repo-level protection is a separate switch. It needs GitHub Secret Protection and is off until an admin turns it on. A private company repo needs that switch.
If secret scanning is on, can I skip a spend cap on the OpenAI key?
No. Scanning tells you after the commit. A leaked key spends until you rotate it or a cap stops it. OpenAI says alerts do not stop traffic. A hard spend limit does, with a 429. Anthropic treats a leaked Claude key like a card number and wants a billing cap as the brake.
Should a hired developer get the service-role key in chat?
No. Supabase says never send a secret key over chat, mail, or SMS. Have the person read it from the dashboard, or use an encrypted password-manager share. Invite a role. Keep the company as the project owner. The hire checklist covers seats. This page covers the key.
Does rewriting git history replace rotation?
No. GitHub says revoke or rotate first. A rewrite does not reach forks, old clones, or cached views. GitGuardian found 64 percent of valid 2022 secrets still live in January 2026. The missing step is almost always the rotate, not the filter-repo run.
Thirty minutes with a senior teammate — honest next steps.
Want a second pair of eyes on your app?
Tell us what you built and where it's stuck. We'll tell you what it takes to make it ready for real users.
Prefer writing? Send project details on the contact page.