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:
- Web server access logs: Most servers log every request URL by default
- Proxy and CDN logs: Corporate proxies, load balancers, and CDNs capture full URLs
- Browser history: If this code runs client-side, the URL appears in history
- Network monitoring tools: Security appliances and debugging tools capture URLs
- 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:
- A corporate proxy that logs all outbound requests
- A SIEM system that aggregates these logs for security analysis
- 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:
- Server logs don't capture it: Standard access logs only record URLs, not request headers
- Proxy logs are safer: Most proxies don't log full headers by default
- Browser history is clean: The URL no longer contains sensitive data
- 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
postJsonfunction inlib/crux.jswas 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
apiKeyparameter passed to thepostJsonfunction inlib/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-KeyHTTP 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.