Back to Blog
critical SEVERITY7 min read

How Plaintext Credential Storage Happens in Node.js Config Files and How to Fix It

A critical vulnerability in `config.js` allowed OAuth tokens and user IDs to silently fall back to empty strings when environment variables were unset, enabling credential bypass and potential hardcoded secret exposure. The fix removes the `|| ""` fallback pattern, ensuring credentials are either properly set or explicitly `undefined`, and updates downstream checks to use truthy evaluation instead of empty-string comparison. This change closes a subtle but dangerous gap that could have allowed A

O
By Orbis AppSec
Published August 26, 2026Reviewed August 26, 2026

Answer Summary

This is a Plaintext/Insecure Credential Storage vulnerability (CWE-522) in a Node.js `config.js` file, where `process.env.muserId || ""` and `process.env.mtoken || ""` silently defaulted to empty strings when environment variables were unset. The fix removes the `|| ""` fallbacks so unset credentials become `undefined`, and updates downstream conditional checks in `androidURL.js` and `updateData.js` from `!= ""` comparisons to truthy checks (`if (userId && token)`), ensuring the application never operates with missing credentials.

Vulnerability at a Glance

cweCWE-522
fixRemove `|| ""` fallbacks so unset credentials are `undefined`, and update downstream checks to use truthy evaluation
riskOAuth tokens and user IDs silently become empty strings, allowing unauthenticated API calls or credential bypass
languageJavaScript (Node.js)
root cause`process.env.muserId || ""` and `process.env.mtoken || ""` mask missing credentials with empty strings instead of failing safely
vulnerabilityInsecure Credential Storage / Silent Credential Bypass via Empty-String Fallback

The Problem Hidden in Plain Sight

The config.js file in this Node.js application handles two of the most sensitive values in the entire codebase: a user ID (muserId) and an OAuth token (mtoken) used to authenticate against external video APIs. At first glance, the configuration looked reasonable — it was reading from environment variables, which is the recommended approach. But two characters, "", introduced a critical security flaw that could silently disable authentication entirely.

// Before the fix
const userId = process.env.muserId || ""
const token  = process.env.mtoken  || ""

This pattern is so common in JavaScript that many developers write it on autopilot. But for credentials, it creates a dangerous trap.


The Vulnerability Explained

What the Code Was Actually Doing

The || "" operator in JavaScript is a short-circuit fallback: if process.env.muserId is undefined (i.e., the environment variable is not set), the expression evaluates to "" — an empty string. This means:

  1. If a developer forgets to set muserId or mtoken in their environment, the application doesn't crash or warn them. It silently continues with empty credentials.
  2. Downstream checks used string comparison, not truthy evaluation:
// In utils/androidURL.js — BEFORE the fix
if (rateType != 2 && userId != "" && token != "") {
  headers.UserId = userId
  headers.UserToken = token
}

// In utils/updateData.js — BEFORE the fix
if (userId != "" && token != "") {
  // refresh token logic
}

When userId and token are empty strings, userId != "" evaluates to false. This means authentication headers are silently omitted from API requests — the application proceeds unauthenticated without any error, warning, or exception.

The Three-Part Exploit Chain

This vulnerability creates a two-step chain with real-world consequences:

Step 1 — Missing environment variables go unnoticed. A developer deploys the application without setting muserId and mtoken. No startup check, no runtime error, no log warning. The app boots normally.

Step 2 — API calls proceed without credentials. In getAndroidURL() within utils/androidURL.js, the UserId and UserToken headers are simply not added to the request. Depending on the external API's behavior, this could result in anonymous access, degraded functionality, or — critically — if the external API has its own bugs — unintended data exposure.

Step 3 — Hardcoded defaults enter version control. The || "" pattern is an invitation to hardcode. A developer under deadline pressure might change process.env.mtoken || "" to process.env.mtoken || "actual_token_value_here" and commit it. The empty string default normalizes the pattern of providing fallback credential values.

Why This Is Classified as CWE-522

CWE-522: Insufficiently Protected Credentials applies here because the credential handling does not adequately protect the credentials from being absent or substituted. The application never validates that credentials are actually present before using them, and the fallback mechanism actively conceals their absence.


The Fix

The fix is surgical and touches three files, each for a specific reason.

Change 1: config.js — Remove the Empty-String Fallback

// BEFORE
const userId = process.env.muserId || ""
const token  = process.env.mtoken  || ""

// AFTER
const userId = process.env.muserId
const token  = process.env.mtoken

By removing || "", unset environment variables now produce undefined instead of "". This is the critical behavioral change: undefined is falsy in JavaScript, which means truthy checks will correctly detect missing credentials.

Change 2: utils/androidURL.js — Truthy Check Replaces String Comparison

// BEFORE
if (rateType != 2 && userId != "" && token != "") {

// AFTER
if (rateType != 2 && userId && token) {

The != "" check only caught empty strings. The new userId && token check catches undefined, null, "", 0, and any other falsy value — making the guard more robust. Now, if credentials are missing for any reason, the authentication headers are correctly omitted and the application behaves predictably rather than silently.

Change 3: utils/updateData.js — Same Pattern Fixed in Token Refresh Logic

// BEFORE
if (userId != "" && token != "") {

// AFTER
if (userId && token) {

The monthly token refresh logic in update() had the same fragile string comparison. If userId and token were somehow empty strings (e.g., set to "" explicitly in the environment), the old code would skip the refresh silently. The truthy check ensures this logic only runs when real credentials are present.

The Combined Effect

Together, these three changes create a consistent credential-handling contract throughout the application: credentials are either present and truthy, or they are absent and the dependent logic is skipped. There is no longer a "looks set but is empty" middle state.


Prevention & Best Practices

1. Validate Required Credentials at Startup

Instead of silently allowing missing credentials, add an explicit startup check:

const userId = process.env.muserId
const token  = process.env.mtoken

if (!userId || !token) {
  console.error("FATAL: muserId and mtoken environment variables must be set")
  process.exit(1)
}

This converts a silent runtime failure into a loud startup failure — far easier to diagnose.

2. Use a Configuration Validation Library

Libraries like envalid or zod can validate environment variables at startup with clear error messages:

import { cleanEnv, str } from 'envalid'

const env = cleanEnv(process.env, {
  muserId: str({ docs: 'User ID for Migu Video API' }),
  mtoken:  str({ docs: 'Auth token for Migu Video API' }),
})

3. Never Use || "" for Sensitive Values

Reserve the || "default" pattern for non-sensitive configuration like ports, log levels, or feature flags. For credentials, secrets, API keys, and tokens, always let the value be undefined if unset, and handle that case explicitly.

4. Add a .env.example File

Provide a .env.example with placeholder values (never real credentials) so developers know which variables are required:

muserId=YOUR_USER_ID_HERE
mtoken=YOUR_TOKEN_HERE
mport=1234

5. Use Static Analysis

Semgrep rules can detect this exact pattern. A rule matching process.env.$VAR || "" where $VAR contains keywords like token, key, secret, or password would have caught this before it reached production.

Relevant standards:
- OWASP: Secrets Management Cheat Sheet
- CWE-522: Insufficiently Protected Credentials
- CWE-295: Improper Certificate Validation (related credential handling)


Key Takeaways

  • The || "" pattern is dangerous for credentials. In config.js, process.env.mtoken || "" masked a missing token as an empty string, silently bypassing authentication in getAndroidURL() and the token refresh logic.
  • String comparison (!= "") is a weaker guard than truthy checks. The checks in androidURL.js and updateData.js only caught empty strings, not undefined or null — the truthy check if (userId && token) is strictly more correct.
  • Silent failures are harder to debug than loud ones. The original code would allow the application to run indefinitely without credentials and without any error, making it extremely difficult to diagnose in production.
  • A three-file change was required because the vulnerability wasn't just in config.js — it was reinforced by the downstream != "" checks that trusted the empty-string fallback behavior.
  • Environment variable usage alone is not sufficient — the fallback value matters as much as the source of the value.

How Orbis AppSec Detected This

  • Source: process.env.muserId and process.env.mtoken in config.js:2-3, where unset environment variables produce undefined
  • Sink: The || "" operator at assignment, and the != "" comparisons in utils/androidURL.js:58 and utils/updateData.js:220, which together create a path where missing credentials silently pass authentication guards
  • Missing control: No startup validation that credentials are actually present; no truthy check before using credentials in API request construction
  • CWE: CWE-522 — Insufficiently Protected Credentials
  • Fix: Removed || "" fallbacks from config.js and replaced != "" string comparisons with truthy checks (if (userId && token)) in both downstream files

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

This vulnerability is a textbook example of how a single, idiomatic JavaScript pattern — || "" — can silently undermine an entire authentication flow. The config.js file was doing the right thing conceptually (reading from environment variables), but the empty-string fallback turned a safe pattern into a dangerous one. The fix required changes in three files precisely because the vulnerability's impact was distributed: the silent default in config.js was only harmful because androidURL.js and updateData.js trusted that the values would never be meaningfully absent.

For developers working with Node.js configuration, the lesson is clear: treat credentials differently from other config values. Don't give them defaults. Fail loudly at startup if they're missing. And use truthy checks, not string comparisons, when deciding whether a credential is usable.


References

Frequently Asked Questions

What is a silent credential bypass via empty-string fallback?

It occurs when code substitutes an empty string for a missing credential, allowing logic that checks `!= ""` to pass even when no real credential was provided — effectively bypassing authentication silently.

How do you prevent plaintext credential exposure in Node.js config files?

Never use `|| ""` as a fallback for sensitive values. Let them be `undefined` if unset, validate their presence at startup, and use truthy checks (`if (token)`) rather than empty-string comparisons.

What CWE is associated with plaintext credential storage?

CWE-522 (Insufficiently Protected Credentials) covers cases where credentials are stored or transmitted without adequate protection, including situations where missing credentials are masked by empty-string defaults.

Is using environment variables enough to prevent credential exposure?

Using environment variables is a good first step, but the `|| ""` fallback pattern undermines this protection. If the environment variable is not set, the application silently uses an empty string, which can bypass authentication checks or get committed as a default value.

Can static analysis detect empty-string credential fallbacks?

Yes. Tools like Semgrep can detect patterns such as `process.env.VARIABLE || ""` for variables with names matching credential patterns (token, key, secret, password). Orbis AppSec's multi-agent AI scanner flagged this exact pattern in this codebase.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #128

Related Articles

critical

How Plaintext Secret Storage Happens in Cloudflare Workers (wrangler.toml) and How to Fix It

A critical misconfiguration in `platforms/m365/wrangler.toml` left developers one copy-paste away from committing live API keys directly into git history. The fix adds an explicit warning comment blocking the `[vars]` anti-pattern and adds `.dev.vars` to `.gitignore`, ensuring secrets flow through Cloudflare's encrypted `wrangler secret` mechanism instead of plaintext config. This matters because git history is permanent — a key committed even once can be extracted long after it's "deleted."

critical

How Hardcoded API Keys Happen in JavaScript and How to Fix Them

A critical security vulnerability was discovered in `src/js/init.js` where a Bugsnag API key was hardcoded directly into client-side JavaScript, making it visible to anyone who inspects the page source or JavaScript bundle. The fix replaces the hardcoded string with an environment variable reference (`import.meta.env.VITE_BUGSNAG_API_KEY`), ensuring the key is injected at build time rather than baked into the shipped code. This pattern is one of the most common — and most avoidable — secrets exp

high

How Hardcoded API Keys happen in JavaScript and how to fix it

A critical security vulnerability was discovered in `javascripts/common.js` where Firebase API keys, auth domains, and sender IDs were hardcoded directly in client-side JavaScript. Any user who opened browser DevTools or viewed page source could extract these credentials and make unauthorized calls to the Firebase Realtime Database and Yandex Translation services. The fix moves all sensitive configuration values to environment variables, ensuring secrets never reach the client bundle.

critical

How Hardcoded API Keys happen in JavaScript plugins and how to fix them

A critical hardcoded API key was discovered in `plugins/ocr.js` at line 21, where the OCR integration used a plaintext fallback credential `'K81241004488957'` whenever the `OCR_API_KEY` environment variable was absent. This exposed a live API key to anyone with repository access, enabling unauthorized use of the OCR service. The fix removes the hardcoded fallback entirely and fails fast with a clear error message when the required environment variable is not configured.

high

How Information Disclosure happens in Go dependency management and how to fix it

CVE-2026-42151 is a high-severity information disclosure vulnerability in the Prometheus monitoring library (github.com/prometheus/prometheus) that exposed Azure OAuth client secrets through the Prometheus configuration API endpoint. Applications depending on versions prior to v0.311.3 were at risk of leaking sensitive Azure credentials to anyone with access to the config API. The fix involves upgrading the dependency in go.mod from v0.310.0 to v0.311.3.

critical

How eval() Code Injection happens in JavaScript and how to fix it

A critical code injection vulnerability was discovered in `js/lib/jsencrypt.js` at line 195, where a direct `eval()` call executed a JavaScript string shim for the `process` object in browser environments. If an attacker could influence the string passed to `eval()`—through a compromised dependency, a man-in-the-middle attack, or supply chain tampering—they could achieve arbitrary JavaScript execution in any user's browser. The fix replaces the `eval()` call with the equivalent inline JavaScript