Skip to content

Detect chained string methods on interpolated commands in exec/child_process rules - #52707

Open
pelikhan with Copilot wants to merge 5 commits into
mainfrom
copilot/fix-no-exec-interpolated-command
Open

Detect chained string methods on interpolated commands in exec/child_process rules#52707
pelikhan with Copilot wants to merge 5 commits into
mainfrom
copilot/fix-no-exec-interpolated-command

Conversation

Copilot AI commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

no-exec-interpolated-command and no-child-process-interpolated-command only recognized a bare interpolated template literal or dynamic + concatenation as the command argument, so a routine normalization step defeated both rules:

exec.exec(`git checkout ${branch}`.trim(), []);          // was not flagged
execSync(`git log --author=${author}`.toLowerCase());     // was not flagged

The argument node is a CallExpression, not a TemplateLiteral, and resolveWriteOnceInitializerChain only unwraps identifier indirection.

Changes

  • Shared helpers — moved the independently duplicated isStaticExpression / isDynamicStringConcatenation / getDynamicCommandKind trio into command-initializer-utils.ts; both rules now call getDynamicCommandKind(expr, sourceCode), which applies the write-once initializer resolution internally.
  • Unwrap string transformsgetDynamicCommandKind recursively descends through calls to trim, trimStart, trimEnd, toLowerCase, toUpperCase, toLocaleLowerCase, toLocaleUpperCase, replace, replaceAll, and normalize, resolving identifiers at each step. The reported kind is the underlying shape (interpolated template literal / dynamic string concatenation), so existing message data is unchanged.
  • Arguments count too — methods like .replace() can inject dynamic content via their replacement argument, so arguments are inspected as well and isStaticExpression treats a transform call as static only when the receiver and all arguments are static. This also preserves the previous behavior where "a".replace(x) + "b" is dynamic.
  • Tests — invalid cases in both rule suites for .trim()- and .toLowerCase()-chained templates, chained dynamic concatenation, identifier-then-.trim(), and a dynamic .replace() argument; valid cases assert chained methods on fully static strings/concatenations stay unflagged.
  • Docs — README entries for both rules note the chained-method unwrapping and the static-receiver exemption.

Note: the 5 failing tests in require-fs-io-try-catch.test.ts predate this branch and are untouched here.


Generated by 👨‍🍳 PR Sous Chef · gpt54 · 7.2 AIC · ⌖ 7.73 AIC · ⊞ 8.5K ·
Comment /souschef to run again

Copilot AI and others added 2 commits August 14, 2026 12:04
…process rules

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
…ments are flagged

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix chained method call on interpolated command Detect chained string methods on interpolated commands in exec/child_process rules Aug 14, 2026
Copilot AI requested a review from pelikhan August 14, 2026 12:07
@github-actions

Copy link
Copy Markdown
Contributor

PR Triage\n\n- Category: feature\n- Risk: low\n- Priority: medium\n- Score: 47/100 (impact 30 + urgency 10 + quality 7)\n- Recommended action: batch_review\n

Generated by 🔧 PR Triage Agent · auto · 62.8 AIC · ⌖ 2.76 AIC · ⊞ 7.8K ·

@github-actions

Copy link
Copy Markdown
Contributor

👋 Great work on the eslint-factory improvements! This PR looks ready for review.

The refactoring cleanly centralizes command detection logic (getDynamicCommandKind) to handle chained string methods (.trim(), .toLowerCase(), .replace(), etc.) that were previously defeating the security checks. The test coverage is thorough—both valid static cases (chained methods on fully static strings) and invalid dynamic cases (chained methods applied to interpolated templates or dynamic concatenations) are well represented.

The changes:

  • ✅ Move shared helpers to command-initializer-utils.ts to avoid duplication
  • ✅ Extend detection to unwrap string transforms and inspect their arguments
  • ✅ Update documentation to reflect the new behavior
  • ✅ Add comprehensive test cases for both rules

This aligns with the project's agentic development process (core team using Copilot agent) and adds genuine security value by catching previously-missed edge cases.

Generated by ✅ Contribution Check · auto · 55.5 AIC · ⌖ 4.15 AIC · ⊞ 8.8K ·

@pelikhan
pelikhan marked this pull request as ready for review August 14, 2026 16:13
Copilot AI balanced review requested due to automatic review settings August 14, 2026 16:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Extends command-injection lint rules to detect interpolated commands wrapped in common string transformations.

Changes:

  • Centralizes dynamic-command analysis in shared utilities.
  • Recursively analyzes chained string methods and their arguments.
  • Adds rule tests and documentation.
Show a summary per file
File Description
command-initializer-utils.ts Adds shared recursive command analysis.
no-exec-interpolated-command.ts Uses the shared analyzer.
no-exec-interpolated-command.test.ts Tests transformed exec commands.
no-child-process-interpolated-command.ts Uses the shared analyzer.
no-child-process-interpolated-command.test.ts Tests transformed child-process commands.
README.md Documents recognized chained methods.

Review details

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

Suppressed comments (2)

eslint-factory/src/rules/command-initializer-utils.ts:125

  • Function-valued replacements remain an escape: exec.exec("git X".replace("X", () => ${branch}), []) is not reported because getDynamicCommandKind receives an ArrowFunctionExpression and returns null, although its interpolated return value becomes command text. Inspect return expressions of replacement callbacks for replace/replaceAll.
    const argKind = getDynamicCommandKind(arg, sourceCode, seen);

eslint-factory/README.md:722

  • This supported-method list omits toLocaleLowerCase and toLocaleUpperCase, although both are included in STRING_TRANSFORM_METHODS, so users cannot tell that these calls are also inspected.
- `` exec.exec(`git checkout ${branchName}`.trim()) `` — chained string-normalizing methods (`trim`, `trimStart`, `trimEnd`, `toLowerCase`, `toUpperCase`, `replace`, `replaceAll`, `normalize`) are unwrapped before the check.
  • Files reviewed: 6/6 changed files
  • Comments generated: 2
  • Review effort level: Balanced

Comment on lines +123 to +126
for (const arg of transform.args) {
if (arg.type === AST_NODE_TYPES.SpreadElement) continue;
const argKind = getDynamicCommandKind(arg, sourceCode, seen);
if (argKind) return argKind;
Comment thread eslint-factory/README.md Outdated
- `execFileSync("git " + branch, ["status"], { shell: true })` — shell-enabled execFileSync.
- `spawn("git checkout " + branch, ...opts)` — spread options are treated conservatively as potentially shell-enabled.
- ESM imports are recognized (`import { execSync } from "node:child_process"`).
- `` execSync(`git checkout ${branch}`.trim()) `` — chained string-normalizing methods (`trim`, `trimStart`, `trimEnd`, `toLowerCase`, `toUpperCase`, `replace`, `replaceAll`, `normalize`) are unwrapped before the check.
@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot Please address the current review feedback and then run the pr-finisher skill.

Open review feedback to resolve:

  • Review summary from copilot-pull-request-reviewer flagged a missed dynamic-command case in replace/replaceAll callback returns.
  • Documentation should mention toLocaleLowerCase and toLocaleUpperCase if those methods are intentionally supported.

After updates, summarize what changed and any remaining blockers for maintainers.

Generated by 👨‍🍳 PR Sous Chef · gpt54 · 7.2 AIC · ⌖ 7.73 AIC · ⊞ 8.5K ·
Comment /souschef to run again

… case methods

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>

Copilot AI commented Aug 14, 2026

Copy link
Copy Markdown
Contributor Author

@copilot Please address the current review feedback and then run the pr-finisher skill.

Open review feedback to resolve:...

Addressed in 3ba... (latest push):

  • getDynamicCommandKind now also inspects .replace()/.replaceAll() replacer callback return values (arrow expression body or return statements not crossing a nested function boundary), so "cmd PLACEHOLDER".replace("PLACEHOLDER", () => branch) is now flagged.
  • README entries for both rules now list toLocaleLowerCase/toLocaleUpperCase alongside the other chained methods.

No remaining blockers from these items. The 5 pre-existing require-fs-io-try-catch.test.ts failures noted in the PR description are unrelated and untouched.

@github-actions

Copy link
Copy Markdown
Contributor

PR Triage

  • Category: feature
  • Risk: medium
  • Priority: medium (score: 44/100)
  • Score breakdown: impact 20/50, urgency 10/30, quality 14/20
  • Recommended action: batch_review
  • Batch: small-features-tests-cluster

Automated triage by PR Triage Agent.

Generated by 🔧 PR Triage Agent · auto · 64.3 AIC · ⌖ 2.94 AIC · ⊞ 7.8K ·

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

Projects

None yet

4 participants