Your live site's login is wired to Clerk's development instance
Look at the Clerk publishable key in your frontend. The first thing after the prefix tells you which environment your login is running on. pk_live_ is your production instance. pk_test_ is the development one. If a production site is serving a pk_test_ key, the live app's authentication is wired to Clerk's development backend, and that's a different, weaker system than the one a real site should be using.
This isn't a leaked-secret story in the usual sense. The publishable key is public by design, meant to ship to the browser, and finding one isn't a problem the way a leaked secret key is a fire. What the key leaks here is a fact about your configuration: which environment you're actually on. And on a live domain, the answer development is the finding.
What the development instance actually is
Clerk gives you two instances per app, and they are not the same backend with a flag flipped. The development instance is shared infrastructure built for local work and previews. It runs on *.accounts.dev rather than your own domain, it carries looser rate limits, and it uses Clerk's shared, test OAuth credentials for social logins rather than your own registered app credentials with the providers.
None of that is a problem where it belongs, on localhost or a preview branch. The problem is running your real users through it. Looser rate limits mean less protection against credential-stuffing and abuse on the endpoint that guards every account. Shared test OAuth means your "Sign in with Google" is going through Clerk's test client, not a production app you control, with the consent screens and trust assumptions that come with that. The development instance was never hardened to be the front door of a live product, and on a pk_test_ production site, that's exactly what it is.
How the check avoids crying wolf
A pk_test_ prefix alone isn't enough to be sure, for two reasons, and the check handles both.
First, the prefix has to actually be Clerk's. Other products use pk_test_-shaped strings, so matching the prefix by itself would flag unrelated keys. Clerk's publishable key encodes its instance host in the part after the prefix, base64-encoded. The check decodes that tail and confirms it resolves to an accounts.dev host, which is what makes it a Clerk development instance specifically and not a coincidental match. The signal is read out of the key itself, not guessed from context.
Second, a pk_test_ key is correct and expected on a development domain. So the check skips the scanned host entirely if it's already a dev, staging, preview, or localhost name, the places a test key belongs. It only fires when a development key shows up on a host that looks like production. That's the combination that means something went out wrong: the dev configuration shipped to the live site.
The fix is the production instance
The fix is the swap Clerk expects you to make before launch and that's easy to forget when the dev keys "just work." Create a production instance in the Clerk dashboard, which runs on your own domain instead of *.accounts.dev, register your real OAuth credentials with each social provider against it, and switch the frontend to its pk_live_ publishable key and the matching sk_live_ server secret.
# .env on the production deploy NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...accounts.dev CLERK_SECRET_KEY=sk_test_...
While you're at it, the sk_test_ server secret usually rides along with the pk_test_ key in the same misconfigured deploy. That one is a real secret and shouldn't be in the frontend at all, dev or prod, which is its own secret-key exposure problem if it ended up client-side. Production keys, server secret kept server-only, and the live login is running on the backend that was built for it.
This is readable from outside because the publishable key is right there in your frontend, which is where it's supposed to be. SurfaceCheckr reads it the same way the browser does, decodes the instance host out of the key, and checks it against the domain it found the key on. It doesn't log into Clerk or test your auth flow. It just reads the one public string that says which environment your live login is actually running on, and tells you when that answer is "development" on a site that isn't.
Read next
- What are source maps, and are you handing strangers your original code?The secrets hiding in your JavaScript
- Is your Stripe secret key in your JavaScript bundle right now?The secrets hiding in your JavaScript
- Why is there an AWS key in your build, and who can use it?The secrets hiding in your JavaScript
- Why your "public" key is fine but your secret key is a fireThe secrets hiding in your JavaScript
Find it before someone else does.
Paste your domain. The grade and issue count are free, and you'll see in a couple of minutes exactly what's reachable from outside.