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.

string found in bundleverdict
pk_live_Y2xlcmsueW91cnNpdGUuY29t...production instance, your own domainsafe
pk_test_Y2xlcmsuyourapp.accounts.dev...development instance, on a live domainflagged
Both keys are public by design. The prefix is the tell: pk_test_ on a production site means the live login runs on the shared dev backend.

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.

$ scan yourapp.com
probing yourapp.com from outside, no credentials...
pk_test_ key, decoded host = clerk.yourapp.accounts.devClerk development instance confirmed
scanned host = yourapp.com (not dev/staging/preview)production domain, so dev mode is wrong here
2 exposures visible to anyone. None required a login.
Two conditions together: a decoded Clerk dev host, on a production domain. A test key on a staging URL is fine and doesn't fire.

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_...
The development keys are for local and preview only. A production domain needs the production instance, on your own host, with your own OAuth apps.

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.

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.