Skip to content

fix: escape shell arg single quotes#107

Merged
killagu merged 5 commits into
3.xfrom
codex/fix-escape-shell-arg-3x
Jul 15, 2026
Merged

fix: escape shell arg single quotes#107
killagu merged 5 commits into
3.xfrom
codex/fix-escape-shell-arg-3x

Conversation

@killagu

@killagu killagu commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Summary

Backport the escapeShellArg fix to 3.x so embedded single quotes use the POSIX-safe '\'' quoting pattern instead of \', which does not escape single quotes inside POSIX shell single-quoted strings.

Why

The previous output could terminate the quoted argument and allow a following shell metacharacter sequence to execute when callers used the documented child_process.exec() string-concatenation pattern.

Changes

  • Replace the single quote escaping logic with the POSIX standard '\'' sequence.
  • Add a POSIX shell regression test that verifies a payload containing '; echo ...; # remains one argument.
  • Update the helper docs to clarify that this is POSIX shell single-argument escaping and recommend execFile() / spawn() argument arrays where possible.

Validation

  • npm test -- --grep escapeShellArg ran lint successfully, but the script treated escapeShellArg as a file filter and reported no test files.
  • npm run test-local -- test/app/extends/escapeShellArg.test.js passed: 4 passing.
  • git diff --check
  • Direct helper regression with Node + child_process.exec: payload '; echo EGG_SECURITY_INJECTED; # is printed as one argument and does not execute the injected echo.

Note: the first non-escalated test-local attempt failed with sandbox listen EPERM while binding the Egg test app; rerunning the same command with local port permission passed.

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d36da73a-dcf5-49b3-8001-24d70f1c912a

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/fix-escape-shell-arg-3x

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pkg-pr-new

pkg-pr-new Bot commented Jul 15, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/egg-security@107

commit: 73afecc

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the escapeShellArg helper to correctly escape POSIX shell arguments by replacing single quotes with '\'' instead of escaping backslashes and single quotes. It also updates the documentation to clarify its POSIX-only usage and adds corresponding tests. The review feedback suggests removing the node: prefix from the imports to maintain compatibility with older Node.js versions in the 3.x branch, and using assert.strictEqual instead of assert.equal for strict equality checks.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread test/app/extends/escapeShellArg.test.js Outdated
Comment on lines +1 to +3
const assert = require('node:assert/strict');
const { exec: childProcessExec } = require('node:child_process');
const { promisify } = require('node:util');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Since this is a backport to the 3.x branch, it may be run on older Node.js versions (such as Node.js 14 < 14.18.0) that do not support the node: prefix or node:assert/strict. To ensure maximum compatibility across all supported Node.js versions in the 3.x release line, it is safer to use standard require statements without the node: prefix.

Suggested change
const assert = require('node:assert/strict');
const { exec: childProcessExec } = require('node:child_process');
const { promisify } = require('node:util');
const assert = require('assert');
const { exec: childProcessExec } = require('child_process');
const { promisify } = require('util');

Comment thread test/app/extends/escapeShellArg.test.js Outdated
const payload = '\'; echo EGG_SECURITY_INJECTED; #';
const { stdout } = await exec(`printf 'ARG:%s\\n' ${escapeShellArg(payload)}`);

assert.equal(stdout, 'ARG:\'; echo EGG_SECURITY_INJECTED; #\n');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Use assert.strictEqual instead of assert.equal to ensure strict equality comparison, especially when using the standard assert module instead of the strict assertion mode.

Suggested change
assert.equal(stdout, 'ARG:\'; echo EGG_SECURITY_INJECTED; #\n');
assert.strictEqual(stdout, 'ARG:\'; echo EGG_SECURITY_INJECTED; #\n');

@codecov

codecov Bot commented Jul 15, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.29%. Comparing base (8e12a8e) to head (73afecc).
⚠️ Report is 1 commits behind head on 3.x.

Additional details and impacted files
@@           Coverage Diff           @@
##              3.x     #107   +/-   ##
=======================================
  Coverage   97.29%   97.29%           
=======================================
  Files          32       32           
  Lines        1367     1367           
  Branches      344      344           
=======================================
  Hits         1330     1330           
  Misses         37       37           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@killagu killagu marked this pull request as ready for review July 15, 2026 14:44
Copilot AI review requested due to automatic review settings July 15, 2026 14:44
@killagu killagu merged commit 1d48b95 into 3.x Jul 15, 2026
28 checks passed
@killagu killagu deleted the codex/fix-escape-shell-arg-3x branch July 15, 2026 14:44

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR backports a security fix to the escapeShellArg helper on the 3.x line so single quotes embedded in an argument are escaped using the POSIX-safe '\'' pattern, preventing quoted-string termination when callers build child_process.exec() command strings.

Changes:

  • Update escapeShellArg() to escape embedded single quotes using the POSIX-safe quoting sequence.
  • Add a POSIX-only regression test that verifies a payload like '; echo ...; # remains a single argument when executed via /bin/sh.
  • Improve test reliability/cleanup by closing mock apps in after() hooks and by waiting for logs in dta tests; update docs to clarify POSIX-only scope and recommend execFile()/spawn() argument arrays.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
lib/helper/escapeShellArg.js Implements POSIX-safe embedded single-quote escaping for shell single-argument quoting.
test/app/extends/escapeShellArg.test.js Adds a POSIX-only exec-based regression test to ensure injected metacharacters don’t break argument boundaries; improves teardown.
test/fixtures/apps/helper-escapeShellArg-app/app/router.js Updates expected escaped output for the fixture route to match the new POSIX escaping behavior.
README.md Clarifies the helper is POSIX-shell-only and recommends execFile()/spawn() argument arrays.
README.zh-CN.md Same clarification as README.md, in Chinese.
test/utils.test.js Refactors utils.checkIfIgnore tests to create/close apps per test; adds missing app close for isSafeDomain suite.
test/dta.test.js Replaces a platform-specific sleep with a bounded retry helper (waitForLog) and ensures app closure.
test/app/extends/spath.test.js Ensures app is closed in after() and restores mocks.
test/app/extends/sjson.test.js Ensures app is closed in after() and restores mocks.
test/app/extends/sjs.test.js Ensures app is closed in after() and restores mocks.
test/app/extends/helper.test.js Ensures all apps are closed in after() and restores mocks.
test/app/extends/escapeShellCmd.test.js Ensures app is closed in after() and restores mocks.
test/app/extends/cliFilter.test.js Ensures app is closed in after() and restores mocks.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


const str = '' + string;
return '\'' + str.replace(/\\/g, '\\\\').replace(/\'/g, '\\\'') + '\'';
return '\'' + str.replace(/'/g, '\'\\\'\'') + '\'';
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.

2 participants