Back to Blog
high SEVERITY7 min read

How Insecure Credential Storage Happens in Node.js and How to Fix It

A critical vulnerability in the Google Vision translator module stored API keys in plaintext configuration files accessible to attackers with local filesystem access. The fix relocates the API key from the URL query parameter to a secure HTTP header, eliminating the exposure vector while maintaining full functionality.

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

Answer Summary

This vulnerability involved hardcoded credential storage in plaintext configuration files (CWE-798) within a Node.js desktop application. An attacker with local filesystem access could extract the Google Vision API key from `config` files stored in the user data directory. The fix removes the API key from the URL query string and instead passes it as an `X-Goog-Api-Key` HTTP header, which is not persisted to disk and follows Google's recommended security practices.

Vulnerability at a Glance

cweCWE-798 (Use of Hard-Coded Credentials), CWE-312 (Cleartext Storage of Sensitive Information)
fixMove API key from URL query parameter to HTTP `X-Goog-Api-Key` header
riskAttackers with local filesystem access can extract API credentials and abuse Google Vision services
languageJavaScript (Node.js)
root causeAPI keys embedded in configuration file URLs instead of using secure header-based authentication
vulnerabilityInsecure Credential Storage / Plaintext API Key Exposure

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:

  1. Persistent Plaintext Storage: The configuration file containing googleVisionApiKey is stored at fileModule.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.

  1. 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

  2. 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:

  1. Navigate to the application's config directory
  2. Extract the googleVisionApiKey value from the plaintext configuration
  3. Use this key to make unauthorized Google Vision API calls
  4. Incur thousands of dollars in API charges on the victim's Google Cloud billing account
  5. 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

  1. 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

  2. Follows Google's Best Practices: The X-Goog-Api-Key header is Google's recommended method for API key authentication. The Google Cloud documentation explicitly advises against URL-based API keys for this exact reason.

  3. 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-Key header with identical functionality to the query parameter approach.

  4. 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 crypto or tweetnacl to 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-Key HTTP 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 in google-vision.js:16-17 removes the API key from the URL query string and moves it to the X-Goog-Api-Key header, 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

Frequently Asked Questions

What is insecure credential storage?

Storing sensitive credentials like API keys, passwords, or tokens in plaintext files, environment variables without protection, or hardcoded in source code makes them vulnerable to theft if an attacker gains filesystem or code access.

How do you prevent credential storage vulnerabilities in Node.js?

Use environment variables with proper file permissions, dedicated secret management systems (HashiCorp Vault, AWS Secrets Manager), secure credential storage libraries, and never commit secrets to version control. Pass credentials via HTTP headers rather than URL parameters.

What CWE is this vulnerability?

CWE-798 (Use of Hard-Coded Credentials) and CWE-312 (Cleartext Storage of Sensitive Information) both apply. This specific case also relates to CWE-327 (Use of a Broken or Risky Cryptographic Algorithm) if credentials are not encrypted at rest.

Is storing API keys in environment variables enough?

Environment variables are better than plaintext files but still not ideal. They can be exposed via process listings, memory dumps, or logs. Combine environment variables with encryption at rest, proper file permissions (0600), and access controls for maximum security.

Can static analysis detect this vulnerability?

Yes. Static analysis tools can identify credentials stored in config files, API keys in URLs, and hardcoded secrets. However, they may miss dynamically loaded credentials from files. Runtime analysis and secrets scanning are also important complementary approaches.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #32

Related Articles

critical

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.

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.

high

How Denial-of-Service via Unbounded Brace Expansion Happens in Node.js and How to Fix It

A critical denial-of-service vulnerability in the `brace-expansion` package allowed attackers to exhaust process memory through unbounded intermediate array expansion. The fix upgrades the package to patched versions (1.1.18, 2.1.4, 3.0.6, 5.0.9) that implement proper expansion length limits, preventing out-of-memory crashes in production applications.