Back to Blog
critical SEVERITY5 min read

How API Key Exposure in URL Query Parameters Happens in Node.js and How to Fix It

A critical security vulnerability was discovered in the `lib/crux.js` file where the CrUX API key was being transmitted as a URL query parameter instead of using secure HTTP headers. This exposed the API key in server logs, proxy logs, browser history, and network monitoring tools. The fix moves the API key to the `X-Goog-Api-Key` header, preventing credential leakage across logging systems.

O
By Orbis AppSec
Published September 2, 2026Reviewed September 2, 2026

Answer Summary

This vulnerability involves API key exposure through URL query parameters in a Node.js library (CWE-598). The `postJson` function in `lib/crux.js` was appending the CrUX API key directly to the URL as `?key=${apiKey}`, causing credentials to appear in server logs, proxy logs, and browser history. The fix moves the API key from the URL to the `X-Goog-Api-Key` HTTP header, which is the secure, recommended method for Google API authentication.

Vulnerability at a Glance

cweCWE-598
fixMove API key from URL query string to X-Goog-Api-Key header
riskAPI key theft through log file access, enabling unauthorized API usage
languageJavaScript/Node.js
root causeAPI key passed as URL query parameter instead of HTTP header
vulnerabilitySensitive Data Exposure via URL Query Parameter

Introduction

The lib/crux.js file handles communication with Google's Chrome User Experience Report (CrUX) API, fetching real-world performance data for web pages. However, a critical flaw in the postJson function at line 126 created a significant security risk: the API key was being transmitted as a URL query parameter, exposing it to anyone with access to logs or network traffic.

This vulnerability is particularly concerning because this is a Node.js library—meaning every downstream consumer who uses this package inherits this security flaw. The exposed API key could be extracted from server logs, proxy logs, or network captures, allowing attackers to make unauthorized API calls at the victim's expense.

The Vulnerability Explained

When making HTTP requests, there are two common ways to pass authentication credentials: in the URL as query parameters, or in HTTP headers. The vulnerable code chose the former approach:

const res = await fetch(`${endpoint}?key=${encodeURIComponent(apiKey)}`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(body),
});

While encodeURIComponent() properly handles special characters in the API key, it doesn't address the fundamental problem: the API key is now part of the URL itself.

Why This Is Dangerous

URLs are logged everywhere:

  1. Web server access logs: Most servers log every request URL by default
  2. Proxy and CDN logs: Corporate proxies, load balancers, and CDNs capture full URLs
  3. Browser history: If this code runs client-side, the URL appears in history
  4. Network monitoring tools: Security appliances and debugging tools capture URLs
  5. Referrer headers: The URL (including the key) may leak to third parties via the Referer header

Attack Scenario

Imagine a development team using this library in their performance monitoring pipeline. Their infrastructure includes:

  1. A corporate proxy that logs all outbound requests
  2. A SIEM system that aggregates these logs for security analysis
  3. A junior developer with read access to log files for debugging

The junior developer—or worse, an attacker who compromises their account—can simply grep through the logs:

grep "key=" /var/log/proxy/access.log | cut -d'=' -f2 | cut -d'&' -f1

Within seconds, they have valid CrUX API keys that can be used for unauthorized API calls, potentially exhausting quotas or incurring charges on the victim's Google Cloud account.

The Fix

The fix is elegantly simple: move the API key from the URL to an HTTP header. Here's the before and after:

Before (Vulnerable)

const res = await fetch(`${endpoint}?key=${encodeURIComponent(apiKey)}`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(body),
});

After (Secure)

const res = await fetch(endpoint, {
    method: "POST",
    headers: { "Content-Type": "application/json", "X-Goog-Api-Key": apiKey },
    body: JSON.stringify(body),
});

Why This Works

The X-Goog-Api-Key header is Google's recommended method for API authentication. By moving the key to a header:

  1. Server logs don't capture it: Standard access logs only record URLs, not request headers
  2. Proxy logs are safer: Most proxies don't log full headers by default
  3. Browser history is clean: The URL no longer contains sensitive data
  4. Referrer leaks are prevented: Headers aren't included in Referer

The change is minimal—just two lines modified—but the security improvement is substantial. The API key is now transmitted securely while maintaining full compatibility with Google's CrUX API.

Key Takeaways

  • The postJson function in lib/crux.js was leaking API keys through URL query parameters—a pattern that affects all downstream consumers of this Node.js library
  • encodeURIComponent() doesn't protect secrets—it only handles URL encoding, not confidentiality
  • Google APIs support header-based authentication via X-Goog-Api-Key—always prefer this over URL parameters
  • A two-line change eliminated the vulnerability—security fixes don't have to be complex
  • Library authors have amplified responsibility—vulnerabilities in shared code affect every project that depends on it

How Orbis AppSec Detected This

  • Source: The apiKey parameter passed to the postJson function in lib/crux.js
  • Sink: URL string concatenation at line 126: `${endpoint}?key=${encodeURIComponent(apiKey)}`
  • Missing control: No mechanism to prevent the API key from being included in the URL; header-based authentication was not implemented
  • CWE: CWE-598 (Use of GET Request Method With Sensitive Query Strings)
  • Fix: Moved the API key from the 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

This vulnerability demonstrates how a seemingly minor implementation choice—passing an API key in a URL versus a header—can create significant security exposure. The fix required changing just two lines of code, but the impact is substantial: API keys are no longer leaked across logging infrastructure, protecting both the library maintainers and every downstream user.

When working with APIs, always ask: "Where will this credential appear?" If the answer includes "URLs," "logs," or "browser history," it's time to refactor. Modern APIs universally support header-based authentication for exactly this reason.

Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #8

Related Articles

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

How Hardcoded API Key Exposure Happens in Node.js and How to Fix It

A critical vulnerability in `archive/open_claude_code/src/api/client.mjs` exposed five different API providers' credentials through direct `process.env` access. The fix introduced a centralized `readApiKey()` function to enforce secure credential retrieval across Anthropic, OpenAI, Google, and other integrations.

high

How Insufficiently Protected Credentials happens in Node.js and how to fix it

A regex-based guard in `skills/xmemo/scripts/xmemo-skill.mjs` was supposed to block sensitive credentials from being passed as CLI flags, but it only matched a handful of keyword patterns—missing `password`, `client-secret`, `access-token`, `xmemo-key`, and more. The fix expands the blocklist regex to cover these additional credential patterns, closing a gap that could let secrets end up in shell history and process listings.