Introduction
In a Node.js library used by downstream consumers, we discovered a critical hardcoded API key vulnerability in src/utils.js at line 65. The ipregistry.co API key f8n4kqe8pv4kii was embedded directly in the source code as a fallback value using the pattern apiKey = apiKey || "f8n4kqe8pv4kii". Because this library ships as client-side JavaScript, anyone who opened their browser's DevTools could extract this credential in seconds—no decompilation or reverse engineering required.
This matters because the library is a package consumed by other applications. Every downstream project that bundles this code inadvertently ships a third-party API key to their end users, creating a supply chain credential leak that scales with adoption.
The Vulnerability Explained
What's happening in the code
The utils object in src/utils.js contains an IP geolocation lookup function. Here's the vulnerable code:
// src/utils.js - BEFORE fix (line 62-68)
const utils = {
// ... other methods
lookupIp(ip, apiKey) {
if (cache[ip]) {
return cache[ip];
}
apiKey = apiKey || "f8n4kqe8pv4kii"; // ← HARDCODED KEY
return fetch(`https://api.ipregistry.co/${ip}?key=${apiKey}`)
.then((resp) => resp.json())
.then((result) => {
// ...
});
}
};
The line apiKey = apiKey || "f8n4kqe8pv4kii" acts as a "convenience" fallback: if no API key is passed to the function, it defaults to a hardcoded one. This is a classic anti-pattern in library development—providing a "just works" experience at the cost of security.
How an attacker exploits this
The exploitation is trivial:
- Open any web application that uses this library
- Open browser DevTools (F12 or Ctrl+Shift+I)
- Navigate to Sources tab and locate
src/utils.jsin the bundle (or search for "ipregistry") - Copy the API key
f8n4kqe8pv4kii
With the extracted key, an attacker can:
- Abuse the ipregistry.co service under the key owner's account, consuming their API quota or racking up charges on a paid plan
- Perform mass IP geolocation lookups for reconnaissance, spam campaigns, or scraping operations—all billed to the legitimate key owner
- Trigger rate limits or account suspension for the key owner, causing denial of service for all legitimate users of the library
Why this is critical
This isn't just a theoretical risk. The key is:
- In production code (not test-only)
- In a client-side JavaScript bundle (fully visible to end users)
- In a library (multiplied across every downstream consumer)
- Used in a network request (the key travels in the URL query string, potentially logged by proxies)
The Fix
The fix is a single-line removal that eliminates the hardcoded fallback:
Before (vulnerable)
apiKey = apiKey || "f8n4kqe8pv4kii";
return fetch(`https://api.ipregistry.co/${ip}?key=${apiKey}`)
After (fixed)
return fetch(`https://api.ipregistry.co/${ip}?key=${apiKey}`)
Why this works
By removing line 65 entirely, the function no longer provides a fallback credential. If a caller doesn't pass an apiKey parameter, the value will be undefined, and the API call will fail with an authentication error from ipregistry.co. This is the correct behavior—it forces consumers to:
- Provide their own API key explicitly
- Load that key from a secure source (environment variable, server-side config, or secret manager)
- Take responsibility for their own credential management
The fix preserves the function's interface—apiKey is still a parameter—but removes the dangerous default. Existing tests pass because they either supply a key or mock the fetch call.
What about the exposed key?
Removing the key from source code is necessary but not sufficient. The key f8n4kqe8pv4kii should also be:
- Rotated on ipregistry.co's dashboard (the old key is compromised)
- Scrubbed from git history if the repository is public (using git filter-branch or BFG Repo-Cleaner)
- Monitored for unauthorized usage in ipregistry.co's analytics
Prevention & Best Practices
1. Never embed secrets in client-side code
Client-side JavaScript is inherently public. There is no way to hide a secret in a browser-delivered bundle. If you need to call a third-party API:
- Use a server-side proxy: Your backend calls the API with the key, and your frontend calls your backend
- Use environment variables:
process.env.IPREGISTRY_API_KEYfor server-side Node.js - Use build-time injection carefully: Tools like webpack's
DefinePlugincan inject values, but they still end up in the bundle
2. Use secret scanning in CI/CD
Add pre-commit hooks and CI checks to catch secrets before they reach the repository:
# Example: GitHub Actions with gitleaks
- name: Scan for secrets
uses: gitleaks/gitleaks-action@v2
3. Library design patterns
For library authors who need API keys:
// GOOD: Require explicit configuration
function createClient(config) {
if (!config.apiKey) {
throw new Error('API key is required. Get one at https://ipregistry.co');
}
// ...
}
// BAD: Hardcoded fallback
function createClient(config) {
const apiKey = config.apiKey || "hardcoded-key-here";
// ...
}
4. Reference standards
- CWE-798: Use of Hard-coded Credentials
- OWASP: Cryptographic Failures (A02:2021)
- OWASP Secret Management Cheat Sheet: Guidance on secure credential storage and injection
Key Takeaways
- The
apiKey = apiKey || "f8n4kqe8pv4kii"pattern insrc/utils.jsturned a convenience feature into a credential leak—fallback values for secrets are never acceptable in client-side code. - Library authors bear extra responsibility: a hardcoded key in a library propagates to every downstream consumer's production bundle.
- The ipregistry.co key
f8n4kqe8pv4kiiwas extractable by any user with browser DevTools—no sophisticated tooling or reverse engineering needed. - Removing the fallback is the correct fix: failing loudly when no key is provided forces proper credential management by consumers.
- The exposed key must be rotated immediately—removing it from code doesn't invalidate it on the service provider's side.
How Orbis AppSec Detected This
- Source: The
apiKeyparameter insrc/utils.jswith a hardcoded string literal fallback"f8n4kqe8pv4kii"at line 65 - Sink: The
fetch()call tohttps://api.ipregistry.co/${ip}?key=${apiKey}which transmits the credential over the network - Missing control: No mechanism to load the API key from a secure external source (environment variable, secret manager, or server-side proxy); instead, a plaintext credential was embedded directly in distributable source code
- CWE: CWE-798 — Use of Hard-coded Credentials
- Fix: Removed the hardcoded fallback assignment
apiKey = apiKey || "f8n4kqe8pv4kii"from line 65, requiring callers to explicitly provide their own API key
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
Hardcoded credentials remain one of the most common and most preventable security issues in modern software. In this case, a single line of "convenience" code—apiKey = apiKey || "f8n4kqe8pv4kii"—turned a Node.js library into a credential distribution mechanism, shipping an ipregistry.co API key to every end user of every application that consumed it.
The fix was simple: remove the fallback and let the function fail explicitly when no key is provided. But the lesson runs deeper. Library authors must design their APIs to require secure credential injection from the start, and every development team should have automated secret scanning in their CI pipeline to catch these issues before they reach production.
If you're working on a library or application that calls third-party APIs, audit your codebase today for fallback credential patterns. Your future self—and your API bill—will thank you.