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
•Technically reviewed by Anupam Mediratta•Published August 26, 2026•Reviewed 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.


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.


Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #128

Related Articles

critical

redmine_drawio View Hook Inlines Base64 Redmine API Keys

The Redmine drawio plugin's body-bottom view listener embedded the logged-in user's Redmine REST API key into client-side JavaScript on every wiki and issue page, "protected" only by Base64 encoding of the reversed string. Any script on the page — or anyone with browser developer tools, a cached copy of the HTML, or a proxy log — could decode it in one line and act as that user through the Redmine REST API. The fix removes the embedded credential from the rendered page entirely; the plugin's dia

critical

Yandex Translate API Key Leaked via URL Query Parameter

The `translateYandex()` helper built its request URL by interpolating the caller-supplied API key directly into the query string, meaning every call leaked the credential into server access logs, proxy logs, and any Referer header sent by intermediaries. The fix switches the request from a GET with the key in the URL to a POST with the key in the request body via `URLSearchParams`, removing the credential from any URL-logging surface entirely.

critical

Google Generative AI Keys Exposed in Client-Side Fetch URLs

A client-side AI model discovery utility was embedding Google Generative AI API keys directly in fetch request URLs, making them visible to any user inspecting network traffic or browser DevTools. The fix moves the key from the URL query parameter to a secure HTTP header, eliminating exposure while maintaining API authentication.

critical

How credential leakage through console logging happens in JavaScript browser extensions and how to fix it

A browser extension's `src/background/credentials.js` printed the full Strava authentication cookie string — including a signed JWT and CloudFront-Signature values — straight into the extension console via `console.debug`. Anyone who could open DevTools on the background page (or any tooling that scraped the console) could copy a live session and impersonate the user. The fix replaces the credential payload in both log statements with `Boolean(credentials)` and strips a realistic-looking JWT out

critical

How Hardcoded Secrets Compromise Authentication in JavaScript and How to Fix It

A critical vulnerability in `Tool/QuantumultX/Rewrite/RRSP.js` exposed hardcoded API authentication credentials—a TOKEN and UMID device identifier—directly in source code. Anyone with repository access could extract these credentials to impersonate the legitimate user and gain full account access to the RRTV API service. The fix replaced hardcoded secrets with empty placeholders, forcing users to manually configure credentials through secure channels.

critical

How API Key Exposure in Request Bodies happens in React and how to fix it

The Chatbot component in gitforme was transmitting Azure OpenAI API keys inside JSON request bodies, causing them to be logged by servers, proxies, and middleware. By moving the apiKey from requestBody.apiKey to an Authorization header, credentials are now protected from persistence in generic request logging infrastructure.