Back to Blog
critical SEVERITY4 min read

Google Generative AI Keys Exposed in Client-Side Fetch URLs

A client-side AI model discovery utility was embedding Google Generative AI API keys directly in fetch request URLs, making them visible to any user inspecting network traffic or browser DevTools. The fix moves the key from the URL query parameter to a secure HTTP header, eliminating exposure while maintaining API authentication.

O
By Orbis AppSec
•Published September 25, 2026•Reviewed September 25, 2026

Answer Summary

The affected code in `discoverAvailableModels()` and `initializeModel()` functions passes the Google Generative AI API key as a URL query parameter to the Google API endpoint. An attacker (or any user) inspecting network traffic or DevTools can trivially extract the API key and use it for unauthorized API calls, quota exhaustion, or billing fraud. The fix relocates the API key from the URL query string into the `x-goog-api-key` HTTP header. This is a CWE-798 hardcoded credentials vulnerability.

Vulnerability at a Glance

cweCWE-798 (Use of Hard-Coded, Security-Relevant Constants)
fixMove API key from query string to x-goog-api-key HTTP header
riskAny user can extract the API key from network traffic or browser tools and abuse it
languageJavaScript
root causeAPI key passed as URL query parameter instead of secure HTTP header
vulnerabilityHardcoded API key exposure in client-side code

Client-Side API Keys Are Credentials, Even in Headers

A critical flaw in AI model discovery code was handing Google Generative AI API keys directly to end users. The discoverAvailableModels() function accepted an API key parameter and embedded it into a fetch URL as a query string:

const listResponse = await fetch(
  `https://generativelanguage.googleapis.com/v1/models?key=${apiKey}`
);

In client-side code, this pattern is catastrophic. Every user who opens browser DevTools, inspects network traffic, or reviews the bundled JavaScript can see the key. Search engine crawlers may index it. Logs may record it. The key becomes as exposed as a hardcoded password in a public repository.

For Google Generative AI endpoints, the fix is to use the x-goog-api-key HTTP header instead:

const listResponse = await fetch(
  'https://generativelanguage.googleapis.com/v1/models',
  { headers: { 'x-goog-api-key': apiKey } }
);

This is not a cure—it is a necessary harm reduction. Even in a header, a client-side key is still client-side. But headers are not part of the URL, not indexed by search engines, and not visible in browser history. They also allow backend-driven revocation and origin-specific CORS policies, which query parameters do not.

Why Query Parameters Are Worse Than Headers

When an API key lives in the URL:

  • Search engine indexing: Crawlers visiting your application or intercepted requests may cache the full URL with the key.
  • Referrer headers: A link from your site to an external domain will send the referrer header, leaking the key to that domain.
  • Browser history: The full URL is stored in local browser history and sync services.
  • Proxy logs: Any proxy, CDN, or corporate firewall logs the full request URI.
  • Static bundle analysis: The key is visible in minified JavaScript without even running the code.

HTTP headers are request metadata—not part of the URL. They are not logged by default, not sent in referrers, and not visible in browser history. Search engines ignore headers for indexing.

This does not make client-side keys safe. It makes them marginally less universally exposed.

The Attack Surface

An attacker (or a curious user) who extracts the Google API key can:

  1. Make unlimited requests to your quota, exhausting it and breaking your application.
  2. Generate content using your billing account, incurring charges.
  3. Map the key to your organization and perform reconnaissance on your infrastructure.
  4. Revoke your own key by capturing it and then replacing it in their own client, locking you out of your own API.

For a production application, this is not theoretical—it is inevitable. API keys in client-side code will be harvested within days.

The Fix in Context

The pull request changed two things:

  1. Removed the query parameter interpolation: ?key=${apiKey} becomes a plain endpoint URL.
  2. Passed the key via the headers option in the fetch configuration: { headers: { 'x-goog-api-key': apiKey } }.

The Google Generative AI API accepts both forms, but the header form is the intended client-side pattern. It keeps the key out of the URL and makes credential rotation and origin-locking feasible from the backend.

// Before (vulnerable)
const listResponse = await fetch(
  `https://generativelanguage.googleapis.com/v1/models?key=${apiKey}`
);

// After (mitigated)
const listResponse = await fetch(
  'https://generativelanguage.googleapis.com/v1/models',
  { headers: { 'x-goog-api-key': apiKey } }
);

This change applies to both discoverAvailableModels() and initializeModel() functions, which share the same authentication pattern.

The Larger Problem: Client-Side Keys Should Not Exist

This fix is tactical. The strategic problem is that API keys should not be in client-side code at all.

In a production architecture:

  • Frontend calls your own backend (or a same-origin proxy).
  • Backend holds the Google API key and makes requests to Google on behalf of the frontend.
  • Frontend never sees the key.

This pattern is called a "backend-for-frontend" (BFF) or API proxy. It allows you to:

  • Rotate credentials without redeploying client code.
  • Apply rate limits, logging, and access control at the backend.
  • Monitor your own quota usage and costs.
  • Revoke a leaked key without breaking the frontend.

The header-based fix allows you to move toward this architecture incrementally. If you must embed a key in the frontend (for prototyping, open-source projects, or client-side-only applications), use headers and restrict the key's permissions in the Google Cloud Console to the bare minimum—typically read-only access and a specific API method.

How Orbis AppSec Detected This

Source: The apiKey parameter passed to discoverAvailableModels() and initializeModel() functions.

Sink: The fetch() call that constructs the URL with template string interpolation: `https://generativelanguage.googleapis.com/v1/models?key=${apiKey}`.

Missing control: No validation that the key is not embedded in a URL. The functions accepted the key and immediately put it into the most exposed place possible—the query string.

CWE: CWE-798 (Use of Hard-Coded, Security-Relevant Constants).

Fix: Move the API key from URL query parameter to the x-goog-api-key HTTP header.

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

API keys in client-side code are a permanent leak. Headers reduce the attack surface compared to query parameters by keeping credentials out of URLs, logs, and history—but they do not eliminate the exposure. The real fix is to never embed credentials in client-side code at all. Use a backend proxy, restrict the key's permissions in your API provider's console, and treat any client-side key as compromised from the moment it ships.

Prevention and further reading

Frequently Asked Questions

Can I use any HTTP header to pass the Google API key, or must it be specifically `x-goog-api-key`?

Google's API requires specifically the `x-goog-api-key` header for client-side authentication. Using non-standard headers will cause the API to reject the request with an authentication error.

If I move the key to a header, will it still be visible in browser DevTools?

Yes, headers are visible in DevTools' Network tab—but they are not indexed by search engines, not logged in browser history, and not exposed in JavaScript bundle analysis. Headers also allow you to rotate credentials server-side and control which origins can use them via CORS, which URL parameters cannot.

Does this fix allow me to safely deploy the key to production client-side code?

No. Even with headers, any API key embedded in client-side code is accessible to end users. The fix mitigates casual exposure, but for production you should proxy API calls through your own backend and never embed credentials in client-side code.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #22

Related Articles

critical

Yandex Translate API Key Leaked via URL Query Parameter

The `translateYandex()` helper built its request URL by interpolating the caller-supplied API key directly into the query string, meaning every call leaked the credential into server access logs, proxy logs, and any Referer header sent by intermediaries. The fix switches the request from a GET with the key in the URL to a POST with the key in the request body via `URLSearchParams`, removing the credential from any URL-logging surface entirely.

critical

How credential leakage through console logging happens in JavaScript browser extensions and how to fix it

A browser extension's `src/background/credentials.js` printed the full Strava authentication cookie string — including a signed JWT and CloudFront-Signature values — straight into the extension console via `console.debug`. Anyone who could open DevTools on the background page (or any tooling that scraped the console) could copy a live session and impersonate the user. The fix replaces the credential payload in both log statements with `Boolean(credentials)` and strips a realistic-looking JWT out

critical

How Hardcoded Secrets Compromise Authentication in JavaScript and How to Fix It

A critical vulnerability in `Tool/QuantumultX/Rewrite/RRSP.js` exposed hardcoded API authentication credentials—a TOKEN and UMID device identifier—directly in source code. Anyone with repository access could extract these credentials to impersonate the legitimate user and gain full account access to the RRTV API service. The fix replaced hardcoded secrets with empty placeholders, forcing users to manually configure credentials through secure channels.

critical

How API Key Exposure in Request Bodies happens in React and how to fix it

The Chatbot component in gitforme was transmitting Azure OpenAI API keys inside JSON request bodies, causing them to be logged by servers, proxies, and middleware. By moving the apiKey from requestBody.apiKey to an Authorization header, credentials are now protected from persistence in generic request logging infrastructure.

critical

How Hardcoded API Keys in WASM Modules Happen in KAP and How to Fix Them

A critical security vulnerability in `wasm/kap/standard-lib/fhelp-impl.kap` exposed hardcoded Gemini API keys directly in source code distributed to end users via WASM modules. The fix replaces the embedded credential with secure environment variable retrieval, preventing credential extraction through browser developer tools or binary inspection.

critical

chrome.runtime.onMessage: Missing Sender Validation in Extension

A critical authentication flaw in a Chrome extension's message handling allowed arbitrary extensions and malicious web pages to trigger sensitive operations including script injection and tab manipulation. The vulnerability existed because chrome.runtime.onMessage listeners processed requests without verifying sender identity or origin, trusting any message source by default.