Secrets in Client-Side Code: What Should Never Ship to Browsers

Code that runs in a browser is, by definition, code that is delivered to the visitor. That simple fact has a consequence that surprises newcomers: anything embedded in client-side code, whether a web page, a script file or a mobile app bundle, can be read by anyone who opens the developer tools and looks. API keys, passwords, tokens and private credentials therefore do not belong in client-side code, no matter how obscure the file name or how minified the output. The Client-Side Secret Exposure Checker scans code that you paste in for common secret patterns, such as assignments that look like API keys or tokens. It works on pasted text, so it cannot scan your whole codebase or prove that a secret is valid; it can only flag patterns that look like secrets.

How it works

The patterns a scanner looks for are shapes, not certainties. A long string of random-looking characters assigned to a variable named api_key, secret, token or password is a strong signal. Known prefixes used by popular services are another signal, because many providers issue keys that begin with a recognisable prefix. URLs that embed a username and password, connection strings that include credentials, and private key blocks are further patterns. None of these patterns proves that a value is a live secret: a scanner will also flag sample keys in documentation, test fixtures and placeholder values, which is why every finding needs human judgement. A false positive is an annoyance; a missed real secret is a security incident, so scanners are tuned to err on the side of flagging.

Where it goes wrong

The critical response to a real finding is revocation, not deletion. Deleting a secret from the code does not delete it from the copies that have already been shipped, cached, indexed or downloaded, and a secret that has been visible, even briefly, must be assumed to have been copied. The correct sequence is to revoke or rotate the secret at the provider, so that the old value stops working, and only then to remove it from the code and replace it with a safe mechanism, such as a server-side call that holds the secret out of reach of the browser. Teams that only delete the value, without rotating it, leave a working secret in the hands of anyone who captured it, which is worse than the original exposure because it creates a false sense of safety.

Worked example

Worked example with sample values. Suppose you paste a sample file of 40 lines into the checker. It flags 2 lines: one assigning a sample-looking API key to a variable named api_key, and one embedding a sample password in a connection string. The flagged rate is 2 divided by 40, which equals 0.05, or 5 percent of the pasted lines. If the same file also contained 3 sample keys in comments that are clearly marked as examples, a careful reviewer would still count the findings as 5 flagged lines in total, then dismiss the 3 comment examples as false positives, leaving 2 real findings, because 5 minus 3 equals 2. The arithmetic is simple, but the discipline matters: count every finding, disposition each one, and never let a dismissed false positive hide an undispositioned real one. The sample code here is illustrative, and the sample secrets are not real credentials.

Mistakes to avoid

Prevention is cheaper than response, and it rests on a few habits. Secrets should live in server-side configuration or a secrets manager, never in source files that are shipped to clients. Client-side code that needs a service should call a server endpoint that performs the authenticated request, rather than calling the service directly with a key. Repositories should be scanned before merging and before publishing, and history should be considered exposed if a secret ever entered it, since removing a file from the current version does not remove it from history. Environment files, build outputs and bundled assets all need the same attention, because a secret hidden in one place and copied to another is still exposed.

Putting it into practice

Use the checker as one layer in a broader routine, and handle its input with care: do not paste real secrets into a page you have not verified, and prefer local or trusted scanning for sensitive material. Read every finding, disposition it as real or false positive, revoke anything real before deleting it, and fix the mechanism that allowed the secret into client-side code in the first place. A scan that finds nothing is not proof of safety, since a scanner only recognises patterns it knows, but a scan that finds something and is acted on properly is one of the cheapest security wins available.

Related tools and reading: the Password Generator and the JSON Studio cover neighbouring checks that pair naturally with this one, and the guide at [/blog/security-check-before-handover-client-site/] extends the same topic with further background and examples for readers who want to go deeper.

Key points

  • No real credentials embedded in client-side code or bundles
  • Every scanner finding dispositioned as real or false positive
  • Real findings revoked and rotated before deletion
  • Server-side endpoints used for authenticated service calls
  • Repositories and build outputs scanned, including history

In short: Anything shipped to a browser is visible to its readers. The checker scans pasted code for common secret patterns. A found secret must be revoked, because deletion cannot un-share it. Do not paste real secrets into a tool you have not verified.

One habit ties all of this together: re-check after every change. A result from the Client-Side Secret Exposure Checker is only as current as the text it was run on, so a fix, a redesign, or a new version of the underlying material deserves a fresh run rather than reliance on an old report. Keep the previous result alongside the new one, note what changed between them, and treat any new finding as a question to investigate rather than a verdict to accept or dismiss. That loop of checking, fixing, re-checking and recording is what turns a one-off test into a reliable routine, and it costs far less time than debugging a problem that a second check would have caught early.