Back to Blog
high SEVERITY5 min read

package_abridge.js Command Injection via Unsanitized CLI Arguments

A high-severity command injection vulnerability in a build script allowed attackers who control CLI arguments to execute arbitrary shell commands by injecting metacharacters into an unvalidated parameter. The fix validates incoming CLI arguments and rejects those containing dangerous shell metacharacters before they reach command execution.

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

Answer Summary

The build script in the affected codebase passes the second command-line argument (process.argv[2]) directly to shell command execution without sanitization. An attacker controlling the build invocation could inject shell metacharacters such as semicolons, pipes, or command substitution syntax to execute arbitrary commands with the privileges of the build process. The fix adds input validation that rejects CLI arguments containing dangerous characters before concatenation into shell commands. CWE-78 (Improper Neutralization of Special Elements used in an OS Command).

Vulnerability at a Glance

cweCWE-78
fixWhitelist validation rejecting arguments containing shell metacharacters
riskArbitrary code execution during build process if attacker controls CLI arguments
languageJavaScript
root causeDirect concatenation of unvalidated process.argv[2] into shell command strings
vulnerabilityOS Command Injection

Build Scripts and the Cost of Convenience

Build scripts exist in a privileged position in any codebase—they run with developer credentials, often on CI/CD systems with elevated permissions, and they frequently invoke external tools. When a build script also accepts command-line arguments, those arguments become an implicit trust boundary. If the script concatenates those arguments directly into shell commands without validation, that boundary dissolves.

This is exactly what happened in the build orchestration script. The second CLI argument—intended to pass options to a downstream tool—was concatenated directly into a shell command string without any validation or escaping. An attacker or insider who could invoke the build with a crafted argument could inject arbitrary shell commands.

Affected Versions

Affected N/A (first-party code)
Fixed in N/A (first-party code fix)
Ecosystem N/A
CVE / GHSA not assigned
CWE CWE-78 (Improper Neutralization of Special Elements used in an OS Command)

The Vulnerability Explained

The vulnerable code pattern was straightforward:

// Receive user-controlled CLI argument
var args = process.argv[2] ? ' ' + process.argv[2] : '';

// Concatenate directly into shell command (vulnerable)
execPromise('zola build ' + args)

Here, process.argv[2] is passed by the caller—typically a developer or CI/CD pipeline. If a developer runs:

npm run abridge -- "--base-url https://example.dev"

The argument "--base-url https://example.dev" is benign and reaches the shell tool as intended. But what if the argument is:

npm run abridge -- "; rm -rf /"

Now the concatenated command becomes:

zola build ; rm -rf /

The shell interprets the semicolon as a command separator, executing the rm command after zola build completes. Other metacharacters work similarly:

  • && – command chaining on success
  • || – command chaining on failure
  • `command` or $(command) – command substitution
  • | – piping output to another command
  • >, < – output redirection
  • {} – subshell grouping

An attacker with the ability to control the build invocation—such as a CI/CD integration that accepts external input, a shared build machine, or a developer workstation—could exploit this to execute code with the privileges of the build process. In many environments, that means read/write access to the repository, credentials stored in environment variables, or access to deployment infrastructure.

The Fix

The fix introduces a simple but effective whitelist validation:

if (process.argv[2] && /[;&|`$(){}<>\\\n]/.test(process.argv[2])) { 
  throw new Error('ERROR: unsafe characters detected in CLI argument!'); 
}
var args = process.argv[2] ? ' ' + process.argv[2] : '';

Before the argument is concatenated into any shell command, the regex pattern checks whether the string contains any of the shell metacharacters: ;, &, |, `, $, (, ), {, }, <, >, \, or newline. If any are present, the script terminates with an error and never reaches the execPromise() call.

This approach is effective because:

  1. Early rejection – The validation happens before any shell command is constructed, eliminating the window for injection.
  2. Explicit enumeration – The regex targets the exact characters that have special meaning in shell syntax. Legitimate arguments like --base-url https://example.dev or --output-dir ./dist pass through without issue.
  3. Fail-safe – If an argument is rejected, the build terminates with a clear error message rather than silently executing unexpected commands.

The fix preserves the original behavior for safe arguments while blocking the attack vector entirely.

Key Takeaways

  • Never concatenate user-controlled input into shell command strings, even if you believe the input is "trusted" or comes from a "safe" source. Build arguments are often controlled by CI/CD systems or automation, which expand the attack surface.

  • Shell metacharacters are a closed set. When passing arguments to shell commands, validate against the explicit set of characters that have special meaning: ;, &, |, `, $, and others. A regex pattern like the one in this fix is more reliable than trying to escape characters (which is error-prone and context-dependent).

  • Command injection in build scripts has elevated impact. Build processes run with developer or service account privileges and often have access to credentials, source code, and deployment systems. An injection here is not limited to user-facing data corruption—it can compromise the entire CI/CD pipeline.

  • Test your argument parsing with adversarial input. If your build script accepts CLI arguments, include test cases that attempt injection: "; echo hacked", $(whoami), and similar payloads. Your tests should verify that these are either rejected or safely escaped.

  • Prefer structured argument passing over shell concatenation. Where possible, use argument arrays instead of string concatenation when invoking child processes. Node.js tools like child_process.execFile() can accept arguments as an array, eliminating the shell parsing step entirely.

How Orbis AppSec Detected This

Source: CLI argument passed via process.argv[2] without validation.

Sink: The args variable, containing the unsanitized argument, concatenated into a shell command string passed to execPromise().

Missing control: No validation or sanitization of the CLI argument before use in shell command construction.

CWE: CWE-78 – Improper Neutralization of Special Elements used in an OS Command.

Fix: A regex-based whitelist validation rejects any CLI argument containing shell metacharacters before the argument is used in command construction.

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

Command injection in build scripts is a high-impact vulnerability that can easily go unnoticed during code review, especially if the script has been in use for some time without incident. The fix here—a simple regex validation applied at the entry point—is minimal, maintainable, and eliminates the attack vector without breaking legitimate use cases.

The key lesson is that build orchestration code is not exempt from input validation. Any script that accepts external input and uses it to construct system commands must validate that input, regardless of how "internal" or "trusted" the source seems. In this case, a few characters in a regex pattern prevented a complete compromise of the build pipeline.

Prevention and further reading

Frequently Asked Questions

What metacharacters does the validation reject, and why those specifically?

The fix rejects `;`, `&`, `|`, `` ` ``, `$`, `(`, `)`, `{`, `}`, `<`, `>`, `\`, and newlines—these are the shell syntax operators that enable command chaining, redirection, variable expansion, and subshell execution. Any one of them can break out of the intended argument context.

Does the build script still accept legitimate multi-word arguments after the fix?

Yes. Arguments like `--base-url https://example.dev` pass validation because they contain no metacharacters. The rejection is strict to the shell syntax operators only, not whitespace or other printable characters.

Can an attacker bypass this validation with URL encoding or escape sequences?

No. The validation runs on the raw process.argv[2] value *before* any shell processing, so URL encoding or backslash escapes arrive as literal characters (e.g., `%3B` or `\;`) and do not match the regex pattern.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #241

Related Articles

critical

shell-quote 1.8.3: Line Terminator Command Injection (CVE-2026-9277)

CVE-2026-9277 is a critical command injection vulnerability in shell-quote versions before 1.9.0, where unescaped line terminators allow attackers to break out of quoted strings and execute arbitrary shell commands. The fix upgrades the dependency across multiple React Native CLI packages and related libraries through npm overrides.

critical

Voice Assistant Command Injection via os.system() f-String

A critical command injection vulnerability in a voice assistant's audio playback handler allowed attackers to execute arbitrary shell commands by manipulating file paths passed to os.system(). The fix replaces shell invocation with subprocess calls and direct OS APIs, eliminating shell metacharacter interpretation entirely.

critical

How Command Injection happens in Node.js and how to fix it

A critical command injection vulnerability in `scripts/sync-skill.mjs` allowed attackers to execute arbitrary commands through malicious command-line arguments. The fix implements strict whitelist validation on `process.argv` inputs, ensuring only the `--check` flag is accepted before any shell interaction occurs.

high

How shell injection happens in GitHub Actions workflows and how to fix it

A composite GitHub Action in `.github/actions/design-health/action.yml` interpolated `inputs.path`, `inputs.verbose`, and other values directly into `run:` shell scripts using `${{ ... }}` syntax. Because these values are substituted as raw text before the shell ever runs, an attacker-influenced input could inject arbitrary shell commands into the CI runner. The fix moves every interpolated value into `env:` blocks so the shell treats them as data, not code.

high

How command injection happens in Java ProcessBuilder and how to fix it

The `efw` framework exposes OS command execution to application code through `CmdManager.execute(String[] params)`, which passed its parameter array straight into `new ProcessBuilder(...)` at `CmdManager.java:25` with no validation and no documented trust boundary. Because `params` is commonly assembled in event JavaScript from HTTP request parameters — often via string concatenation — the call site was a ready-made command and argument injection primitive. The fix adds explicit parameter valida

high

fs.readFileSync(process.argv[2]) Path Traversal in Zola Build

A build-time helper that extracts the expected SHA-256 for a downloaded Zola release passed `process.argv[2]` straight into `fs.readFileSync()` with no directory constraint, so any caller able to influence that argument could make the integrity check read an arbitrary file. The fix resolves the requested path and requires it to be a direct child of the tools directory, which is now passed in as an extra argument, and exits with an error otherwise. Because the bytes read become the "expected" che