How Insecure Credential Storage Happens in Node.js and How to Fix It
Introduction
In the Google Vision translator module (src/module/translator/google-vision.js), we discovered a critical severity credential storage vulnerability that exposed API keys in plaintext configuration files. The textDetection() function was constructing Google Vision API URLs by directly concatenating the API key as a query parameter:
const apiUrl = 'https://vision.googleapis.com/v1/images:annotate?key=' + apiKey;
This pattern created a dangerous exposure vector: any attacker with read access to the application's user data directory could extract the API key from the configuration files stored at paths like fileModule.getUserDataPath('config'). In a desktop application context, this is particularly concerning because local file access is a realistic attack vector—whether through malware, privilege escalation, or physical access.
The vulnerability matters because API keys are cryptographic credentials that grant full access to your cloud services. Exposed Google Vision API keys can be used to:
- Execute unlimited image analysis requests (incurring massive costs)
- Access sensitive images your application has processed
- Perform reconnaissance on your application's capabilities
- Potentially pivot to other Google Cloud resources
The Vulnerability Explained
The Problematic Code Pattern
The vulnerable code in google-vision.js:16-17 embedded the API key directly into the URL:
const apiUrl = 'https://vision.googleapis.com/v1/images:annotate?key=' + apiKey;
const header = { 'Content-Type': 'application/json' };
This creates multiple security issues:
- Persistent Plaintext Storage: The configuration file containing
googleVisionApiKeyis stored atfileModule.getUserDataPath('config'), typically in locations like:
- Linux:~/.config/appname/config
- macOS:~/Library/Application Support/appname/config
- Windows:%APPDATA%\appname\config
These files often have insufficient permissions (e.g., 0644 instead of 0600), making them readable by other local users or processes.
-
URL-Based Credential Exposure: Embedding credentials in URLs means they appear in:
- Browser history (if logged)
- HTTP proxy logs
- Server access logs
- Application logs or crash dumps
- Process memory inspection tools -
Configuration File Attack Surface: The config file is accessed at runtime and loaded into memory. An attacker with local access can:
- Read the file directly:cat ~/.config/appname/config | grep apiKey
- Use filesystem tools to inspect file contents
- Monitor file access patterns to identify when credentials are loaded
Attack Scenario
An attacker with local access to the user's machine could:
- Navigate to the application's config directory
- Extract the
googleVisionApiKeyvalue from the plaintext configuration - Use this key to make unauthorized Google Vision API calls
- Incur thousands of dollars in API charges on the victim's Google Cloud billing account
- Process sensitive images the legitimate user had analyzed through the application
The Fix
The security fix relocates the API key from the URL query parameter to a secure HTTP header, following Google's recommended authentication approach:
Before (Vulnerable):
const apiUrl = 'https://vision.googleapis.com/v1/images:annotate?key=' + apiKey;
const header = { 'Content-Type': 'application/json' };
After (Secure):
const apiUrl = 'https://vision.googleapis.com/v1/images:annotate';
const header = { 'Content-Type': 'application/json', 'X-Goog-Api-Key': apiKey };
Why This Fix Works
-
HTTP Headers Are Ephemeral: Unlike URL query parameters, HTTP headers are:
- Not persisted to disk by default
- Not logged in most server access logs (when configured properly)
- Not visible in browser history
- Transmitted only in the request, not stored in the URL -
Follows Google's Best Practices: The
X-Goog-Api-Keyheader is Google's recommended method for API key authentication. The Google Cloud documentation explicitly advises against URL-based API keys for this exact reason. -
Maintains Functionality: The fix doesn't change the API endpoint or request payload—only how authentication is provided. The Google Vision API accepts API keys via the
X-Goog-Api-Keyheader with identical functionality to the query parameter approach. -
Reduces Configuration Exposure: While the API key is still loaded from the config file into memory, it's no longer persisted in multiple locations (URL strings, logs, history).
Prevention & Best Practices
To avoid credential storage vulnerabilities in your Node.js applications:
1. Never Embed Credentials in URLs
- Use HTTP headers for authentication (Bearer tokens, API keys)
- Query parameters are logged, cached, and visible in URLs
-
``javascript // ❌ Bad const url =https://api.example.com/data?apiKey=${key}`;// ✅ Good
const headers = { 'Authorization':Bearer ${token}};
```
2. Encrypt Credentials at Rest
- Use encryption libraries like
cryptoortweetnaclto encrypt sensitive config values - Store encryption keys separately from encrypted data
javascript const crypto = require('crypto'); const cipher = crypto.createCipher('aes-256-cbc', encryptionKey); const encrypted = cipher.update(apiKey, 'utf8', 'hex') + cipher.final('hex');
3. Restrict File Permissions
- Set config files to
0600(read/write for owner only): javascript const fs = require('fs'); fs.chmodSync(configPath, 0o600);
4. Use Environment Variables with Caution
- Environment variables are better than plaintext files but still visible to processes on the same machine
- Combine with proper access controls and secrets management systems
javascript const apiKey = process.env.GOOGLE_VISION_API_KEY; if (!apiKey) throw new Error('Missing GOOGLE_VISION_API_KEY');
5. Implement Secrets Management
- For production applications, use dedicated secrets management:
- HashiCorp Vault
- AWS Secrets Manager
- Azure Key Vault
- Google Cloud Secret Manager
- These systems provide encryption, rotation, audit logging, and access controls
6. Audit Logging and Monitoring
- Log when credentials are accessed (without logging the credentials themselves)
- Monitor for unusual API key usage patterns
- Set up alerts for failed authentication attempts
7. Use Static Analysis Tools
- Tools like Semgrep, TruffleHog, and git-secrets can detect:
- Hardcoded credentials in code
- API keys in configuration files
- Credential patterns in URLs
- Integrate into your CI/CD pipeline to prevent credential commits
Key Takeaways
-
API keys in URLs are a critical vulnerability: The
X-Goog-Api-KeyHTTP header is the secure alternative for Google services and prevents credentials from being logged, cached, or persisted. -
Configuration files need encryption and strict permissions: Storing plaintext API keys in config files is exploitable by attackers with local filesystem access. Use
fs.chmodSync(path, 0o600)and encrypt sensitive values. -
This vulnerability affected the specific
textDetection()function: The fix ingoogle-vision.js:16-17removes the API key from the URL query string and moves it to theX-Goog-Api-Keyheader, eliminating the exposure vector. -
Desktop applications have unique threat models: Local file access is a realistic attack vector. Assume attackers can read user data directories and protect accordingly.
-
HTTP headers are more secure than URL parameters: Headers are ephemeral, not logged by default, and follow security best practices for credential transmission.
How Orbis AppSec Detected This
Source: API key loaded from plaintext configuration file at fileModule.getUserDataPath('config')
Sink: API key concatenated into URL string at src/module/translator/google-vision.js:16 in the textDetection() function
Missing control: No encryption of credentials at rest, no secure HTTP header usage, insufficient file permissions on configuration files
CWE: CWE-798: Use of Hard-Coded Credentials, CWE-312: Cleartext Storage of Sensitive Information
Fix: Relocated API key from URL query parameter to the X-Goog-Api-Key HTTP header, following Google's recommended authentication method and preventing credential persistence in URLs and logs.
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
Credential storage vulnerabilities are among the most exploitable security flaws because they grant attackers direct access to your cloud services and data. The fix applied to google-vision.js demonstrates a critical principle: credentials should be transmitted securely (via HTTP headers) and never embedded in URLs or stored in plaintext.
By moving the API key from the URL to the X-Goog-Api-Key header, this application now follows Google's security recommendations and eliminates a major attack surface. As you review your own applications, audit how credentials are stored and transmitted—particularly in configuration files, URLs, and logs. Implement proper encryption, file permissions, and secrets management to protect your cloud infrastructure from unauthorized access.
References
- CWE-798: Use of Hard-Coded Credentials
- CWE-312: Cleartext Storage of Sensitive Information
- Google Cloud: API Key Best Practices
- Google Cloud: Using API Keys
- OWASP: Credential Storage Cheat Sheet
- Node.js Crypto Module Documentation
- Semgrep Rule: Hardcoded Credentials
- GitHub PR: fix: fix security issue in google-vision.js