If you shipped your app or store with an AI assistant or a no-code stack, there is a real chance a secret key ended up in the browser. It happens constantly: a .env value gets inlined into a build, a quick prototype calls an API directly from the client, or a config object meant for the server gets bundled with the rest of your JavaScript. Once a secret is in the page or in a script the browser downloads, anyone who opens DevTools can read it — and so can automated scrapers.
This page explains what an exposed frontend secret looks like, what LaunchTrust looks for, why store reviewers and security expectations care, and exactly how to remove and rotate a leaked key.
What LaunchTrust checks
The secret_leak detector is a passive check. It fetches the public URL you give it and reads the page's HTML. It then finds up to four same-origin <script src="..."> bundles referenced by that page, downloads each one (capped in size), and combines everything into a single text corpus. It does not log in, submit forms, run your code, or touch cross-origin scripts.
Against that corpus it matches a small set of high-confidence secret patterns — the kinds of strings that are very unlikely to appear by accident:
- OpenAI keys (
sk-/sk-proj-prefixes) - Anthropic keys (
sk-ant-) - Stripe live secret keys (
sk_live_/rk_live_) - AWS access key IDs (
AKIA…) - Google API keys (
AIza…) - GitHub tokens (
ghp_…,github_pat_…) - Slack tokens (
xox…) - PEM private-key blocks (
-----BEGIN … PRIVATE KEY-----) - A softer signal: the literal
service_roleassignment used by some database SDKs
The detector reports one of three signals:
- Detected — at least one of these patterns appears in the page or its same-origin scripts. Severity is reported as high when a high-confidence prefix matches (for example a Stripe live key or an AWS key ID) and medium for softer signals like a
service_rolereference. - Not detected — none of these high-confidence patterns were found in the page or the scripts it scanned.
- Unable to determine — the page could not be fetched, so nothing could be inspected.
Two honest caveats. First, the scan looks at the page and a handful of same-origin bundles, not your entire deployment, so "not detected" means these patterns were not seen here, not "you have no secrets anywhere." Second, a "detected" signal is a pattern match, not proof the key is live — it is your cue to verify and rotate, not a verdict. The detector deliberately avoids matching hyphenated CSS class names and similar noise, but you should always confirm a hit by hand.
Why it matters
A secret key in the frontend is a cybersecurity exposure, plain and simple. The point of a secret key is that only your server holds it. When it lands in the browser, the trust boundary is gone:
- Billing and abuse. A leaked OpenAI, Anthropic, or cloud key can be used by strangers, and the bill is yours. Stolen LLM and cloud keys are actively harvested from public sites.
- Data and account compromise. A database
service_rolekey or a private-key block can bypass row-level security or impersonate your backend entirely. - App store and platform review. Mobile and web app review processes expect that you do not embed credentials that grant access to backend services in client-side code. A reviewer or an automated check that finds a live secret can trigger rejection or removal.
- Regulatory exposure. If a leaked key enables unauthorized access to personal data, that can become a security-and-breach issue under regimes such as the EU's GDPR-aligned expectations, which commonly require appropriate technical measures to protect personal data.
This is the single most common high-fear finding for indie and "vibecoder" launches, which is why it sits at the top of a pre-submission security pass.
A concrete example
Here is what a leak looks like in a bundled file, with the secret masked so nothing real is shown:
// build output: /assets/app-4f2a.js
const stripe = require("stripe")("sk_live_REDACTED_DO_NOT_SHIP_THIS");
const ai = new OpenAI({ apiKey: "sk-proj-REDACTED" });
If a string like sk_live_… (a Stripe live secret) or sk-proj-… (an OpenAI key) sits in a file the browser downloads, the detector flags it as detected / high. The fix is never to "hide" the string — minification and obfuscation do not protect a secret a client must read. The key has to move off the client entirely.
How to address it
- Confirm the hit. Open the flagged page, view source or DevTools → Sources, and search for the prefix (for example
sk_live_orAKIA). Check whether the matched string is a real credential or a harmless placeholder. - Rotate immediately if it is real. Treat any committed-or-shipped secret as compromised. Generate a new key in the provider's dashboard and revoke the old one. Rotation comes first because removal alone does not invalidate a key someone already copied.
- Move the call server-side. Route requests through your own backend or a serverless function that holds the secret as an environment variable. The browser calls your endpoint; your server adds the secret and calls the provider.
- Use the right key type on the client. If you genuinely need a client-side key, use a publishable/restricted key (for example Stripe's
pk_…) scoped to the minimum it needs — never a secret/live key. - Purge it from the build and history. Remove the value from your source, rebuild, and confirm it is gone from the new bundle. If it was committed to git, also scrub it from history and check for a publicly exposed
.envor.gitdirectory. - Add guardrails. Wire a secret scanner into your build or CI so a key cannot ship again, and tighten your security headers and transport security while you are at it.
Check this in 30 seconds
Paste your live URL into LaunchTrust's free scanner. It fetches the page and its same-origin scripts and tells you whether any high-confidence secret pattern is detected, not detected, or unable to determine — no signup needed for the basics. If you publish a security.txt file too, researchers have a clear way to report anything you miss. It is the fastest way to catch a leaked key before a customer, a scraper, or a store reviewer does.
FAQ
Does a "not detected" result mean my app has no exposed secrets? No. It means the specific high-confidence patterns were not found in the page and the same-origin scripts the scanner read. Other files, other pages, or unusual secret formats may not be covered. Treat it as one useful signal, not an all-clear.
Does this make my app secure or certify that it has no leaks? No. LaunchTrust surfaces signals to help you find gaps before launch. It is not legal advice, not a security certification, and not a guarantee of store approval. A "detected" result is a prompt to verify and rotate; a "not detected" result is not proof of security.
The scanner flagged a key but it is just a test/placeholder. Is that a problem? The detector matches a pattern, so placeholders that look like real keys can match. Confirm by hand. Even so, avoid shipping anything that resembles a live secret, since it adds noise and can alarm reviewers who run their own checks.
I rotated the key — am I done? Rotation revokes the leaked credential, which is the urgent part. Then remove the value from your code and build, scrub it from git history if it was committed, and re-scan to confirm the new bundle is clean.
Compliance aid, not legal advice. LaunchTrust reports signals, not a verdict or certification.