To check if your Stripe secret key is exposed, search your git history with git log -p --all | grep -E "sk_live_[a-zA-Z0-9]{20,}", then search your live site's JavaScript for sk_live_. If either finds a key, roll it in the Stripe dashboard right away. Run gitleaks to keep checking.
How do I check if my Stripe secret key is exposed?
Run two checks: one on your repo history and one on what the browser downloads. Run this against your own repo right now:
git log -p --all | grep -E "sk_live_[a-zA-Z0-9]{20,}"
That walks every commit in every branch, not just what's currently checked out. If it returns nothing, good, that's one leak path closed. If it returns a key, don't just delete the line and commit again, that key is permanently in history unless you rewrite it, and even then Stripe should treat it as burned.
Then open your live site, view source or check the Network tab, and search the loaded JavaScript for sk_live_. If it's there, same answer: rotate now, fix the env var prefix after.
How do Stripe secret keys usually get exposed?
Stripe secret keys usually leak in three ways: bundled into the frontend, committed to git history, or pasted somewhere that isn't your codebase.
Bundled into the frontend. The publishable key starts with pk_live_ and is meant for the browser. The secret key starts with sk_live_ and belongs on your server only. An AI coding tool building your checkout hits an API call that fails client-side and grabs whatever env var has STRIPE in the name. If your secret key sits in a variable named NEXT_PUBLIC_STRIPE_KEY or VITE_STRIPE_SECRET, the NEXT_PUBLIC_ or VITE_ prefix tells the bundler to inline the value into the JavaScript every visitor downloads. I covered this mechanism in why your API key ends up in the frontend bundle.
Committed to git history. Someone hardcodes the key while testing, catches it, deletes the line, commits again. The original commit is still sitting in .git/, and if the repo is public or ever becomes public, git log -p walks right back to it.
Pasted somewhere that isn't your codebase at all. Slack, a support ticket, a ChatGPT session asking for debugging help, a screen-share recording. Someone pastes a key into ChatGPT to ask why a webhook isn't firing, and now sk_live_ sits in a third-party chat log. None of these show up in a code scan.
What tools find exposed API keys?
Two free tools cover most of it: gitleaks and GitHub secret scanning. gitleaks is open source and scans a repo's full history for key patterns like sk_live_, so run it in CI rather than once. GitHub's secret scanning also covers Stripe keys: push a live key to a public repo and GitHub reports it to Stripe, which may notify you before you've noticed.
For the browser side, view source and the Network tab in Chrome DevTools will show a key inlined in the bundle. I go through where these tools fit against static analyzers like Semgrep and Snyk in the security scan tools comparison.
What should I do if my Stripe secret key was exposed?
Rotate it first. In the Stripe dashboard, go to Developers, API keys, roll the key, and set the old one to expire now rather than on a delay. Killing the old key matters more than however long you spend investigating how it got there.
Then check your webhook handler. If it skips signature verification, anyone who finds your endpoint URL can POST a fake "payment succeeded" event and your app will believe them. It's the same shape as an exposed anon key with no RLS behind it: the check that should gate access never runs.
Git history is the part people skip. The old key is still readable in every clone of that repo that exists anywhere, including forks you don't control. Rotating in Stripe is what actually neutralizes it, rewriting history is just cleanup.
What are the Stripe secret key best practices?
Keep sk_live_ on the server only, in an env var without a NEXT_PUBLIC_ or VITE_ prefix, such as STRIPE_SECRET_KEY in .env.local. Use pk_live_ in the browser, where it can load Stripe.js, tokenize card details and confirm a payment your server already set up.
Never paste a .env file into a chat window. Verify webhook signatures with your signing secret. Run gitleaks in CI so a key never lands in a commit. A secret key can issue refunds, pull customer records and list every payment your business has taken, so treat it that way.
Why this keeps happening in AI-built apps specifically
None of this is unique to AI tooling. Developers have leaked Stripe keys since Stripe existed. What's changed is the volume and the speed. When I scanned 66 live AI-built apps, 13 of the 32 running on Supabase had a table anyone could read with the public key. Different key, same shape of problem: a tool that optimizes for "the payment went through" has no reason to ask whether the key that made it work belongs in a .env.local file that never leaves the server or in a bundle that ships to every browser. The demo doesn't care. Your Stripe balance does.
If you want someone to go through your whole payment flow before you launch, not just the key but the webhook verification and the checkout session handling around it, that's what a Ship Check is. One option among a few. The free version is the two commands above, run them first.