How Hardcoded API Keys Happen in JavaScript Plugins and How to Fix Them
The Vulnerability at a Glance
| Field | Detail |
|---|---|
| Vulnerability | Hardcoded API Key / Hardcoded Secret |
| CWE | CWE-798: Use of Hard-coded Credentials |
| Language | JavaScript (Node.js) |
| Risk | Unauthorized OCR API access; credential theft via source code |
| Root Cause | form.append('apikey', process.env.OCR_API_KEY \|\| 'K81241004488957') |
| Fix | Remove fallback literal; fail fast when env var is absent |
Direct Answer
This is a hardcoded secret vulnerability (CWE-798) in plugins/ocr.js. The API key 'K81241004488957' was embedded as a fallback value in a form.append() call. Any attacker with source code access could extract and abuse this key. The fix removes the fallback and adds an early-exit guard: if process.env.OCR_API_KEY is not set, the function replies with an error message and stops execution before the API call is made.
Introduction
The plugins/ocr.js file handles optical character recognition requests — a user sends an image, the plugin downloads it, submits it to an external OCR API, and returns the extracted text. It's a convenient feature, but a single line of convenience-oriented code turned it into a critical security liability:
form.append('apikey', process.env.OCR_API_KEY || 'K81241004488957')
The || operator here is doing what JavaScript developers use it for all the time: providing a default value when the left side is falsy. In most contexts, that's perfectly reasonable. In the context of API credentials, it means a live, functional API key — K81241004488957 — is sitting in plaintext inside the source file, ready to be discovered by anyone who can read the code.
This pattern is so common it has its own CWE entry: CWE-798, Use of Hard-coded Credentials. It's also one of the most consistently exploited categories in real-world breaches, precisely because it feels harmless when you write it.
The Vulnerability Explained
What Actually Happened
At line 21 of plugins/ocr.js, the OCR plugin constructs a multipart form submission to an external OCR API service. The apikey field is populated using this expression:
// VULNERABLE — line 21 of plugins/ocr.js
form.append('apikey', process.env.OCR_API_KEY || 'K81241004488957')
The intent was likely: "Use the environment variable in production, but have a fallback so the feature works during development or if someone forgets to set the variable." That's a reasonable developer concern. The execution, however, is dangerous.
The fallback 'K81241004488957' is not a placeholder. It is a real API key that was almost certainly used during development or testing. Once committed to the repository, it becomes permanently accessible in the git history, even if the line is later changed.
How This Gets Exploited
The attack chain here is short — rated as a 2-step chain in the vulnerability assessment:
-
Step 1 — Source acquisition: An attacker gains access to the source code. This could happen through a public GitHub repository, a leaked backup, a compromised developer machine, or even by decompiling a distributed build of the application.
-
Step 2 — Key extraction and abuse: The attacker searches for the string
apikeyorOCR_API_KEYin the codebase, finds'K81241004488957'atplugins/ocr.js:21, and begins making OCR API requests authenticated with that key.
No network interception, no brute force, no sophisticated tooling required. The key is in the code.
Real-World Impact
Depending on the OCR API provider's pricing and rate limits, an attacker exploiting this key could:
- Run up significant charges on the account associated with the key, resulting in unexpected billing for the application owner
- Exhaust API quotas, causing the legitimate OCR feature to fail for real users
- Access usage data or account information if the API key grants broader permissions than just OCR submissions
- Use the key as a pivot — some API providers expose account management endpoints authenticated by the same key
Because this is described as a web service in the threat model, the source code exposure surface is elevated. The application is deployed and potentially accessible to a wide range of actors who might attempt to obtain its code.
The Fix
The fix makes two precise changes to plugins/ocr.js:
Before
const form = new FormData()
form.append('apikey', process.env.OCR_API_KEY || 'K81241004488957')
form.append('language', 'eng')
After
if (!process.env.OCR_API_KEY) return m.reply('OCR API key is not configured.')
const form = new FormData()
form.append('apikey', process.env.OCR_API_KEY)
form.append('language', 'eng')
Why This Fix Works
Change 1 — Early exit guard: The added line if (!process.env.OCR_API_KEY) return m.reply('OCR API key is not configured.') checks for the environment variable before any form construction or API call happens. If the variable is absent, the function returns immediately with a user-facing error message. This is a "fail fast" pattern — the application makes its configuration requirements explicit rather than silently falling back to a hardcoded value.
Change 2 — Removal of the fallback literal: The || 'K81241004488957' expression is completely removed. form.append('apikey', process.env.OCR_API_KEY) now passes the environment variable directly. Since the guard above ensures OCR_API_KEY is truthy before this line executes, there is no risk of submitting an undefined or empty apikey to the OCR service.
The fix is scoped to one file and one logical path — the OCR request handler. Valid inputs (a properly configured OCR_API_KEY environment variable) are completely unaffected. Only the insecure fallback behavior is removed.
What Happens to the Hardcoded Key?
The key K81241004488957 should be treated as permanently compromised. Even after this fix is merged, the key exists in the repository's git history. The correct remediation steps alongside this code fix are:
- Revoke the key immediately via the OCR API provider's dashboard
- Generate a new key and store it in the deployment environment's secret management system
- Audit API usage logs for the old key to determine if unauthorized access occurred
- Consider a
git filter-repoor BFG Repo Cleaner run if the repository is public, to purge the key from history
Prevention & Best Practices
Never Use || for Credential Fallbacks
The pattern process.env.SECRET || 'hardcoded_value' is one of the most common sources of hardcoded credential bugs in Node.js applications. The || operator is idiomatic JavaScript for default values, which is exactly why it's dangerous here — it looks completely normal.
Instead, use an explicit check:
// Good: fail fast with a clear error
if (!process.env.OCR_API_KEY) {
throw new Error('OCR_API_KEY environment variable is required but not set.')
}
Or use a configuration validation library at startup:
// Using a schema validator like envalid or zod
const env = cleanEnv(process.env, {
OCR_API_KEY: str({ docs: 'https://ocr.space/ocrapi' }),
})
Validate All Required Secrets at Startup
Don't wait until a feature is invoked to discover that a required secret is missing. Validate all required environment variables when the application starts, so misconfiguration fails loudly at deploy time rather than silently at runtime (or worse, silently succeeds with a hardcoded fallback).
Use a Secrets Manager for Production
For production deployments, avoid environment variables stored in .env files on disk. Use a dedicated secrets management solution:
- AWS Secrets Manager or AWS Parameter Store
- HashiCorp Vault
- Azure Key Vault / GCP Secret Manager
- Doppler or Infisical for developer-friendly workflows
Add Secret Scanning to Your CI/CD Pipeline
Tools that can catch hardcoded secrets before they reach production:
- TruffleHog — scans git history for high-entropy strings and known secret patterns
- GitLeaks — configurable secret detection for git repos
- GitHub Secret Scanning — built into GitHub, alerts on known API key formats
- Semgrep — static analysis rules for hardcoded credentials
Relevant Security Standards
- OWASP Top 10 A02:2021 — Cryptographic Failures: Storing or transmitting credentials without protection
- OWASP Secrets Management Cheat Sheet: Guidance on handling secrets in applications
- CWE-798: Use of Hard-coded Credentials
- CWE-259: Use of Hard-coded Password (related, more specific)
Key Takeaways
- The
|| 'fallback'pattern is dangerous for secrets: Inplugins/ocr.js,process.env.OCR_API_KEY || 'K81241004488957'silently used a live API key whenever the environment variable was absent — a pattern that looks harmless but creates a critical exposure. - Hardcoded keys survive code changes via git history: Even after the fix is merged,
K81241004488957exists in the repository's commit history and must be revoked at the provider level. - Fail fast beats silent fallback for credentials: The replacement guard
if (!process.env.OCR_API_KEY) return m.reply('OCR API key is not configured.')makes misconfiguration visible and actionable instead of silently dangerous. - Source code access is a realistic threat vector: This was assessed as a 2-step exploit chain — obtain source, extract key. For web services with any public-facing exposure, this attack surface is real.
- Secret scanning in CI would have caught this before merge: Tools like TruffleHog or GitHub Secret Scanning can detect patterns like
K81241004488957automatically, preventing this class of bug from reaching production.
How Orbis AppSec Detected This
- Source: The hardcoded string literal
'K81241004488957'embedded directly inplugins/ocr.jsat line 21 - Sink:
form.append('apikey', process.env.OCR_API_KEY || 'K81241004488957')— the credential is passed to an external HTTP request to the OCR API service - Missing control: No environment variable presence validation; the
||fallback operator bypassed any requirement to configure the secret externally - CWE: CWE-798 — Use of Hard-coded Credentials
- Fix: Removed the
|| 'K81241004488957'fallback and added an explicit early-exit check that returns an error message whenOCR_API_KEYis not set
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 vulnerability in plugins/ocr.js is a textbook example of how developer convenience — a simple || fallback to keep the OCR feature working without configuration — can create a critical security exposure. The key K81241004488957 was never meant to be permanent, but once committed, it became a permanent part of the repository's history and a permanent risk.
The fix is minimal and precise: one line added, one literal removed. But the lesson is broader. Every time you reach for process.env.SECRET || 'some_value' in a Node.js application, ask whether that fallback value is something you'd be comfortable publishing publicly. If the answer is no, use a fail-fast guard instead.
Credentials belong in secrets managers and environment variables — not in source code, not in comments, and not in || fallback expressions.