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.

Prevention & Best Practices

1. Never Put Secrets in URLs

This is a fundamental rule of secure API design. URLs should be considered public information. Always use:
- Authorization headers for bearer tokens
- Custom headers like X-Api-Key or X-Goog-Api-Key for API keys
- Request bodies for sensitive data (with HTTPS)

2. Use Environment Variables

Store API keys in environment variables, not in code:

const apiKey = process.env.CRUX_API_KEY;
if (!apiKey) {
    throw new Error('CRUX_API_KEY environment variable is required');
}

3. Implement Secret Scanning

Use tools like:
- git-secrets: Prevents committing secrets to repositories
- truffleHog: Scans git history for high-entropy strings
- GitHub Secret Scanning: Automatically detects exposed credentials

4. Review Third-Party Libraries

Before adopting a library, check how it handles authentication. Look for patterns like:
- ?key= or ?api_key= in URL construction
- ?token= or ?secret= patterns
- Any sensitive data concatenated into URLs

5. Log Sanitization

Configure your logging infrastructure to redact sensitive patterns:

// Example: Sanitize URLs before logging
function sanitizeUrl(url) {
    return url.replace(/[?&](key|token|secret|password)=[^&]*/gi, '$1=REDACTED');
}

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.

References

Frequently Asked Questions

What is API key exposure via URL query parameters?

It occurs when sensitive credentials like API keys are included in URLs, causing them to be logged in server logs, proxy logs, browser history, and network monitoring tools where attackers can extract them.

How do you prevent API key exposure in Node.js?

Always transmit API keys in HTTP headers (like Authorization or X-Goog-Api-Key) rather than URL query parameters, and use environment variables to store keys securely.

What CWE is API key exposure via URL?

CWE-598: Use of GET Request Method With Sensitive Query Strings covers this vulnerability class where sensitive data is exposed through URL parameters.

Is URL encoding enough to prevent API key exposure?

No, URL encoding (like encodeURIComponent) only handles special characters—it doesn't prevent the key from being logged in plaintext across various systems.

Can static analysis detect API key exposure in URLs?

Yes, static analysis tools can detect patterns where sensitive variables are concatenated into URL strings, especially when variable names contain keywords like "key", "token", or "secret".

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #8

Related Articles

critical

How API Key Exposure and Unsafe Process Spawning Happens in Node.js Scripts and How to Fix It

A critical security vulnerability in the `scripts/close-issues.mjs` file exposed API key patterns in documentation and used unsafe `spawnSync` calls to execute curl commands. The fix replaces dangerous process spawning with native `fetch()` API calls and removes sensitive configuration examples from documentation, eliminating both credential exposure and command injection risks.

critical

How hardcoded API credentials in client-side JavaScript happens in userscripts and how to fix it

The jhs-enhance.user.js userscript contained a hardcoded Imgur API Client-ID embedded directly in client-side JavaScript code, exposing it to anyone who installed or viewed the script source. This critical vulnerability allowed unauthorized users to extract and abuse the API credentials for unlimited image uploads. The fix replaced the hardcoded credential with a user-prompt mechanism that requires each user to provide their own Imgur Client-ID.

critical

How Hardcoded HMAC-SHA256 Keys Compromise API Authentication in HarmonyOS and How to Fix It

A critical vulnerability in the Bika application exposed a hardcoded HMAC-SHA256 signing key directly in the Constants.ets file, allowing attackers to forge valid API requests. The fix implements runtime key deobfuscation using XOR masking, removing the plaintext credential from both source code and compiled binaries. This change demonstrates why symmetric keys must never be embedded in client-side code.

critical

How API Key Exposure in URL Parameters happens in Python and how to fix it

The Wine Cellar Home Assistant integration exposed Gemini API keys by transmitting them as URL query parameters in HTTP requests. This critical vulnerability allowed API keys to be logged in server logs, proxy caches, and browser history. The fix moved authentication to the secure `x-goog-api-key` HTTP header, preventing credential leakage.

critical

How Hardcoded API Keys Happen in TOML Configuration Files and How to Fix Them

A hardcoded Google Maps API key was discovered in `exampleSite/config/_default/params.toml` at line 113, exposing a live credential that any attacker could extract from the repository and use to make unauthorized API calls. This critical vulnerability was automatically detected and fixed by replacing the hardcoded key with an empty placeholder, eliminating the risk of credential theft and unauthorized usage charges.

critical

How Server-Side Request Forgery happens in Python FastAPI and how to fix it

A critical Server-Side Request Forgery (SSRF) vulnerability was discovered in app.py where the `/parse` and `/parse-video` endpoints accepted user-supplied URLs with only substring validation. The application checked if 'doubao.com' appeared anywhere in the URL string, allowing attackers to bypass this check and access internal services, cloud metadata endpoints, or scan the internal network. The fix implemented proper hostname parsing with an allowlist of legitimate domains.