Skip to content

# Command Injection Reproduction Notes for next-remote-watch.js - #36

Open
DongZifan wants to merge 1 commit into
pycoder2000:masterfrom
DongZifan:master
Open

DongZifan wants to merge 1 commit into
pycoder2000:masterfrom
DongZifan:master

Conversation

@DongZifan

Copy link
Copy Markdown

Summary

Hello,
I am writing to report a potential OS Command Injection vulnerability in the following file:
next-remote-watch.js

The issue appears when the changed file path (filePathContext) and event name (eventContext) are interpolated into a user-provided --command template and the resulting string is executed via spawn(shell, ['-c', ...]). Under these conditions, a file whose name contains shell metacharacters (such as ;, &, |, or $()) can escape the intended command context and execute arbitrary code on the host system. This poses a significant security risk as it allows for Remote Code Execution (RCE).

Root Cause

The issue is caused by unsanitized interpolation of attacker-influenced values into a shell command in the following function:

const shell = process.env.SHELL;

// ...

if (program.command) {
    // Use spawn here so that we can pipe stdio from the command without buffering
    spawn(
      shell,
      [
        '-c',
        program.command
          .replace(/\{event\}/gi, filePathContext)   // changed file path
          .replace(/\{path\}/gi, eventContext),       // event name
      ],
      {
        stdio: 'inherit',
      },
    );
}

Because spawn(shell, ['-c', command]) runs the command through the system shell, any shell metacharacters present in the interpolated filePathContext (or eventContext) are interpreted as command syntax rather than literal text.

Reproduction Material

A minimal reproduction script is provided in: poc_next-remote-watch.js
poc_next-remote-watch.js

This Proof of Concept (PoC) is intended to demonstrate that a malicious file path can reach dangerous command execution logic through the vulnerable code path.

What the PoC Does

The PoC performs a minimal end-to-end trigger of the vulnerable code path:

  1. It hooks child_process.spawn to log intercepted commands before they execute.
  2. It loads next-remote-watch.js (with its optional dependencies stubbed for a self-contained run).
  3. It captures the chokidar event callback registered by the file.
  4. It invokes that callback with a crafted file path README.md; calc # and the default event name change.
  5. Because --command is set to echo {event} {path}, the file path is substituted into the shell command and passed to spawn(shell, ['-c', ...]), where ; is interpreted as a command separator and calc is executed as a second command.

In the provided example, the injected payload is crafted so that successful command execution launches the Calculator application. This serves as a visible indicator that external input can reach the OS command execution sink without proper sanitization.

Example Payload

The PoC uses the following malicious file path for the changed file:

README.md; calc #

How to Run

Run from the folder containing the files:

node poc_next-remote-watch.js

Expected Output

When the PoC runs, you should see output similar to:

[POC] Using shell: ...
[POC] Triggering watcher event with file path: README.md; calc #

[POC] child_process.spawn() would run:
>> FILE : /bin/sh
>> ARGS : ["-c","echo README.md; calc # change"]
>> SHELL COMMAND : echo README.md; calc # change
[POC] >>> INJECTION CONFIRMED: "calc" reached the shell command

[POC] The injection instruction has been delivered to the operating system

In the vulnerable version, when the crafted file path is processed, the shell interprets the ; metacharacter and executes the injected calc command, launching the Calculator application as a benign demonstration effect.
This shows that an attacker-controlled file path can influence command execution behavior through the vulnerable --command path.

Patch Explanation

This branch also includes a patched version of next-remote-watch.js (new_next-remote-watch.js) intended to mitigate the command injection risk described above.

What the patch changes

The patch introduces shell escaping for the interpolated values so that shell metacharacters in filePathContext and eventContext are treated as literal text rather than command syntax, while preserving the existing --command feature for legitimate callers.

Validation introduced by the patch

A shellQuote helper is added to escape each interpolated value:

function shellQuote(value) {
  return "'" + String(value).replace(/'/g, "'\\''") + "'";
}

The command construction now escapes both interpolated values before substitution:

program.command
  .replace(/\{event\}/gi, shellQuote(filePathContext))
  .replace(/\{path\}/gi, shellQuote(eventContext))

Before the patch, a file path such as README.md; calc # would be interpreted by the shell as two commands (echo README.md and calc). After the patch, the value is wrapped in single quotes (with embedded single quotes escaped), so the shell treats the whole path as a single literal argument. Shell metacharacters such as ;, &, |, $(), and ` are no longer interpreted, while legitimate file paths (including those containing spaces) continue to work as expected.

@vercel

vercel Bot commented Nov 4, 2026

Copy link
Copy Markdown

@DongZifan is attempting to deploy a commit to the Parth Desai's projects Team on Vercel.

A member of the Team first needs to authorize it.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant