
Editorial illustration; the code and review scene represent a security workflow, not a real repository.
You open your new website in a browser, click View Source, and find an API key sitting inside a JavaScript bundle. It may be surrounded by minified code and look obscure, but the browser has already received it. Anyone who can load the page can inspect it, copy it, and use it outside your application.
That is the uncomfortable part of frontend development: anything required by browser code should be treated as public. Obfuscation, a hidden HTML field, a different variable name, or a build-time environment variable does not change that basic rule.
Quick Answer
Never put a secret API key, private token, database password, signing key, or service credential in code that runs in the browser. The browser is an untrusted client. Put privileged calls behind a server-side endpoint, keep the real credential in server-side configuration, restrict the credential to the smallest useful permissions, and rotate it immediately if it has been exposed.
Some browser-facing keys are intentionally public. A map provider, payment service, or analytics platform may issue a publishable identifier designed to appear in client-side code. That is safe only when the provider documents it as public and you apply its restrictions, such as allowed origins, APIs, quotas, or limited scopes. A name such as PUBLIC_KEY is not proof that a credential is safe.
Why the browser changes the security boundary
Your server can protect an environment variable because users do not receive the server’s filesystem or process memory. A browser application is different. The user receives HTML, CSS, JavaScript, source maps, network responses, cookies and sometimes configuration embedded in the page. Developer tools can display them, and a user can replay a request with a different client.
This matters even when the site is small. A key may allow someone to consume a paid API quota, read private data, send messages, create resources, or call an internal service. A low-value key can still become a foothold for discovering how the rest of the system works.
The problem is not that users are inspecting your app. Inspection is how the web works. The problem is giving an untrusted client a credential with authority that belongs on a trusted server.
The common ways secrets leak into frontend code
The most obvious example is a literal:
const apiKey = "sk_live_example";
fetch("https://api.example.com/private", {
headers: { Authorization: `Bearer ${apiKey}` }
});
Build tools can create the same problem less visibly. A developer may put a value in .env, then use a frontend-prefixed variable such as VITE_, NEXT_PUBLIC_, or another framework’s public configuration prefix. The file is hidden from Git, but the value is deliberately inserted into the browser bundle at build time.
Other leaks come from debugging. A server may return a token in a JSON response, print credentials in a console message, expose a source map containing an old value, or include a complete upstream response in an error page. A secret can also remain in Git history after the current file is cleaned up.
A safer design: browser request, server decision
Move the privileged part of the operation to a server endpoint. The browser sends the minimum user input to your server. The server authenticates the user, validates the input, applies business rules, calls the upstream API with its private credential, and returns only the data the browser needs.
The flow looks like this:
- The browser sends a request to
/api/report. - The server checks the user session and validates the requested report ID.
- The server reads
REPORT_SERVICE_TOKENfrom its protected runtime configuration. - The server calls the upstream service and handles errors without returning the token.
- The server sends a small, filtered response to the browser.
This does not mean a server endpoint is automatically secure. It still needs authentication, authorization, input validation, rate limiting where appropriate, careful error handling, and protection against request forgery. But it keeps the upstream secret on the side of the system that can actually protect it.
For a static site, use a narrowly scoped serverless function, edge function, or small backend endpoint. Do not assume that a static frontend can safely call a private API just because the repository is private. The deployed JavaScript is still delivered to visitors.
How to check a project before shipping
1. Search the source and build output
Search for suspicious names and patterns, not just the word password. Look for API_KEY, SECRET, TOKEN, Authorization, private-key headers, cloud access identifiers, and URLs for internal services. Inspect both source files and the final generated assets because build steps may expand configuration.
Do not paste a real secret into a third-party scanner while investigating. Run a trusted local secret scanner or a repository hook, and review its findings rather than blindly ignoring every match. Test fixtures and documentation examples can create false positives, but an ignored finding should have a written reason.
2. Inspect the browser’s network panel
Load the production-like build in a clean browser session. Watch requests made during page load and common user actions. Check query strings, request headers, response bodies, console logs, error messages and source maps. If a credential appears anywhere the browser can read, treat it as public.
3. Trace each credential to its required authority
For every key, write down the service, allowed operation, environment, expiry or rotation process, and owner. Ask whether the browser needs the credential or only needs the result of a server-side operation. This simple inventory often reveals keys that were copied into a project for a test and never removed.
4. Reduce permissions and scope
Use separate credentials for development, staging and production. Give each one only the API operations and resources it needs. Add origin or referrer restrictions only as an additional control; they are not a replacement for keeping private credentials out of the browser. Set quotas and alerts so unusual use becomes visible quickly.
5. Remove old values from history
Deleting a key from the current branch does not make an exposed key safe. If the repository, build artifact, log, screenshot or support ticket contained the real value, rotate or revoke it at the provider. Then remove the historical copy using the provider’s documented cleanup process and explain the incident to anyone who may have access.
What to do if a key is already exposed
Act in this order:
- Revoke or rotate the credential at the provider. Assume it has been copied even if you cannot see abuse.
- Check usage logs, billing, resource creation and recent requests for unexpected activity.
- Identify every place the key was used, including CI variables, deployment settings, logs, screenshots and cached bundles.
- Replace the frontend call with a server-side path or a documented public credential.
- Add a repository scan and a pre-release check so the same class of leak is caught earlier.
- Record what happened, which systems were affected, and when the old credential stopped working.
Do not wait for a perfect incident report before revoking a credential that is clearly private. Investigation and containment can happen together.
Mistakes to avoid
The most common mistake is treating minification as encryption. It is not. Another is relying only on a .gitignore file; that prevents some future commits but does nothing for a value already in a bundle or history. A third is placing a secret in an HTML comment or a hidden form field. Hidden from the page layout is not hidden from the user.
Also avoid returning the upstream response wholesale. Even when the credential stays server-side, the response might reveal internal IDs, private fields or debugging details. Define a small response shape and log identifiers rather than sensitive payloads.
My Take
The best secret-management rule for a frontend team is easy to remember: if the browser can use it, assume the user can use it too. That rule is more reliable than memorising every framework’s environment-variable convention.
A short review before release is usually enough to catch the largest mistakes: inspect the bundle, inspect network requests, list the authority of every credential, and test the server-side alternative. Security becomes much less dramatic when the application’s trust boundary is explicit.
Bottom Line
A browser is a public execution environment, not a safe place for private credentials. Move privileged API calls to a server, use documented public keys only when they are genuinely designed for browser use, restrict every credential, and rotate anything that has escaped. The goal is not to hide a secret better; it is to stop making the browser responsible for keeping it secret.