Stripe secret key exposed? Roll it now, then fix the code
A Stripe live secret key gives full API access to your account: charges, refunds, customer data. If one shipped in your frontend, this page tells you what to do in the next ten minutes and how to restructure the code so it can't happen again.
Short answer: If sk_live_ or rk_live_ appears in your site's JavaScript, roll the key in the Stripe Dashboard under Developers, API keys, then review recent payments, refunds and events. Keep only the pk_live_ publishable key in the browser and move every secret-key call into a server function.
This page covers Stripe only, because a Stripe secret key is the leak with the most direct financial impact. For other providers, use the leaked API key checker.
Which Stripe keys are safe where
Stripe gives you two kinds of keys per mode, and confusing them is the root of almost every Stripe leak in AI-built apps:
pk_live_...(publishable): meant for the browser. Stripe.js and Checkout use it. Safe to ship.sk_live_...(secret): full API access. Can create charges, issue refunds, read customers, and change settings. Server only.rk_live_...(restricted): a secret key with permissions you choose. Safer on a server, still never for the browser.sk_test_/pk_test_: test mode equivalents. No real money, same rules.
The typical failure: you ask the AI to "add a checkout button" and it calls stripe.checkout.sessions.create() from a React component, which needs the secret key, so it puts sk_live_ in a VITE_STRIPE_SECRET_KEY variable. That works, and it hands the key to every visitor. The VITE_ variables guide explains why; if an AI key leaked alongside it, see OpenAI key exposed.
Check it yourself [manual]
- Open your deployed site, open DevTools, go to Network, filter JS, reload.
- Search all sources (Ctrl+Shift+F, or Cmd+Option+F on a Mac) for
sk_live_, thenrk_live_, thensk_test_. - Search for
api.stripe.com. Your browser should talk to Stripe through Stripe.js or a redirect to Checkout, not by calling the REST API with a bearer key. - Walk through the checkout flow once and search again, in case payment code loads on demand.
Fix it, in this order
- Roll the key [manual]. In the Stripe Dashboard go to Developers, API keys, open the menu next to the secret key, and choose Roll key. Stripe lets you keep the old key working for a short period so your server doesn't break, but if it leaked, expire it immediately. A brief checkout outage is cheaper than an attacker with refund rights. For a leaked restricted key, roll or delete that key the same way.
- Review activity [manual]. Check Payments, Refunds, Customers and the Workbench logs or events for anything you didn't do since the key first shipped. Look for refunds you didn't issue, new webhook endpoints, and API requests from unfamiliar IPs. If something is wrong, contact Stripe support.
- Move secret-key calls to the server. The browser asks your endpoint to create a Checkout Session; your endpoint uses the secret key and returns the session URL. The browser only ever sees
pk_live_and the redirect URL. Price and quantity must be decided on the server, from your own product IDs, never from amounts the browser sends.Remove every use of a Stripe secret key (sk_live_, sk_test_, rk_live_) from client-side code and any VITE_ or NEXT_PUBLIC_ variable. Create a server-side function "create-checkout" that reads STRIPE_SECRET_KEY from a server-only secret, takes only a product ID from the client, looks up the price on the server, creates a Stripe Checkout Session, and returns its URL. The frontend should only use the publishable key, if it needs one at all.
- Store the new key server-side only [manual]. Put it in your function's secrets (for Supabase Edge Functions:
supabase secrets set STRIPE_SECRET_KEY=...; on Netlify or Vercel, an environment variable with no public prefix), then redeploy. - Prefer a restricted key [manual]. If your server only creates Checkout Sessions, create an
rk_live_key with just the permissions it needs, so a future leak can do less. - Search again. Repeat the DevTools check on the live site. No
sk_orrk_strings should remain.
Make it stick
- Verify webhook signatures on your server with the endpoint's signing secret (
stripe.webhooks.constructEventin Node). Otherwise anyone can post a fake "payment succeeded" event to your webhook URL. - Grant paid access from the webhook or a server-side lookup, never from a "success" page the browser can open directly.
- Run gitleaks or GitHub push protection so a secret key pasted into code is caught before deploy.
What the free scan checks here (and what it doesn't)
It fetches your live page plus up to 25 linked scripts and flags Stripe sk_live_ and rk_live_ keys, along with OpenAI, Anthropic, AWS, Google and Supabase secret keys. It also checks /.env, /.git/HEAD, HTTPS and four security headers.
It does not check webhook signature verification, your Stripe Dashboard settings, test keys, or how your server decides prices. Those are [manual].
FAQ
- Is a Stripe publishable key safe to put in the frontend?
- Yes. Keys starting with pk_live_ or pk_test_ are designed for the browser and can only do things like create tokens for Stripe.js. Secret keys (sk_live_) and restricted keys (rk_live_) must only be used on a server.
- Is a leaked sk_test_ key an emergency?
- Less so. Test keys can't move real money, but they expose your test data and show the code is wrong. Roll it, and fix the code before you switch to live keys, because the live key will leak the same way.
- Does the free scan detect Stripe keys?
- It flags sk_live_ and rk_live_ keys found in your page HTML or in up to 25 scripts it links to. It doesn't flag pk_ keys (they're public) or test keys, and it doesn't check your webhooks or Stripe settings.