fix(agent): stop git_diff and git_commit arguments reaching the shell - #2
Merged
voidstackloop merged 1 commit intoJul 25, 2026
Conversation
gitDiff interpolated the model-supplied path into `git diff -- "<path>"` and gitCommit interpolated the message into `git commit -m "<message>"`. Both strings are executed via `sh -c`, and a POSIX shell still expands `$(...)` and backticks inside double quotes, so either argument could run arbitrary commands. JSON.stringify escapes `"` and `\` but not `$` or backticks, so it is not a shell-quoting function. git_diff is offered as a read-only tool the user can grant "always allow this session", so this turned a standing approval for reading diffs into one for arbitrary execution, with no further prompt. Both values now go through a shared quoting helper in command-sandbox.ts, which is platform-aware: single quotes for POSIX shells, double quotes for cmd.exe (where `'` is not a quote character at all, and where there is no `$(...)` or backtick expansion to defend against). Quoting Windows arguments POSIX-style would have split ordinary paths and messages on their spaces.
voidstackloop
approved these changes
Jul 25, 2026
voidstackloop
added a commit
that referenced
this pull request
Jul 25, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
gitDiffbuildsgit diff -- "<path>"andgitCommitbuildsgit commit -m "<message>", interpolating a model-supplied value into a string thatgitCommand→runCommandhands topromisify(exec), i.e. tosh -c.Double quotes do not make that safe on a POSIX shell: command substitution and backticks are still expanded inside them.
JSON.stringifyescapes"and\but not$or backticks, so it looks like quoting without being shell quoting.The reason this is worth a patch rather than a note:
git_diffis classified read-only in the renderer (READ_ONLY_TOOLS,Chat.tsx), which is what makes the "Always allow this session" button appear for it. Granting that — a reasonable thing to do during a code-review task — converts a standing approval to read diffs into a standing approval to execute, with no further prompt. Agent mode reads file contents and web pages, so the value reachingpathis not necessarily something the user typed.Severity varies by platform: on Linux with a working bubblewrap, or on macOS, the command is confined to the workspace with network denied. Where no sandbox mechanism is available — Windows, and Linux where bubblewrap can't create user namespaces (the AppArmor restriction
command-sandbox.tsalready documents) — it is unconfined.findDangerousCommandReasonsees the assembled string and catches some shapes, but it is a keyword blocklist and has no notion of substitution.Fix
Both values now go through a shared quoting helper, exported from
command-sandbox.tsrather than duplicated.The helper is platform-aware, which matters here:
sh— single quotes, embedded quote written as'\''.cmd.exe— double quotes, embedded quote written as"". Single quotes are not quote characters tocmd.exe, so quoting Windows arguments POSIX-style would have split ordinary paths and commit messages on their spaces.cmd.exealso has no command substitution to defend against.applySandboxpasses itsplatformargument through, so the existing wrapper-folding behaviour is unchanged.Tests
Added to
agent-tools.test.ts(git tools) andcommand-sandbox.test.ts:git_diffwith a path argument containing a substitution — asserts the side effect does not happen.git_commitwith a message containing a substitution — same.git_commitwith a message containing$, backticks, single and double quotes — asserts the whole subject survives intact, so the quoting can't pass by mangling the input.shellQuoteunit tests for both platforms, using the injectable-platformpattern already used in this file.Control experiment: the two injection tests fail on
main(the marker files are created) and pass with the patch.npx tsc -p tsconfig.json --noEmitclean;npm test232 passed.