Ethras Blog
RPC API Keys in Browser Apps: What Build Variables Cannot Hide
Learn why frontend environment variables cannot protect RPC credentials, where a server boundary helps, and how to prepare a practical key rotation process.
A wallet dashboard needs an RPC endpoint. A developer puts the endpoint in an environment file, excludes that file from Git, and ships the frontend. The repository contains no credential, but the browser still needs the endpoint to send requests.
That is the boundary worth examining: does the application deliver the credential to an environment you do not control? A build variable can keep a value out of source control. It cannot keep a value confidential after delivering it to the browser.
An RPC API key usually controls access to provider resources, quotas, or billing. It is different from a wallet private key that authorizes signatures. Keeping that distinction clear helps teams respond to the right incident without treating either credential casually.
What a frontend build variable actually does
Consider this Vite client code:
const rpcUrl = import.meta.env.VITE_RPC_URL;
If VITE_RPC_URL contains a credential and the application uses it, the resulting client must obtain that value. Vite's environment variable documentation explains that exposed variables become part of the client bundle and must not contain sensitive information.
Ignoring .env.local protects the repository copy. Minification changes presentation. Neither stops a person inspecting the browser's JavaScript or outgoing requests. The same design question applies if the frontend downloads its configuration after loading: configuration returned to the browser is still browser-visible.
Before choosing a storage mechanism, decide whether the credential is supposed to be confidential at all. A deliberately public, tightly restricted client credential has a different operating model from a confidential application credential.
Give the credential a clear owner
For an application-owned key that must remain confidential, keep it on a server you control. The browser calls your application, and that server performs the required upstream operation.
Some applications instead ask each developer to supply their own provider key. That can be a reasonable developer-tool design, but explain where the key is stored and transmitted. Avoid silently sending it to analytics or persisting it indefinitely. A user's key does not become safe merely because your application did not issue it.
Use a separate credential for each application and environment when the provider supports that arrangement. A staging incident then has a smaller recovery scope. This separation is an operational choice; check whether the provider pools usage across an account before assuming separate keys create separate budgets.
Make the server boundary useful
A proxy that accepts any upstream URL and forwards any body transfers the abuse problem to your server. Start with the small operation the application actually needs.
This illustrative browser call asks an application's own backend for a chain head. It is an example route, not an Ethras API:
async function loadChainHead() {
const response = await fetch('/api/chain-head', {
credentials: 'same-origin',
});
if (!response.ok) {
throw new Error('Chain head is temporarily unavailable');
}
const data = await response.json();
return data.blockNumber;
}
The server chooses the configured upstream, chain, and permitted RPC method. The caller does not choose an arbitrary destination. If the application needs a block range, validate that range and cap its size before making the upstream request.
Apply authentication and authorization where the operation requires them. Give public operations deliberate abuse controls too. Set request and response size limits, timeouts, concurrency bounds, and rate limits appropriate to the operation. These controls follow the general approach in OWASP's REST security guidance; they are requirements for your application boundary, not features supplied by the snippet above.
CORS controls which browser origins may read responses. It does not authenticate a caller or prevent a separate program from requesting a public endpoint. Test your server's controls without relying on frontend UI restrictions.
Look beyond the source repository
Inventory where complete upstream endpoints can appear: application logs, reverse-proxy access logs, error reports, tracing attributes, screenshots, and support messages. A credential in a URL path or query can be captured by systems that record URLs.
Log a non-secret credential identifier and a sanitized operation name instead of the complete endpoint. Scrub upstream errors before returning them to the browser. If an endpoint supports header authentication, that can reduce URL exposure, but a header still needs protection from logging and tracing.
Also review your referrer policy where page URLs contain sensitive data. MDN's referrer policy guide explains what the browser sends in Referer. That setting does not conceal the destination of an RPC request from the browser or remove values already written to logs.
Rehearse rotation before an incident
A scheduled rotation can follow a short runbook: create a replacement, update server configuration, verify normal traffic, then revoke the old credential and check that it is rejected under the provider's documented behavior. Record the owner and every consumer so an old worker is not forgotten.
Active abuse changes the priority. Revoke or disable the exposed credential promptly, even if restoring dependent clients requires a brief interruption. OWASP's secrets lifecycle guidance treats revocation as a separate step from rotation. Deploying a new frontend does not invalidate a copied key.
Before release, inspect the production build and a browser network session. Confirm that confidential credentials stay behind the server boundary, errors are sanitized, and an operator can replace and revoke a key. For the product's related guidance, see the RPC security topic. A short, exercised recovery process is more useful than a hidden-looking configuration file.
Share this article
Share on XReview your account and post on X. Opening X or copying text does not send a post.