Storing API Keys in Environment Variables for React Agent Front-Ends
Anything compiled into a React bundle is public, so secrets need a backend.

Storing API keys in environment variables for React agent front-ends does not keep them secret. Client-side environment variables are compiled into public JavaScript. Secret credentials belong behind a backend layer, and agents need more than safe storage. Their access must be scoped, enforced outside the model, and recorded so that autonomous actions remain accountable.
Why environment variables in React do not hide secrets from the browser
A .env file kept out of version control solves a source-control hygiene problem. The distinction is mechanical. During a build, Webpack, Vite, or an equivalent bundler substitutes process.env.REACT_APP_API_KEY or import.meta.env.VITE_API_KEY with its literal string value. So variables with the REACT_APP_ or VITE_ prefix become part of the JavaScript sent to users. The .env file itself need never leave the build machine for its contents to reach a browser.
Create React App’s documentation makes the warning explicit. “Do not store any secrets (such as private API keys) in your React app!” It also explains that environment variables are embedded in the build, where anyone inspecting the app’s files can see them. Minification renames variables but leaves string values intact and visible in the shorter bundle.
Production source maps make the code easier to inspect because they show the original variable names and the code around them. Disabling them is a useful hygiene step, but the key remains in the bundle. The browser needs the value to use it.
What anyone with a browser can do with a key that lands in a bundle
A documented Vite misconfiguration exposed live AWS credentials and a CircleCI API key in client-side JavaScript. The AWS credentials opened S3 buckets containing the full production codebase. The CircleCI token gave control over the production CI/CD pipeline and its stored secrets, ultimately extending to GitHub repositories. The leak was not confined to the website. A browser-delivered credential became an entry point into the systems that built and operated it.
Extracting such a credential requires no specialized equipment. A visitor can open DevTools, search Sources for api_key, sk-, or Bearer, or inspect the Network tab as the application sends requests. Keys used in request headers or URL query parameters are visible there as someone uses the page. Someone can also download the JavaScript bundle and search it for familiar secret patterns. The browser is already equipped for the job.
The damage then depends on what the credential permits. A key without expiration, scope constraints, or environment isolation does not distinguish a legitimate lookup from an attempt to overwrite production data. Pay-per-use APIs can also cause harm because the account holder pays for use they did not authorize. If the credential also opens access to personal data, the exposure can implicate GDPR, CCPA, or SOC 2 access-control requirements.
Which keys are actually safe on the client side and which are not
Not every value called a key is a secret. Firebase API keys are made for use on the client, where Firebase security rules control access, while anonymous client keys for another backend platform rely on row-level security policies. Stripe publishable keys need server-side confirmation before they can start charges. Google Maps API keys can be used with a domain allowlist configured in the provider’s dashboard.
For public keys that are safe to share, domain restrictions and tight usage quotas help curb abuse and limit costs where those controls apply. React environment variables also remain useful for development, staging, and production API base URLs, feature flags, and analytics identifiers. Those values confer no authentication authority. The dividing line for a variable whose name contains “key” is what possession allows someone to do.
Stripe secret keys, AWS credentials, and service account credentials with write, delete, or administrative privileges belong on the other side of that line. They must not appear in client code. OpenAI and similar AI inference API keys from other vendors belong there too, even when the interface using them looks like an ordinary chat box.
Why AI agent front-ends make the env-var problem significantly worse
An agent interface can conceal a much broader authority surface than a simple data-fetching component. Its credentials may authorize tool invocation, data reads and writes, and downstream API calls on a user’s behalf. That changes the consequences of exposure. If an exposed key gives access to the agent’s full tool surface, an attacker can use those tools at machine speed and do more than retrieve a dataset.
Long-lived credentials make that authority difficult to contain when they lack scope constraints and per-environment isolation. The same credential may serve a read-only lookup and an attempt to overwrite production data. Early MCP deployments commonly inherited a pattern from server-side applications, placing a long-lived API key in a .env file or injecting it at startup. That pattern does not by itself provide the narrower, temporary authority an autonomous agent needs. Moving it into a browser bundle adds public exposure to an already broad grant of access.
MCP servers running under a user account can also have ambient access to SSH keys, local environment files, database credentials, and browser cookies unless that access is explicitly limited. If an exposed credential gives access to those resources, the harm can go beyond the intended tool call. Coding agents introduce a separate route to exposure because they commit credentials into repositories at higher rates than human developers. Repository leakage and bundle leakage can therefore compound within the same workflow.
The Backend-for-Frontend pattern: how to keep secrets off the client entirely

The architectural remedy is to keep secret-bearing requests on the server. The React application calls an endpoint the developer controls, that endpoint adds the credential to an upstream request, and only the processed response returns to the client. The Backend-for-Frontend pattern, or BFF, and the API proxy pattern apply this idea at different scales. Both place credential ownership outside the browser. The frontend asks for work rather than receiving the authority to perform it directly.
The request contrast is straightforward. In the unsafe version, a browser-side fetch sends an upstream request with an Authorization header containing the key compiled from process.env.REACT_APP_OPENAI_KEY. In the safe version, the React component calls fetch('/api/generate') on its own backend without that upstream credential. The code on the server adds the key at runtime. No file, header, or response traveling back to the browser may contain it.
Serverless functions take the least work to set up and run. Vercel and Netlify let teams deploy a function with the frontend and store its secret as a genuine server-side environment variable in the platform dashboard. A dedicated Express/Node proxy provides another path through an /api/* route, reading credentials from its server environment or a secrets manager. Its production cross-origin access configuration should be restricted to the production frontend domain. Teams with an existing backend can instead route agent calls through it, treating the AI or tool API as another downstream service.
A server-side environment variable can stay private because the server makes the authenticated request. A client-side variable cannot offer that boundary because its value must reach the browser.
Secrets management for agents that go beyond a single API key
A BFF solves browser exposure, but it does not manage the whole credential lifecycle. Agents may use several services, span environments, and continue working through extended sessions. Static secrets last a long time, are hard to rotate, and are a single point of failure. Server-side compromise can be just as damaging as client-side compromise, while being harder to detect.
For those deployments, the next step is encrypted secrets storage with per-customer or per-context segmentation. HashiCorp Vault and AWS Secrets Manager are established options for teams in those ecosystems, though per-customer segmentation typically needs additional architectural configuration beyond their defaults. The objective is a system that controls which context receives which credential, for which service, and for how long. Short-lived tokens, automatic rotation, and per-service access controls take the place of a shared static key and the ongoing access it gives.
Where an upstream service supports them, OAuth tokens are preferable for agent-to-service connections because they provide expiry, scope, and revocability. Credentials should also be scoped to the agent’s task or session. A research agent might get read access to one collection without the right to write or export. That is a bounded grant of authority. Where API keys cannot be replaced with short-lived tokens, an enforced expiration schedule and automatic rotation are necessary.
What agents must and must not be permitted to decide about their own access
Keeping a credential safe does not decide whether an action should be allowed. A credential establishes what the downstream service will accept, but an agent may need permission to use only a portion of that authority in a given workflow. So the rules that govern permissions must be enforced outside the agent. The model can propose an action without becoming the judge of whether it is allowed. Otherwise, the key’s maximum scope becomes the agent’s practical operating policy.
Fine-grained authorization at the proxy or gateway provides that separate decision point. A research agent may have read credentials but still be barred from bulk downloading or exporting data. A remediation agent may write to a given ticket type without getting identity-administration rights. Where elevated access is genuinely required, it should be temporary and expire automatically. These controls limit behavior independently of what the underlying credential technically permits.
Tool design helps make those boundaries enforceable. A function such as restartApprovedService(serviceId) keeps sensitive logic in deterministic code with bounded scope. By contrast, executeCommand(command) hands the model a generic execution primitive that policy cannot usefully constrain after the fact. The function signature is an authorization decision. A tool with a narrow scope makes it clearer to an external enforcement layer which action it needs to check.
The NSA’s May 2026 advisory flagged the lack of standardized authorization and credential isolation at the protocol level, leaving gateways to implement server-side authorization. The MCP 2026-07-28 specification added hardening through issuer validation aligned with RFC 9207 and method and tool names in HTTP headers. Those headers give gateways a cleaner way to route requests and enforce policy. They do not remove the implementation burden or provide a role-based access control model on the team’s behalf.
Audit logs, SOC 2, and what happens when an agent acts without a human directly authorizing each step
People are still accountable for privileged actions when agents act on their own. SOC 2’s access-control criteria expect those actions to be attributable to an accountable person. When an agent writes to a database, sends a communication, or calls an external API, the organization needs evidence connecting that action to its authorized purpose. A shared credential cannot supply that explanation alone. The session, user, and initiating workflow must remain visible.
For agentic systems, audit evidence includes clear definitions of permitted and prohibited actions, attributable action logs, and proof that permissions are limited to actual need. Prompt and response logs also need tamper-evident storage, including append-only object storage with object lock and hot and cold retention tiers. A log of an excessive permission grant does not make the grant appropriate. Even a sound access policy is hard to prove without records of how it worked.
When employees use agents without the security team’s knowledge, Shadow AI leaves gaps in policy, evidence, and how data is handled. The organizational response is to discover agents, monitor approved AI traffic, detect anomalous behavior and policy violations in real time, and enforce rules outside the agents themselves. For coding workflows, pre-receive hooks or required status checks can block merges when secrets are detected, so the infrastructure itself provides a barrier. Complete, attributable audit logs then connect those controls to the actions taken.
The proxy, secrets store, and authorization layer serve distinct parts of that accountability chain. Without them, an agent acting at machine speed can create gaps that neither the security team nor the auditor can close after the fact.


