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 Hardcoded API Keys in WASM Modules Happen in KAP and How to Fix Them

A critical security vulnerability in `wasm/kap/standard-lib/fhelp-impl.kap` exposed hardcoded Gemini API keys directly in source code distributed to end users via WASM modules. The fix replaces the embedded credential with secure environment variable retrieval, preventing credential extraction through browser developer tools or binary inspection.

critical

How Hardcoded API Key Exposure Happens in Node.js and How to Fix It

A critical vulnerability in `archive/open_claude_code/src/api/client.mjs` exposed five different API providers' credentials through direct `process.env` access. The fix introduced a centralized `readApiKey()` function to enforce secure credential retrieval across Anthropic, OpenAI, Google, and other integrations.

high

How Insufficiently Protected Credentials happens in Node.js and how to fix it

A regex-based guard in `skills/xmemo/scripts/xmemo-skill.mjs` was supposed to block sensitive credentials from being passed as CLI flags, but it only matched a handful of keyword patterns—missing `password`, `client-secret`, `access-token`, `xmemo-key`, and more. The fix expands the blocklist regex to cover these additional credential patterns, closing a gap that could let secrets end up in shell history and process listings.

critical

How Hardcoded Firebase API Keys Happen in JavaScript Service Workers and How to Fix Them

A production Firebase service worker file (`static/firebase-messaging-sw.js`) contained hardcoded API keys and project configuration directly in publicly accessible JavaScript — including a second, previously commented-out set of credentials from an older project. While Firebase web config values are technically public identifiers, pairing them with unrestricted Firebase projects or missing Security Rules turns them into an open door for push notification abuse, quota exhaustion, and unauthorize

critical

How hardcoded API key exposure happens in Node.js plugins and how to fix it

A critical hardcoded API key (`actor-studio-gpt-beta`) was discovered in the `src/plugins/llm/index.js` file of the Actor Studio application, granting anyone with source code access the ability to make unauthorized requests to the LLM service endpoints. The fix removes the default key from both the LLM class definition and the settings module, requiring the key to be explicitly configured through module settings instead.

high

How Sensitive Data Exposure happens in Zotero plugins and how to fix it

A high-severity data exposure vulnerability in `Zotero.ts` automatically transmitted complete document metadata—including private notes, attachment paths, and tags—to external LLM services without user consent. The fix replaces broad `item.toJSON()` serialization with explicit field selection, sending only essential bibliographic data.