fix(agent): shell-escape paths interpolated by grep/file_glob/is_file_path helpers (#11132)#11391
Conversation
…_path helpers (warpdotdev#11132) The internal helpers behind the AI agent's `grep` and `file_glob` tools built shell commands by interpolating an agent-supplied path inside double quotes and handing the result to `Session::execute_command`. POSIX double quotes still expand `$VAR`, backticks, and `$(...)`; PowerShell double quotes still expand `$x` and `$env:USERPROFILE`. A path containing any of those metacharacters was therefore parsed (and potentially executed) by the user's shell before the underlying tool received it — e.g. `is_file_path("/tmp/innocent$(touch ~/PROBE_RAN)")` would run `touch` as a side effect of the existence probe. Fixes warpdotdev#11132. Eight call sites updated: * `execute.rs` — `is_file_path` (POSIX + PowerShell), `is_git_repository` * `execute/grep.rs` — `run_git_grep_command`, `run_grep_command`, `run_select_string_command` * `execute/file_glob.rs` — `run_find_command`, `run_powershell_get_childitem_command` Each path now flows through `ShellFamily::shell_escape` (the same primitive used by the recently-fixed cd / open-folder / cli-install paths), so a single literal word reaches the underlying `test -f` / `Test-Path` / `git -C` / `grep` / `find` / `Get-ChildItem` / `Select-String` regardless of any metacharacters in the path. The query escaping (`escape_double_quotes` / `powershell_escape_double_quotes`) is intentionally left alone — queries are regex literals with their own escaping concerns, separate from path-as-shell-word handling. To keep the change reviewable, each helper grew a pure `build_*_command(...)` extraction that takes the inputs and a `ShellFamily` / `ShellType` and returns the rendered command. The async wrappers are thin; the builders are unit-tested without a live `Session`. Tests: 21 new unit tests across three sibling `*_tests.rs` files cover POSIX backslash-escaping (spaces, `$(...)`, backticks, `~` mid-path), PowerShell backtick-escaping (spaces, `$env:USERPROFILE`), empty-path handling, and pin the security boundary that `$(...)` and backtick command substitution can never survive into the rendered command. ``` $ cargo nextest run -p warp -E 'test(path_quoting::) or test(file_glob::tests::)' 21 passed; 0 failed $ cargo clippy -p warp --tests -- -D warnings clean $ cargo fmt -p warp -- --check clean ``` Shell-level before/after (the security boundary this fixes): ``` $ bash -c 'test -f "/tmp/innocent$(touch /tmp/PROBE_RAN)"' $ ls /tmp/PROBE_RAN /tmp/PROBE_RAN ← touch ran via command substitution (BUG) $ bash -c 'test -f /tmp/innocent\$\(touch\ /tmp/PROBE_RAN\)' $ ls /tmp/PROBE_RAN ls: /tmp/PROBE_RAN: No such file or directory ← shell-escaped: no side effect (FIX) ``` Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
I'm starting a first review of this pull request. You can view the conversation on Warp. I completed the review and no human review was requested for this pull request. Comment Powered by Oz |
There was a problem hiding this comment.
Overview
This PR switches several grep/file_glob/is_file_path shell command builders from double-quoted path interpolation to ShellFamily::shell_escape and adds unit coverage for the extracted builders. No approved spec context was available for comparison.
Concerns
- 🚨 [CRITICAL] [SECURITY]
file_globstill routes git-repository searches throughrun_git_ls_files_command, which buildsgit ls-files ... -- '{target_path/...}'from the rawtarget_path. Becauserun_file_globchooses this branch before the newly escapedfind/Get-ChildItembuilders whenever the target is in a git repo, a directory path containing a single quote plus shell substitution can still break out of the quoted pathspec and execute in the user's shell. Please apply the same argument-safe escaping to the git-backed file_glob path/pattern arguments and add a regression test for a git-repo path containing quotes/command substitution.
Security
- Critical path command injection remains in the git-backed
file_globimplementation.
Verdict
Found: 1 critical, 0 important, 0 suggestions
Request changes
Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).
Powered by Oz
…eview) `run_git_ls_files_command` wrapped each joined `<target_path>/<pattern>` in literal single quotes (`'...'`). A target_path containing a single quote closed the wrapper and let any following metacharacters parse as shell input — a real command-injection vector once the agent's path contained `'$(...)'` or backticks. Oz flagged this on PR warpdotdev#11391: > [CRITICAL] [SECURITY] file_glob still routes git-repository searches > through run_git_ls_files_command, which builds > `git ls-files ... -- '{target_path/...}'` from the raw target_path. Extract the command into `build_git_ls_files_command` (matching the pure-builder pattern of the other helpers in this PR) and run each pattern arg through `ShellFamily::from(shell_type).shell_escape` instead of single-quoting. The shell consumes the escapes and hands `git ls-files` the literal pathspec, so glob characters in the pattern (e.g. `*.rs`) still survive for git's own matching. This also fixes the previously-broken PowerShell variant, which was shipping POSIX-only single-quoting on Windows. Adds 6 regression tests covering: spaces, embedded single quote + command substitution (the exact injection shape), command substitution alone, backticks, PowerShell-shaped paths, and the top-level + subdir doubled pattern arg. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
Thanks for catching that — you're right, the git-backed branch was wrapping each Addressed in
Added 6 regression tests in
13/13 in /oz-review |
|
I'm re-reviewing this pull request in response to a review request. You can view the conversation on Warp. I completed the review and no human review was requested for this pull request. Comment Powered by Oz |
There was a problem hiding this comment.
Overview
This PR extracts command builders and applies ShellFamily::shell_escape to path arguments used by the agent grep, file_glob, is_file_path, and is_git_repository helpers. The path escaping direction matches the reported issue, but the same shell-command construction still interpolates other agent-supplied fields without shell-safe quoting.
Concerns
- Grep queries are still inserted into shell commands inside double quotes after only escaping
\"; POSIX and PowerShell both expand command substitutions / variables in that context. - Non-git file_glob patterns are still inserted into shell commands inside single quotes, so an embedded single quote can break out and execute additional shell syntax.
Security
- Agent-supplied grep queries and file_glob patterns remain shell-injection vectors despite the new path escaping. These inputs should be shell-escaped/quoted for the active shell, or passed without going through shell string interpolation.
Verdict
Found: 2 critical, 0 important, 0 suggestions
Request changes
Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).
Powered by Oz
| fn build_grep_command(queries: &[String], target_path: &str) -> String { | ||
| let mut grep_command = "grep --color=never -nrIHE --devices=skip".to_string(); | ||
| for query in queries { | ||
| grep_command.push_str(format!(" -e \"{}\"", escape_double_quotes(query)).as_str()); |
There was a problem hiding this comment.
🚨 [CRITICAL] [SECURITY] query is agent-supplied and still goes inside double quotes after only escaping \"; POSIX shells expand $(), $VAR, and backticks inside double quotes, so a grep query can execute before grep runs. Render each -e value with shell-family escaping/quoting, or avoid shell string interpolation for query argv construction.
| fn build_find_command(patterns: &[String], target_path: &str) -> String { | ||
| let pattern_args = patterns | ||
| .iter() | ||
| .map(|pattern| format!(" -name '{pattern}'")) |
There was a problem hiding this comment.
🚨 [CRITICAL] [SECURITY] pattern is agent-supplied and embedding it in single quotes lets an embedded ' close the string (for example, x'; touch /tmp/pwn; '), so non-git file_glob can still execute shell input even though the path is escaped. Shell-escape/quote each pattern for the active shell before interpolating it.
…arpdotdev#11132 review) The first round of this PR escaped agent-supplied paths but left other agent-controlled inputs going through unsafe quoting. Oz flagged two remaining injection vectors: > Grep queries are still inserted into shell commands inside double quotes > after only escaping `\"`; POSIX and PowerShell both expand command > substitutions / variables in that context. > Non-git file_glob patterns are still inserted into shell commands inside > single quotes, so an embedded single quote can break out and execute > additional shell syntax. Replace both unsafe quoting strategies with `ShellFamily::shell_escape`: - `build_git_grep_command`, `build_grep_command`, `build_select_string_command`: agent queries now go through `family.shell_escape(query)` instead of `"..."` + `\"`-only escape. The shell consumes the escapes and hands grep/Select-String the literal query for regex matching. - `build_find_command`, `build_get_childitem_command`: agent glob patterns now go through `ShellFamily::shell_escape(pattern)` instead of `'...'` wrapping. The shell consumes the escapes and `find`/`Get-ChildItem` see the literal pattern for their own glob matching. - Dropped the now-unused `escape_double_quotes` and `powershell_escape_double_quotes` helpers in grep.rs. Adds 8 new injection-vector regression tests covering: - `$(...)` in grep queries (POSIX, both git and non-git paths) - backticks in grep queries - `$env:VAR` in grep queries and Select-String patterns (PowerShell) - single quote + `$(...)` in find patterns - `$env:VAR` in Get-ChildItem patterns 97/97 in `ai::blocklist::action_model::execute` pass. clippy + fmt clean. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
Both flagged injection vectors closed in `a2f5cfd9`. Grep queries (POSIX + PowerShell, both
Non-git file_glob patterns (POSIX
Added 8 new injection-vector regression tests:
Existing path tests updated where assertions previously baked in the old 97/97 in /oz-review |
|
I'm re-reviewing this pull request in response to a review request. You can view the conversation on Warp. I reviewed this pull request and requested human review from: Comment Powered by Oz |
Oz no longer requests changes for this pull request after the latest automated review.
There was a problem hiding this comment.
Overview
This PR replaces double-quoted interpolation in the agent grep, file_glob, file-existence, and git-repository shell helpers with the shared shell-family escaping helper, and adds unit coverage for POSIX and PowerShell metacharacter handling.
Concerns
- No blocking correctness, security, or spec-alignment concerns found in the annotated diff. The attached spec context does not define additional implementation commitments.
Verdict
Found: 0 critical, 0 important, 0 suggestions
Approve
Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).
Powered by Oz
Description
The internal helpers behind the AI agent's
grepandfile_globtools build shell commands by interpolating an agent-supplied path inside double quotes and handing the result toSession::execute_command. Inside POSIX"..."the shell still expands$VAR, backticks, and$(...); inside PowerShell"..."it still expands$x(including$env:USERPROFILE) and treats backticks specially. So a path containing any of those metacharacters is parsed — and potentially executed — by the user's shell before the underlying tool sees it.is_file_pathandis_git_repositoryare particularly exposed because they run as side-effects of the grep and file_glob tools (run_grep→is_file_path+is_git_repository,run_file_glob→is_git_repository) before the user-facing command is shown, so even path values the user never typed flow into the shell verbatim.Fixes #11132.
Root cause
Eight unquoted call sites across three files:
app/src/ai/blocklist/action_model/execute.rsis_file_path(POSIX)app/src/ai/blocklist/action_model/execute.rsis_file_path(PowerShell)app/src/ai/blocklist/action_model/execute.rsis_git_repositoryapp/src/ai/blocklist/action_model/execute/grep.rsrun_git_grep_commandapp/src/ai/blocklist/action_model/execute/grep.rsrun_grep_commandapp/src/ai/blocklist/action_model/execute/grep.rsrun_select_string_commandapp/src/ai/blocklist/action_model/execute/file_glob.rsrun_find_commandapp/src/ai/blocklist/action_model/execute/file_glob.rsrun_powershell_get_childitem_commandEach one used
format!("… \"{path}\" …")— a literal double-quoted interpolation. The sister fixes forcd "{path}"(open_repo_folder, conversation-restore) andgit checkout(#10444 / #10639) already use the right primitive:warp_util::path::ShellFamily::shell_escape.Fix
Each helper grew a pure
build_*_command(...)extraction that takes the inputs and aShellFamily/ShellTypeand returns the rendered command. The async wrappers are now thin and call the pure builder. The pure builders are unit-tested without a liveSession.The same shape is applied to all eight sites.
Query escaping (
escape_double_quotes/powershell_escape_double_quotes) is intentionally left alone — queries are regex literals with their own escaping concerns, separate from path-as-shell-word handling.Shell-level before/after
This is the security boundary the new
build_is_file_path_command(and its siblings) enforces.Testing
21 new unit tests across three sibling
*_tests.rsfiles:$(...), backticks,~mid-path, empty-string paths$env:USERPROFILE, drive-letter paths$(...)or backticks may survive into the rendered commandServer API dependencies
None — this is client-only.
Agent Mode
Unchecked (external contribution).
Changelog
CHANGELOG-BUG-FIX: Paths containing shell metacharacters (spaces, $, backticks, $(...), etc.) are now correctly passed through the AI agent's grep / file_glob / file-existence helpers without being expanded by the user's shell.Fixes #11132