Repository navigation
fix(fix): build --fix argv without splitting workspace paths on && - #1387
Conversation
Closes OWASP#1385 --fix rebuilt package-manager argv by joining plan strings with && and splitting them again before spawn. A workspace directory whose relative path contained && became a second executable. Build install and parent-update apply argv from structured fields so workspace tokens stay one argument. Quote human-facing plan strings for safe copy-paste, and rebuild parent-update plan text after workspace merges so the printed plan matches what --fix spawns.
There was a problem hiding this comment.
🔵 Needs a closer look
The security-sensitive execution rewrite covers four package managers, but the added execution regressions cover only npm, warranting final human validation.
No open issues 🎉
What changed in this PR
Addresses #1385 by separating executable arguments from human-facing fix commands, preventing workspace paths containing && from becoming extra commands.
Changes:
- Builds
--fixargument arrays directly from structured targets for npm, pnpm, yarn, and bun. - Quotes plan output and rebuilds parent-update commands from merged workspace data.
- Adds regression tests for workspace paths containing
&&and spaces.
| File | Description |
|---|---|
tests/fix-runner-apply.test.ts |
Checks exact npm arguments and prevents extra executable invocations. |
tests/fix-commands-shell-quote.test.ts |
Verifies workspace quoting in displayed commands. |
src/utils/fix-runner.ts |
Replaces command-string splitting with structured argument construction. |
src/remediation/fix-commands.ts |
Quotes display tokens and preserves parent-update workspace context. |
Review effort: Balanced
sonukapoor
left a comment
There was a problem hiding this comment.
Approving, and this one is worth spelling out, because it is command execution rather than cosmetics.
A human-readable command string was being joined together and then taken apart again by splitting on the && separator, with each piece spawned. So a workspace whose directory name contains that separator stops being one command and becomes two, and the second one runs. A directory name is not trusted input: it can arrive with a repository someone else wrote.
I built a workspace whose member is named with the separator and an injected command, confirmed we emit a workspace-scoped command for it, then pasted that command into a real shell. On main the stub standing in for the package manager receives three arguments and the injected command announces itself as having run. On your branch every payload I tried, including an embedded single quote, a command substitution and backticks, arrives as one intact argument with the path whole.
Ordinary output is unchanged. I compared the printed commands for a tree with nothing unusual in its paths and they are identical to main, which matters because the quoting is conditional rather than applied to every token.
Merge result clean against current main at 178 suites and 2,470 tests. Two mutations: neutering the quoting fails 2 of 13 tests, and restoring the old split in the apply path fails 4 of 11.
One limit worth stating rather than leaving implied. The copy-and-paste half is proven end to end, in a shell, with a real payload. The spawn half is proven at unit level only, because every workspace-scoped target my fixtures produced turned out to be a kind that --fix deliberately skips. The project's own pre-existing test models the kind that does get applied, so the path is covered, but I did not reach it from a real tree.
|
@coderabbitai review |
✅ Action performed
|
Walkthrough
Merge Risk:
|
There was a problem hiding this comment.
🔇 Additional comments: 1
src/remediation/fix-commands.ts: 1
558-594:Trivial · Maintainability & Code Quality · Heavy lift
⚠️ Unverified finding
Verification ran but could not confirm this finding. It is shown for review, not as a verified issue.Recommended: derive the display strings and the
--fixargv from one structured builder.
fix-commands.tsbuilds display strings withbuildNpmUpdateCommand,buildPnpmLockfileRefreshCommand,buildYarnUpgradeCommandandbuildWorkspaceInstallCommands.fix-runner.tsbuilds the matching argv inbuildInstallApplyArgvandbuildParentUpdateApplyArgv. The two sets of logic encode the same rules: the"."handling, the pnpm recursive fallback, the npm--workspacesfallback, and the flag order.These rules can drift apart. The comment on Line 474 already notes that the printed plan must match what
--fixspawns, but no test enforces it. One builder that returnsstring[][]would remove the problem. The display layer would then join the argv elements and applyshellQuoteto each one.

Review info
-
Run configuration-
- Source mode: Auto
- Repository: Unavailable
- Branch: Unavailable
- File: No configuration file from this repository applied
- Commit: Unavailable
- Layers: defaults
- Review profile: CHILL
- Plan: Advanced
- Run ID:
c0ef330b-8fb0-48eb-9ef9-3554d34b8b7c
Files selected for processing: 4-
src/remediation/fix-commands.tssrc/utils/fix-runner.tstests/fix-commands-shell-quote.test.tstests/fix-runner-apply.test.ts
Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 2 remain after this review.
-
|
Merged - thank you @alex-powell! |
Fixes #1385
Problem
Under
--fix, package manager commands were built as shell-shaped strings and then re-split on&&and whitespace beforespawn. A workspace directory whose path contains&&(for examplepkgs/evil&&touch PWNED) was split into a second argv list, so--fixspawned an extra executable taken from the path name.Fix
src/utils/fix-runner.ts: build apply argv directly from structured target fields (package, version, workspaces, package manager) for npm, pnpm, yarn and bun installs and parent-updates. The&&re-split oftarget.commandis gone, and each workspace path is passed as one argv element tospawn(still no shell).src/remediation/fix-commands.ts: shell-quote workspace paths and package specs in the human-facing plan strings so a pasted command stays one shell word. Yarn parent-update plan text now matches the argv used by--fixafter workspace merge.Tests
&&and spaced&&workspace paths (direct install and parent-update). On main these produce an extra["touch", "PWNED"]spawn; on this branch they produce a single package manager spawn.tests/fix-commands-shell-quote.test.ts.tsc --noEmitclean.Summary by CodeRabbit