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
•Technically reviewed by Anupam Mediratta•Published September 3, 2026•Reviewed 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.


Prevention and further reading

Frequently Asked Questions

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.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #32

Related Articles

critical

ARTIFICIAL_ANALYSIS_API_KEY: Unvalidated os.getenv in Model Sync

A model-catalogue sync utility read the `ARTIFICIAL_ANALYSIS_API_KEY` environment variable with a bare `os.getenv()` call and passed whatever it found straight into `fetch_intelligence_from_api()`. Any malformed value — a trailing newline from an `--env-file`, a whitespace-only placeholder, or a pasted `Bearer …` prefix — was treated as a valid credential and forwarded into an outbound HTTP request, where it could surface in CI error output. The fix normalizes the value, validates it against a s

critical

redmine_drawio View Hook Inlines Base64 Redmine API Keys

The Redmine drawio plugin's body-bottom view listener embedded the logged-in user's Redmine REST API key into client-side JavaScript on every wiki and issue page, "protected" only by Base64 encoding of the reversed string. Any script on the page — or anyone with browser developer tools, a cached copy of the HTML, or a proxy log — could decode it in one line and act as that user through the Redmine REST API. The fix removes the embedded credential from the rendered page entirely; the plugin's dia

critical

Yandex Translate API Key Leaked via URL Query Parameter

The `translateYandex()` helper built its request URL by interpolating the caller-supplied API key directly into the query string, meaning every call leaked the credential into server access logs, proxy logs, and any Referer header sent by intermediaries. The fix switches the request from a GET with the key in the URL to a POST with the key in the request body via `URLSearchParams`, removing the credential from any URL-logging surface entirely.

critical

Google Generative AI Keys Exposed in Client-Side Fetch URLs

A client-side AI model discovery utility was embedding Google Generative AI API keys directly in fetch request URLs, making them visible to any user inspecting network traffic or browser DevTools. The fix moves the key from the URL query parameter to a secure HTTP header, eliminating exposure while maintaining API authentication.

critical

How credential leakage through console logging happens in JavaScript browser extensions and how to fix it

A browser extension's `src/background/credentials.js` printed the full Strava authentication cookie string — including a signed JWT and CloudFront-Signature values — straight into the extension console via `console.debug`. Anyone who could open DevTools on the background page (or any tooling that scraped the console) could copy a live session and impersonate the user. The fix replaces the credential payload in both log statements with `Boolean(credentials)` and strips a realistic-looking JWT out

critical

How Hardcoded Secrets Compromise Authentication in JavaScript and How to Fix It

A critical vulnerability in `Tool/QuantumultX/Rewrite/RRSP.js` exposed hardcoded API authentication credentials—a TOKEN and UMID device identifier—directly in source code. Anyone with repository access could extract these credentials to impersonate the legitimate user and gain full account access to the RRTV API service. The fix replaced hardcoded secrets with empty placeholders, forcing users to manually configure credentials through secure channels.