Back to Blog
critical SEVERITY7 min read

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.

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

Answer Summary

This vulnerability is a hardcoded secret (CWE-798) in a Node.js/JavaScript LLM plugin where the API key `actor-studio-gpt-beta` was embedded directly in `src/plugins/llm/index.js` and `src/settings/llmNameGeneration.js`. Attackers could extract this key from the source code or distributed package and use it to make unauthorized API calls to the LLM service. The fix replaces the hardcoded default with an empty string in both files, enforcing that the API key must be supplied through module settings at runtime.

Vulnerability at a Glance

cweCWE-798 (Use of Hard-coded Credentials)
fixReplace hardcoded key with empty string defaults, requiring explicit key configuration via module settings
riskUnauthorized access to LLM service endpoints; API abuse, cost escalation, and data exfiltration
languageJavaScript (Node.js)
root causeAPI key `actor-studio-gpt-beta` hardcoded as class property default and settings default value
vulnerabilityHardcoded API Key / Credential Exposure

Introduction

In the Actor Studio application, we discovered a critical hardcoded API key vulnerability in src/plugins/llm/index.js at line 5. The LLM class—responsible for powering name and biography generation via an external AI service—had the API key actor-studio-gpt-beta baked directly into the class property definition:

apiKey = 'actor-studio-gpt-beta';

This wasn't an isolated oversight. The same key appeared as a default value in src/settings/llmNameGeneration.js, meaning it was doubly embedded in the codebase and distributed to every user of the application. For any developer building plugin systems that interact with external APIs, this is a textbook example of how a seemingly convenient default can become a critical security liability.

The Vulnerability Explained

The LLM class in src/plugins/llm/index.js serves as the client for Actor Studio's AI-powered features. It connects to https://actor-studio-llm.vercel.app/api to call endpoints like generateName and generateBiography. Here's the vulnerable code:

class LLM {
  apiKey = 'actor-studio-gpt-beta';
  baseUrl = 'https://actor-studio-llm.vercel.app/api';
  // ...
}

And in the settings file src/settings/llmNameGeneration.js, the same key was registered as the default setting value:

{
  scope: 'world',
  config: true,
  type: String,
  default: 'actor-studio-gpt-beta',
}

Why This Is Dangerous

This creates a two-pronged exposure:

  1. Source code exposure: Anyone who clones the repository, inspects the npm package, or views the distributed JavaScript bundle can extract the string actor-studio-gpt-beta.

  2. Settings system exposure: Because the key is the default in the settings registration, even users who never explicitly configure an API key are silently using this shared credential. The key also appears in language/localization files referenced by the settings system, widening the attack surface further.

Concrete Attack Scenario

An attacker discovers the Actor Studio repository on GitHub (or decompiles the distributed package). They search for strings like apiKey, Bearer, or Authorization and immediately find actor-studio-gpt-beta. With this key, they craft HTTP requests:

curl -X POST https://actor-studio-llm.vercel.app/api/generateName \
  -H "Authorization: Bearer actor-studio-gpt-beta" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "Generate a fantasy character name"}'

This grants the attacker:

  • Unauthorized API access: Free use of the LLM service endpoints without any legitimate Actor Studio installation.
  • Cost escalation: If the LLM backend charges per request (common with GPT-based services), the attacker can rack up costs against the service operator's account.
  • Abuse and data exfiltration: The attacker could probe the API for additional endpoints, attempt prompt injection attacks against the backend LLM, or use the service as a proxy for their own AI workloads.
  • Credential stuffing: The key pattern actor-studio-gpt-beta might hint at naming conventions for other internal keys, enabling further enumeration.

Because this is a 2-step exploitation chain (extract key → craft requests), the barrier to exploitation is extremely low—no sophisticated tooling or deep application knowledge is required.

The Fix

The fix is surgically scoped to exactly two files, removing the hardcoded credential from both locations where it appeared.

Change 1: src/plugins/llm/index.js

Before:

class LLM {
  apiKey = 'actor-studio-gpt-beta';
  baseUrl = 'https://actor-studio-llm.vercel.app/api';

After:

class LLM {
  // No default API key is hardcoded here; it must be supplied via module settings.
  apiKey = '';
  baseUrl = 'https://actor-studio-llm.vercel.app/api';

The apiKey class property is now initialized to an empty string. This means the LLM plugin cannot function until a legitimate key is explicitly configured through the module's settings system. The comment makes the intent crystal clear to future contributors: this is a deliberate security decision, not a missing default.

Change 2: src/settings/llmNameGeneration.js

Before:

{
  scope: 'world',
  config: true,
  type: String,
  default: 'actor-studio-gpt-beta',
}

After:

{
  scope: 'world',
  config: true,
  type: String,
  default: '',
}

The settings registration now defaults to an empty string as well. This is critical because even if someone had already patched index.js, the settings system would have continued injecting the hardcoded key as the default value for any user who hadn't explicitly changed their configuration.

Why Both Changes Are Necessary

Fixing only index.js would leave the key exposed in the settings definition, where it would still be readable in the source and would be auto-populated into new installations. Fixing only the settings file would leave the class-level default intact, meaning the key would still be used if settings retrieval failed or returned undefined. Both files had to be patched to fully eliminate the credential from the codebase.

Important Post-Fix Action

The key actor-studio-gpt-beta must be considered compromised and should be rotated on the backend service (actor-studio-llm.vercel.app). Since it existed in the git history, simply removing it from the current code doesn't prevent extraction from previous commits.

Prevention & Best Practices

1. Never Hardcode Secrets—Even "Temporary" Ones

It's tempting to set a default API key during development for convenience. But "temporary" defaults have a way of shipping to production. Use environment variables or secure configuration from the start:

class LLM {
  apiKey = process.env.LLM_API_KEY || '';
  // ...
}

2. Implement Secret Scanning in CI/CD

Tools like truffleHog, GitLeaks, or GitHub's native secret scanning can catch hardcoded credentials before they reach the main branch. Add them as pre-commit hooks or CI pipeline steps.

3. Use a Secrets Manager

For production applications, use dedicated secrets management:
- Environment variables (minimum viable approach)
- HashiCorp Vault, AWS Secrets Manager, or Azure Key Vault (enterprise-grade)
- Foundry VTT module settings with user-supplied keys (as this fix now requires)

4. Audit Settings Defaults

Settings systems often have default values that are overlooked during security reviews. Treat settings defaults with the same scrutiny as code-level constants—they're equally visible and equally dangerous.

5. Rotate Compromised Keys Immediately

Any key that has ever appeared in source code, logs, or configuration files should be rotated. Git history is permanent (without force-push and rewriting), so removal from HEAD alone is insufficient.

Relevant Standards

Key Takeaways

  • The actor-studio-gpt-beta key was embedded in two separate files—both the LLM class definition and the settings registration—demonstrating how hardcoded secrets can propagate across a codebase.
  • Settings system defaults are a hidden attack surface: the key in llmNameGeneration.js would have persisted even if the class property was fixed, silently distributing the credential to every new installation.
  • A shared API key across all installations means a single compromise affects everyone: any user extracting the key could abuse the service on behalf of all Actor Studio users.
  • The fix correctly initializes apiKey to an empty string in both locations, enforcing that the LLM plugin is non-functional until a legitimate key is explicitly provided—a secure-by-default posture.
  • Post-fix key rotation is mandatory: the actor-studio-gpt-beta key remains in git history and must be revoked on the actor-studio-llm.vercel.app backend.

How Orbis AppSec Detected This

  • Source: The hardcoded string literal 'actor-studio-gpt-beta' in src/plugins/llm/index.js:5 and src/settings/llmNameGeneration.js:34, distributed with the application package.
  • Sink: The Authorization header construction in the LLM class methods (generateName, generateBiography) that send Bearer actor-studio-gpt-beta to https://actor-studio-llm.vercel.app/api/ endpoints.
  • Missing control: No environment variable lookup, no secrets manager integration, and no mechanism requiring user-supplied keys before API calls could be made.
  • CWE: CWE-798 — Use of Hard-coded Credentials
  • Fix: Replaced the hardcoded API key default 'actor-studio-gpt-beta' with an empty string '' in both src/plugins/llm/index.js and src/settings/llmNameGeneration.js, requiring explicit key configuration via module settings.

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

Hardcoded credentials are one of the most common and most preventable security vulnerabilities in modern software. In this case, the API key actor-studio-gpt-beta was embedded directly in the LLM plugin class and its settings registration, making it trivially extractable by anyone with access to the source code or distributed package. The fix is straightforward—replace hardcoded defaults with empty strings and require explicit configuration—but the downstream action of rotating the compromised key is equally critical. If you're building plugin systems or any code that interacts with external APIs, treat every credential as sensitive from day one and never let convenience defaults ship to production.

References

Frequently Asked Questions

What is a hardcoded API key vulnerability?

A hardcoded API key vulnerability occurs when secret credentials like API keys, passwords, or tokens are embedded directly in source code rather than being loaded from secure, external configuration sources. Anyone with access to the code can extract and misuse these credentials.

How do you prevent hardcoded secrets in JavaScript?

Use environment variables, secure vaults (like HashiCorp Vault or AWS Secrets Manager), or runtime configuration systems to inject secrets. Never set default values for API keys in class properties or settings definitions. Use tools like git-secrets or truffleHog to scan for leaked credentials in your codebase.

What CWE is hardcoded API key exposure?

It is classified as CWE-798: Use of Hard-coded Credentials. This covers any instance where credentials are embedded directly in source code, configuration files, or build artifacts rather than being managed through secure external mechanisms.

Is removing the key from source code enough to prevent this vulnerability?

Removing the key from source code is necessary but not sufficient. You must also rotate the exposed key immediately since it should be considered compromised. Additionally, implement secret scanning in CI/CD pipelines and audit git history to ensure the old key isn't recoverable from previous commits.

Can static analysis detect hardcoded API keys?

Yes. Tools like Semgrep, truffleHog, GitLeaks, and GitHub's built-in secret scanning can detect hardcoded credentials using pattern matching and entropy analysis. Custom rules can be written to flag specific key patterns like the `actor-studio-gpt-beta` string found in this case.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #285

Related Articles

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 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 Rate Limiting Vulnerabilities Happen in Node.js OAuth Endpoints and How to Fix Them

A critical resource exhaustion vulnerability was discovered in the OAuth token endpoint at `server/routes/oauth.js`. Without rate limiting, attackers could flood the `/api/oauth/token` endpoint with requests, each triggering expensive bcrypt verification operations that would exhaust server CPU and memory. The fix implements per-IP rate limiting using `express-rate-limit` to cap requests at 20 per 15-minute window.