GitLab, DigitalOcean, GCP: the infrastructure tokens that leak into bundles
Stripe and OpenAI keys get the headlines because the damage is obvious: money, compute. The quieter leak is the one that doesn't bill you. It hands someone the controls to the thing that runs everything else.
These are the infrastructure tokens. A GitLab personal access token. A DigitalOcean API token. A Google Cloud service-account key. A Doppler secrets token. They don't belong anywhere near a browser, and yet they end up in JavaScript bundles for the same dull reason every secret does: a build script needed one, an env var got prefixed for the client, and the bundler inlined it into a file the whole internet can download.
Why these are worse than a normal API key
A normal API key talks to one service. An infrastructure token often controls the service that holds your other secrets, deploys your code, or owns your whole cloud project. Compromise one and the rest follow.
Each prefix is fixed and distinctive, which is exactly what makes them easy to find in a bundle and easy for a scanner to flag with near-zero false positives. Here's what the common ones unlock when one slips out.
The Google service-account key is the sharp one
Most of these are obvious once you see them. The Google service-account key hides better, because it isn't a short prefixed string. It's a JSON blob, and people paste JSON into config without thinking of it as a secret.
It has a "type": "service_account", a "private_key" field with a real PEM body, and a "client_email" that ends in iam.gserviceaccount.com. That private key is the credential. Whoever holds the JSON can authenticate as that robot identity and do everything its IAM roles allow, which on a hastily-built project is frequently "Editor on the whole project." If that JSON ships in a client bundle or sits in a public config file, your GCP project has a second owner. We look for that exact JSON shape, not just a loose string, because the structure is what makes it unambiguous.
How they end up client-side
The honest answer is almost never "someone hardcoded an infrastructure token into a React component on purpose." It's indirect.
A CI step writes secrets into an env file that the build then bundles. A .env with a GITLAB_TOKEN gets copied into the image and a misconfigured static export serves it. A service-account JSON gets committed "just for local dev" and then deployed. An env var meant for the server gets a NEXT_PUBLIC_ or VITE_ prefix because someone got a "not defined in the browser" error and made it go away the fast way. Every one of these is a plumbing mistake, and every one ends with a secret in a file the public can read. This is the same failure mode behind a leaked .env file; the only difference is whether the secret leaked as a file or got baked into the JavaScript.
The fix is the same shape every time
None of these tokens have a browser-safe variant. They live on a server, in a secret manager or the deploy environment, and they never get a client prefix. If a build genuinely needs one, it uses it at build time on the build machine and does not emit it into the output.
# .env (these get inlined into the JS bundle)
NEXT_PUBLIC_GITLAB_TOKEN=glpat-aBcD...
VITE_DO_TOKEN=dop_v1_aBcD...
NEXT_PUBLIC_GCP_KEY={"type":"service_account",...}Then rotate every token that was ever exposed and scope its replacement to the minimum it needs, because a token that was public once is public forever no matter how fast you pull it. A GitLab token can be project-scoped and read-only; a service account can hold one narrow role instead of Editor. The leak hurts less when the credential could do less in the first place.
Which of these prefixes is sitting in your shipped JavaScript right now is readable from the outside, because it's in the files you serve to anyone who asks. SurfaceCheckr fetches your pages and linked scripts and scans them for the infrastructure-token formats the major providers use, including the service-account JSON shape, and tells you which one leaked and where. We read what's public and stop there. We can't tell you what roles the token holds or whether it's already been used, only that it's exposed, which is the fact you need before you decide how fast to rotate.
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.