Back to Blog
high SEVERITY6 min read

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.

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

Answer Summary

This is an Insufficiently Protected Credentials vulnerability (CWE-522) in a Node.js CLI tool, where the sensitive-flag detection regex in `validateCommandInput()` was too narrow to catch common credential flag names like `--password` or `--access-token`. The fix broadens the regex pattern to match additional credential keywords, ensuring more secret-like CLI options are rejected before they can be captured in shell history or `ps aux` output.

Vulnerability at a Glance

cweCWE-522
fixExpanded the blocklist regex in `xmemo-skill.mjs` to include password, credential, client-secret, refresh-token, access-token, private-key, and xmemo-key patterns
riskCredentials passed via CLI flags or environment variables can be captured via shell history, process listings, or plaintext credential files
languageJavaScript (Node.js, ESM)
root causeOverly narrow regex in `validateCommandInput()` failed to block common credential-related flag names
vulnerabilityInsufficiently Protected Credentials

Introduction

The skills/xmemo/scripts/xmemo-skill.mjs file implements a command-line skill for the xmemo tool, and part of its job is guarding against secrets accidentally leaking through CLI arguments. Inside validateCommandInput(), a regex was used to detect and reject flag names that look like credentials — things like --token or --api-key. The intent was solid: stop users (or scripts) from typing secrets directly into a command line where they could be captured by shell history, ps aux, or terminal scrollback.

The problem was in the details. At line 386, the regex only matched a limited set of keyword patterns:

if (/^(token|api[-_]?key|bearer|authorization|cookie|secret)$/i.test(key) && key !== 'from-stdin') {
  throw new Error(`Refusing sensitive command-line option --${key}. Use XMEMO_KEY or --from-stdin where documented.`);
}

This blocked --token, --api-key, --bearer, --authorization, --cookie, and --secret — but nothing stopped a user from passing --password, --client-secret, --access-token, --refresh-token, --private-key, or even --xmemo-key directly on the command line. Any developer extending this tool, or any script automating xmemo calls, could unintentionally leak a credential through one of these unguarded flag names.

The Vulnerability Explained

The core issue is an incomplete allowlist/blocklist. Security-relevant regexes that try to enumerate "bad" input are only as strong as their coverage, and here the coverage had clear gaps.

Before the fix, this regex governed what counted as a sensitive flag:

/^(token|api[-_]?key|bearer|authorization|cookie|secret)$/i

Consider an automation script or a careless user running:

xmemo-skill sync --password=SuperSecretPass123

Because password wasn't in the pattern, this flag would sail past the check. The credential would then:

  1. Land in the shell's history file (.bash_history, .zsh_history), often stored in plaintext indefinitely.
  2. Be visible to any local user running ps aux or /proc/<pid>/cmdline while the process is executing.
  3. Potentially get logged by process monitoring tools, CI/CD systems, or crash reporters that capture command-line arguments.

The PR description also highlights a related concern: the XMEMO_KEY environment variable is the documented "safe" alternative, but even environment variables aren't fully safe from local process inspection — and the --allow-plaintext flag can direct the tool to write credentials unencrypted to ~/.xmemo/skill-credentials.json. Combined, these gaps meant an attacker with local system access (a compromised low-privilege account, a malicious process, or shared-host tenant) had multiple paths to recover secrets: read shell history, inspect /proc, or find the plaintext credentials file.

For a Node.js library like xmemo that's consumed by other developers and pipelines, this matters a lot — any downstream project building automation on top of xmemo-skill.mjs inherits this gap unless it's fixed at the source.

The Fix

The fix tightens the regex used in validateCommandInput() to catch significantly more credential-shaped flag names:

Before:

if (/^(token|api[-_]?key|bearer|authorization|cookie|secret)$/i.test(key) && key !== 'from-stdin') {
  throw new Error(`Refusing sensitive command-line option --${key}. Use XMEMO_KEY or --from-stdin where documented.`);
}

After:

if (/^(token|api[-_]?key|bearer|authorization|cookie|secret|password|passwd|credential|client[-_]?secret|refresh[-_]?token|access[-_]?token|private[-_]?key|xmemo[-_]?key)$/i.test(key) && key !== 'from-stdin') {
  throw new Error(`Refusing sensitive command-line option --${key}. Use XMEMO_KEY or --from-stdin where documented.`);
}

The new pattern adds password, passwd, credential, client[-_]?secret, refresh[-_]?token, access[-_]?token, private[-_]?key, and xmemo[-_]?key — all flag names that commonly carry secret material in real-world CLI tools. It preserves the existing from-stdin exception, so the documented safe path (piping a secret in via stdin) still works without being blocked.

This is a targeted, single-file change: it doesn't alter the behavior of any legitimate, non-credential flag, and it doesn't touch the command dispatch logic elsewhere in the file. Because the check runs early in validateCommandInput(), any of these newly-blocked flag names will now throw immediately with a clear error message pointing users toward the XMEMO_KEY environment variable or --from-stdin, rather than silently accepting a secret on the command line.

Prevention & Best Practices

  • Prefer allowlists over blocklists for security-sensitive input. A blocklist regex like this one requires ongoing maintenance to stay ahead of new naming conventions (refresh-token, access-token, etc.). Where possible, define an explicit allowlist of accepted flags per command instead of trying to enumerate every dangerous name.
  • Never accept secrets via CLI arguments at all, if you can avoid it. Command-line arguments are visible in process listings (ps aux, /proc/<pid>/cmdline) and often persisted to shell history. Environment variables read directly by the process, OS keychains, or secret managers are safer defaults.
  • Treat "plaintext fallback" flags like --allow-plaintext as high-risk features. If your tool supports writing credentials to disk unencrypted, make it opt-in, loudly documented, and ideally gated behind additional confirmation or file permission hardening (e.g., chmod 600).
  • Audit environment variable usage for secrets. Even XMEMO_KEY isn't fully safe — document that users should avoid setting secrets in shell profile files (.bashrc, .zshrc) and instead use per-session exports or dedicated secret-injection tooling.
  • Use static analysis and secret-scanning tools (Semgrep, GitLeaks, or AI-assisted scanners like the one that caught this issue) to continuously check for credential-shaped CLI flags, hardcoded secrets, and insecure storage patterns.
  • Reference the OWASP Secrets Management Cheat Sheet when designing how a CLI tool should accept and store credentials.

Key Takeaways

  • The credential-flag blocklist in validateCommandInput() (xmemo-skill.mjs:386) was missing common secret-related names like password, client-secret, access-token, and xmemo-key — a reminder that regex blocklists need continuous expansion as new naming conventions emerge.
  • Command-line arguments are never truly private on a multi-user or monitored system; ps aux and shell history can expose them long after the command finishes running.
  • The --allow-plaintext flag writing to ~/.xmemo/skill-credentials.json represents a separate, deliberate risk trade-off that should be treated as an explicit opt-in, not a convenience default.
  • Even the "safe" documented path — the XMEMO_KEY environment variable — carries residual risk if set via shell startup scripts, so documentation should steer users toward safer injection methods like --from-stdin.
  • A single-line regex change had outsized security impact here, showing how important it is to review the completeness of security-relevant pattern matches, not just their presence.

How Orbis AppSec Detected This

  • Source: CLI flags parsed from process.argv (e.g., --password, --client-secret, --access-token) passed into validateCommandInput()
  • Sink: Credential values flowing into logged/stored command context, shell history, and process listings once accepted as valid flags
  • Missing control: The sensitive-flag detection regex in validateCommandInput() didn't cover common credential keyword variants, allowing several secret-bearing flag names to bypass the check entirely
  • CWE: CWE-522 (Insufficiently Protected Credentials), related to CWE-214 (Invocation of Process Using Visible Sensitive Information)
  • Fix: Expanded the regex at xmemo-skill.mjs:386 to also match password, passwd, credential, client-secret, refresh-token, access-token, private-key, and xmemo-key, rejecting these flags with a clear error message

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 fix is a small diff with a meaningful security payoff: expanding a single regex in skills/xmemo/scripts/xmemo-skill.mjs closes off several previously-unguarded paths for leaking credentials through CLI flags. It's a good illustration of why blocklist-style security checks demand regular scrutiny — attackers (and well-meaning but careless users) will find the flag names you didn't think to block. Teams building CLI tools that touch secrets should pair this kind of defensive validation with broader guidance: avoid CLI-passed secrets altogether where possible, harden plaintext credential storage options, and steer users toward safer injection mechanisms like piped stdin input.

References

Frequently Asked Questions

What is Insufficiently Protected Credentials?

It's a class of vulnerability (CWE-522) where an application handles secrets—passwords, tokens, API keys—in a way that exposes them to unauthorized observers, such as through command-line arguments, logs, or plaintext storage.

How do you prevent Insufficiently Protected Credentials in Node.js?

Never accept secrets as CLI arguments; require them via environment variables read directly by trusted processes, encrypted secret stores, or stdin, and validate/reject any flag names that look credential-related before they reach argument parsing.

What CWE is Insufficiently Protected Credentials?

CWE-522 (Insufficiently Protected Credentials), often related to CWE-214 (Invocation of Process Using Visible Sensitive Information) when the exposure happens via command-line arguments or environment variables.

Is blocking a few keyword patterns enough to prevent Insufficiently Protected Credentials?

No—keyword-based regex blocklists are inherently incomplete; they must be maintained and combined with structural controls like disallowing credential flags entirely and enforcing plaintext-storage protections.

Can static analysis detect Insufficiently Protected Credentials?

Yes, static analysis and multi-agent AI scanners can flag patterns like credential-bearing CLI flags, plaintext file writes, or environment variable misuse, as was done here.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #26

Related Articles

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.

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.