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
•Technically reviewed by Anupam Mediratta•Published September 17, 2026•Reviewed September 24, 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

webapp-testing Server Script: Shell Injection via Unquoted Command

A critical command injection vulnerability in a web server testing utility allowed attackers to execute arbitrary shell commands by injecting metacharacters into command-line arguments. The fix removes shell interpretation and safely tokenizes user-supplied commands using `shlex.split()`, while preserving support for legitimate directory-change workflows.

high

THiNX Device Platform: Command Injection via source.owner in Git Fetch

The THiNX device management platform fixed a critical vulnerability where the `source.owner` parameter flowed directly into shell commands during git repository operations. An attacker with source configuration access could inject arbitrary shell commands by providing malicious owner values containing metacharacters like `; curl`.

critical

backtest.js CLI args: Unvalidated --days and --start Options

The backtest.js command-line tool accepted --days and --start arguments without validation, creating a command injection vector when the script is invoked by upstream processes with untrusted input. The fix adds strict input validation: --days must be a positive integer ≤ 3650, and --start must match the YYYY-MM-DD date format.

critical

Tauri type_text() Command Injection via Control Characters in

The type_text() system command handler in Tauri accepted arbitrary text up to 2000 UTF-16 characters without validating content, allowing injection of control characters that SendInput's Unicode path interprets as real key presses. An attacker could inject newline, escape, or tab characters to trigger actions in the focused window beyond mere text typing.

high

runStreaming() Command Injection: Defense-in-Depth for Electron Child

An Electron application's `runStreaming()` utility accepted a command string and argument array without validating either, creating a latent command injection vector. The fix adds strict type checking and a whitelist regex that rejects shell metacharacters, bounding the failure mode even if caller input becomes attacker-influenced.

critical

docker_rpc.uc Command Injection: Unsanitized RPC Parameters

A critical command injection vulnerability in the Docker RPC handler allowed authenticated attackers to execute arbitrary system commands by injecting shell metacharacters into container ID, port, user ID, or command parameters. The fix validates all user-supplied inputs against strict whitelist patterns before interpolating them into shell commands.