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

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.