Skip to content

REPRO-2026-00224: Verified Repro With Script Download

REPRO-2026-00224: Node.js task runner improperly escapes single quotes on Unix, enabling shell syntax breakouts and potential command injection via node --run arguments.

REPRO-2026-00224 is verified against node · nodejs. Affected versions: v22.23.0, v24.17.0, v26.3.1 confirmed vulnerable on Linux x64. --run was added in v22.0.0, older versions without --run are not affected. Windows not tested. Fixed in Commit e76c573e4546ce9e89e0dd954f80aaba32148a48. Vulnerability class: Command Injection. This high reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00224.

REPRO-2026-00224 node · nodejs Command Injection Jul 4, 2026 .txt
Severity
HIGH
Confidence
HIGH
Reproduced in
47m 12s
Tool calls
134
Spend
$3.26
01 · Overview

What Is REPRO-2026-00224?

REPRO-2026-00224 documents a high-severity command-injection issue (CWE-77) in Node.js's task runner. The EscapeShell() routine used by node --run improperly escapes single quotes on POSIX shells, allowing crafted arguments to break out of the quoted context and inject shell syntax. Pruva reproduced it (reproduction REPRO-2026-00224).

02 · Severity & CVSS

REPRO-2026-00224 Severity

REPRO-2026-00224 is rated high severity.

HIGH threat level
Weakness CWE-77 — Improper Neutralization of Special Elements used in a Command ('Command Injection')

High — serious impact or readily exploitable. Prioritize remediation.

03 · Affected Versions

Affected node Versions

node · nodejs versions v22.23.0, v24.17.0, v26.3.1 confirmed vulnerable on Linux x64. --run was added in v22.0.0, older versions without --run are not affected. Windows not tested. are affected.

How to Reproduce REPRO-2026-00224

$ pruva-verify REPRO-2026-00224
or curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00224/artifacts/bundle/repro/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh
Run in a VM or disposable container. This exploits a real vulnerability.
06 · Proof of Reproduction

Proof of Reproduction for REPRO-2026-00224

Remote code execution — reproduced
  • reached the target end-to-end
  • full exploit chain demonstrated
  • on the real production code path
  • high confidence
  • the upstream fix blocks the same trigger
Trigger

Positional arguments passed to `node --run <task> -- <args>` containing a single quote followed by shell metacharacters (e.g. "x';id > MARKER;echo INJECTION_PROVEN #")

Attack chain
  1. node --run
  2. ProcessRunner::ProcessRunner
  3. EscapeShell (replaces ' with \' and wraps in single quotes)
  4. uv_spawn /bin/sh -c with the concatenated command string
  5. shell interprets broken escaping as command syntax
  6. injected id/echo commands execute
Runnable proof: reproduction_steps.sh
Captured evidence: test B fixed attempt1test B fixed attempt2test A fixed attempt1
How the agent worked 343 events · 134 tool calls · 47 min
47 minDuration
134Tool calls
102Reasoning steps
343Events
2Dead-ends
Agent activity over 47 min
Support
14
Hypothesis
2
Repro
289
Judge
34
0:0047:12

Root Cause and Exploit Chain for REPRO-2026-00224

Versions: All Node.js builds that include the task runner (--run flag) and are prior to commit e76c573e4546ce9e89e0dd954f80aaba32148a48. Verified vulnerable on system Node.js v24.18.0.

Node.js' task runner (node --run <task> -- <args>) uses an EscapeShell() routine in src/node_task_runner.cc to safely quote positional arguments before concatenating them into a /bin/sh -c command string. On POSIX systems, the vulnerable code escaped each single quote (') by rewriting it to \' and wrapping the result in single quotes. Because a backslash is literal inside POSIX single-quoted strings, the rewritten ' actually terminates the single-quoted context early, leaving the remainder of the argument to be interpreted as raw shell syntax. An attacker-controlled argument containing a single quote followed by shell metacharacters can break out and inject arbitrary commands. The fix (commit e76c573e4546ce9e89e0dd954f80aaba32148a48) replaces \' with the POSIX-safe sequence '"'"' (close single-quote, double-quote the literal quote, reopen single-quote).

  • Package/component affected: Node.js core — src/node_task_runner.cc, the EscapeShell() function used by node --run.
  • Affected versions: All Node.js builds that include the task runner (--run flag) and are prior to commit e76c573e4546ce9e89e0dd954f80aaba32148a48. Verified vulnerable on system Node.js v24.18.0.
  • Risk level and consequences: High. Any application that forwards user-controlled data as arguments to node --run (e.g., a CI runner, a build tool, or a web backend that shells out to node --run) is vulnerable to command injection — arbitrary command execution with the privileges of the Node.js process. Even without metacharacters, a benign single quote in an argument (e.g., a person's name like "I'm") causes a /bin/sh syntax error, breaking the script entirely (denial of service).

Impact Parity

  • Disclosed/claimed maximum impact: Command injection / code execution via node --run arguments on Unix-like systems.
  • Reproduced impact from this run: Full command injection confirmed. An argument x';id > MARKER;echo INJECTION_PROVEN # causes the id command to execute (writing its output to a marker file) and echo INJECTION_PROVEN to print to stdout — arbitrary attacker-controlled commands run via /bin/sh -c. Additionally, the benign argument "I think therefore I'm" causes a /bin/sh syntax error (DoS / broken argument passing).
  • Parity: full — the claimed code-execution impact was demonstrated through the real node --run CLI entrypoint.
  • Not demonstrated: N/A — code execution was achieved.

Root Cause

In POSIX shells, single-quoted strings preserve every character literally — a backslash has no special meaning inside single quotes. The vulnerable EscapeShell() code:

std::string escaped =
    std::regex_replace(std::string(input), std::regex("'"), "\\'");
escaped = "'" + escaped + "'";

rewrites each ' as \' and wraps in '...'. For input x';id > M #:

  • After replace: x\';id > M #
  • After wrap: 'x\';id > M #'

/bin/sh -c parses 'x\' as a single-quoted string containing x\ (the backslash is literal). The ' after \ closes the single-quote context. The remainder ;id > M # is now unquoted shell syntax: ; is a command separator, id > M executes id with output redirected to M, and # begins a comment that consumes the trailing wrap-quote ' — avoiding any syntax error.

The correct POSIX-safe technique is '"'"' (close the single quote, insert a literal ' via double quotes, reopen the single quote). The fix commit applies this:

std::regex_replace(std::string(input), std::regex("'"), "'\"'\"'");

With the fix, the same input becomes 'x'"'"';id > M #', which /bin/sh parses as a single literal argument x';id > M # with no shell interpretation.

Fix commit: e76c573e4546ce9e89e0dd954f80aaba32148a48 ("src: fix escaping of single quotes in task runner", PR #64089, ref. HackerOne #3817602).

Reproduction Steps

  1. Script: bundle/repro/reproduction_steps.sh
  2. What the script does:
    • Reads bundle/project_cache_context.json to locate the prepared Node.js source cache.
    • Builds (or reuses pre-built) Node.js binaries at the vulnerable commit (e76c573^) and the fixed commit (e76c573).
    • Creates a test project with package.json containing an npm script showargs mapped to echo.
    • Runs two attempts per test for both builds:
      • Test A (DoS): node --run showargs -- "I think therefore I'm" — vulnerable build produces /bin/sh: Syntax error: Unterminated quoted string; fixed build prints I think therefore I'm cleanly.
      • Test B (Command injection): node --run showargs -- "x';id > MARKER;echo INJECTION_PROVEN #" — vulnerable build creates a marker file containing id output and prints INJECTION_PROVEN; fixed build passes the argument literally with no marker file.
      • Test C (Ticket's exact payload): node --run showargs -- "foo' ; id ; '" — vulnerable build produces a syntax error (DoS only, since the trailing unbalanced quote is not commented out); fixed build passes the argument safely.
    • Writes bundle/repro/runtime_manifest.json with runtime evidence.
  3. Expected evidence of reproduction:
    • Marker file (marker_vuln_attempt*.txt) containing uid=... output from the injected id command.
    • Test logs showing INJECTION_PROVEN in stdout for the vulnerable build.
    • Test logs showing /bin/sh: Syntax error: Unterminated quoted string for DoS tests.
    • Absence of marker file and clean literal output for the fixed build (negative control).

Evidence

  • Log file locations:
    • bundle/logs/test_A_vuln_attempt{1,2}.log — DoS test on vulnerable build
    • bundle/logs/test_A_fixed_attempt{1,2}.log — DoS test on fixed build
    • bundle/logs/test_B_vuln_attempt{1,2}.log — Injection test on vulnerable build
    • bundle/logs/test_B_fixed_attempt{1,2}.log — Injection test on fixed build
    • bundle/logs/test_C_vuln_attempt{1,2}.log — Ticket payload on vulnerable build
    • bundle/logs/test_C_fixed_attempt{1,2}.log — Ticket payload on fixed build
    • bundle/logs/binary_info.txt — Node.js binary versions/paths
    • bundle/logs/source_diff.txt — Vulnerable vs. fixed EscapeShell source
    • bundle/repro/artifacts/marker_vuln_attempt*.txt — Proof of injected id execution
  • Key excerpts (pre-verified on system Node.js v24.18.0):
    • Injection: stdout shows ["x\\"] then INJECTION_PROVEN; marker file contains uid=1000(vscode) gid=1000(vscode) groups=1000(vscode),969(969)
    • DoS: /bin/sh: 1: Syntax error: Unterminated quoted string
  • Environment: Linux x86_64, /bin/sh (dash), Node.js built from source at commits e76c573^ (vulnerable) and e76c573 (fixed). System Node.js v24.18.0 also confirmed vulnerable.

Recommendations / Next Steps

  • Suggested fix approach: Apply commit e76c573e4546ce9e89e0dd954f80aaba32148a48 — replace \\' with '\"'\"' in the POSIX branch of EscapeShell(). This is the canonical POSIX-safe single-quote escaping technique.
  • Upgrade guidance: Upgrade to any Node.js release that includes commit e76c573. All versions prior to this commit that ship the --run task runner are affected.
  • Testing recommendations: Add regression tests that pass arguments containing single quotes, semicolons, and comment characters through node --run and verify the shell receives them as literal values. The existing test/cctest/test_node_task_runner.cc EscapeShell unit test should be expanded to cover the '"'"' output and end-to-end --run invocations with shell metacharacters.

Additional Notes

  • Idempotency: The script cleans up marker files before each injection test and recreates the test project from scratch, so repeated runs produce consistent results.
  • Key insight: The ticket's exact payload foo' ; id ; ' only causes a syntax error (DoS) because the trailing wrap-quote ' opens an unterminated single-quoted context, causing /bin/sh to reject the entire command before executing anything. The modified payload x';id > MARKER;echo INJECTION_PROVEN # uses a # comment to consume the trailing wrap-quote, producing a syntactically valid shell command in which the injected id and echo commands execute. This achieves the full code-execution impact claimed by the ticket.
  • Why the # technique works: After the broken \' escaping closes the single-quote context, the # (preceded by whitespace) starts a shell comment that extends to end-of-line, swallowing the wrap's trailing '. The commands before # (id, echo) execute normally.
  • Platform scope: This vulnerability is POSIX-only. The Windows branch of EscapeShell() uses a different escaping strategy (double-quote based) and is not affected.

REPRO-2026-00224 Reproduction Transcript

The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.

Full session Replay every step — scrub the timeline or play it back.

Event 1/40
0:001:06
0:00
session startedaccounts/fireworks/routers/glm-5p2-fast · url-2026-07-04T09:29:54Z · REPRO-20
0:03
0:04
web search
0:05
web search
0:07
0:15
0:16
web search
0:27
0:27
extract_facts
no facts extracted
0:28
0:28
0:28
supportrepro
0:53
0:55
0:56
0:56
0:56
0:57
0:58
0:58
0:58
1:00
1:00
1:00
1:02
1:02
$cd /data/pruva/project-cache/6f7cfbc6-90dc-4b8e-bd75-c0200a6d1bf9/repo && git log --oneline -5 && echo "---FIX COMMIT---" && git log --oneline -1 e76c573e4546ce9e89e0dd954f80aaba32148a48 2>&1 || echo "commit not found locally"
ea60060617 vfs: avoid recursive readdir symlink cycles
f2f241a40f stream: normalize Broadcast.from() byte inputs
0c59d78639 tools: update nixpkgs-unstable to 89570f24e97e614aa34aa9ab1c927b6578a
beb9d2bbfc deps: update nghttp3 to 1.17.0
f96a639edd deps: update googletest to 8b53336594cc52213c6c2c7a0b29194fa896d039
---FIX COMMIT---
e76c573e45 src: fix escaping of single quotes in task runner
1:03
1:06
1:06
08 · How to Fix

How to Fix REPRO-2026-00224

Upgrade node · nodejs to Commit e76c573e4546ce9e89e0dd954f80aaba32148a48 or later.

Coming soon

Step-by-step mitigation and hardening guidance for REPRO-2026-00224 — configuration checks, workarounds where no patch exists, and how to verify you're protected — is on the way.

10 · FAQ

FAQ: REPRO-2026-00224

How does the exploit work?

An argument passed to node --run <script> -- <args> that contains a single quote followed by shell metacharacters breaks out of the single-quoted context once EscapeShell() mis-escapes it, letting the remainder of the argument be interpreted by /bin/sh as shell syntax — causing syntax errors or, with a crafted payload, command injection.

Which Node.js versions are affected, and where is it fixed?

v22.23.0, v24.17.0, and v26.3.1 were confirmed vulnerable on Linux x64 (the --run flag was added in v22.0.0, so earlier versions without it are unaffected; Windows was not tested). The fix is commit e76c573e4546ce9e89e0dd954f80aaba32148a48, which replaces the \\' escape with the POSIX-safe '"'"' sequence.

How severe is this issue?

High severity. Any application, CI runner, or build tool that forwards user-controlled data as arguments to node --run is vulnerable to command injection with the privileges of the Node.js process.

How can I reproduce this Node.js task runner bug?

Download the verified script from this page and run it in an isolated environment against a vulnerable Node.js build (e.g. v24.17.0) on Linux x64. Invoke node --run <script> -- <crafted-arg> where the argument contains a single quote followed by shell metacharacters, and confirm the shell syntax breakout; compare against a build containing the fix commit, which no longer breaks out.
11 · References

References for REPRO-2026-00224

Authoritative sources for REPRO-2026-00224 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.