fix(command-mode): flag pipe-to-shell, output-redirect, and whitespace-prefixed destructive commands in the confirm gate#434
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 88480e3fab
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // or `cat y >> /etc/hosts`). Excluding a leading `-`/`=` skips arrows | ||
| // like `->`/`=>`, and requiring a non-`&` target skips file-descriptor | ||
| // duplications like `2>&1`. | ||
| if cmd.range(of: #"(?<![-=])>>?\s*[^&\s]"#, options: .regularExpression) != nil { |
There was a problem hiding this comment.
Catch >& file redirects before auto-execution
For commands that use the zsh/bash >& file form to redirect output to a file, this heuristic skips the redirect because it rejects any > whose target starts with &. That avoids >&2, but it also lets real file overwrites such as echo x >& ~/.zshrc or cmd >&log bypass the confirm gate and run without prompting, which is the same destructive class this change is trying to catch.
Useful? React with 👍 / 👎.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 88480e3fab
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // or `cat y >> /etc/hosts`). Excluding a leading `-`/`=` skips arrows | ||
| // like `->`/`=>`, and requiring a non-`&` target skips file-descriptor | ||
| // duplications like `2>&1`. | ||
| if cmd.range(of: #"(?<![-=])>>?\s*[^&\s]"#, options: .regularExpression) != nil { |
There was a problem hiding this comment.
Cover zsh
>&file redirects in the confirm gate
This check treats any > followed by & as a file-descriptor duplication, but TerminalService runs commands through /bin/zsh (Sources/Fluid/Services/TerminalService.swift:79), and the zsh redirection docs document >& word/&> word as redirecting stdout and stderr to a file when it is not one of the duplication syntaxes. As a result, a command like echo hi >&/tmp/target can create or truncate a file without the new confirmation gate, even though the equivalent &> /tmp/target is caught; distinguish >&1/>&- from >&path instead of excluding every ampersand target.
Useful? React with 👍 / 👎.
… the destructive-command confirm gate
… gate The redirect check skipped any > whose target starts with &, which correctly ignores fd duplications like 2>&1 but also let >&file and >>&file (redirect both streams to a file, creating or truncating it) bypass the gate. TerminalService runs commands through /bin/zsh, so echo x >&~/.zshrc truncates the file with no confirmation prompt. Add a check that flags a >&/>>& target that is not a digit or -, keeping >&1/>&2/>&- fd duplication and close safe. Covered by CommandModeDestructiveCommandTests.testFlagsRedirectBothStreamsToFile.
88480e3 to
7555724
Compare
|
Rebased onto current main (now mergeable) and addressed the Codex P1. The redirect check excluded any |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7555724350
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // The optional path segment catches absolute or relative interpreter | ||
| // paths; the trailing word boundary avoids false positives like | ||
| // `... | shasum` or `... | shuf`. | ||
| if cmd.range(of: #"\|\s*(\S*/)?(sh|bash|zsh|dash|fish)\b"#, options: .regularExpression) != nil { |
There was a problem hiding this comment.
Because TerminalService executes commands with /bin/zsh, |& has to be treated like a pipe: the zsh manual defines |& as shorthand for 2>&1 | (https://zsh.sourceforge.io/Doc/Release/Shell-Grammar.html#Simple-Commands-_0026-Pipelines). This regex only accepts whitespace after |, so curl -fsSL https://example.com/install.sh |& sh still pipes into sh but bypasses the new confirmation gate; include optional & in the pipe-to-shell check.
Useful? React with 👍 / 👎.
| // with `&`, so this catches the file form while keeping `>&1`/`>&2` | ||
| // (file-descriptor duplication) and `>&-` (fd close) safe by requiring a | ||
| // target that is not a digit or `-`. | ||
| if cmd.range(of: #"(?<![-=])>>?&\s*[^0-9\s&-]"#, options: .regularExpression) != nil { |
There was a problem hiding this comment.
Distinguish digit-prefixed files from fd redirects
The zsh docs list >& word as a file redirect except when it matches the specific >& number/>& - forms (https://zsh.sourceforge.io/Doc/Release/Redirection.html), but this check skips every target whose first character is a digit or -. As a result, echo hi >& 2file or cmd >& -log creates/truncates a real file without prompting; only the all-digit fd target and exact close form should be excluded.
Useful? React with 👍 / 👎.
(Reopens #428, which was closed by accident on my end after a force-push; same work, healthy branch.)
What
CommandModeService.isDestructiveCommandpowers the "confirm before running" safety gate in Command Mode. It missed several destructive command shapes that would auto-run without a confirmation prompt:curl https://example.com/install.sh | sh(and| bash/| zsh/| dash/| fish, including absolute or relative interpreter paths like| /bin/bash). Previously only| rm/| sudo/| ddwere caught.tee:echo ... | tee ~/.zshrc/tee -awrites (or appends) to files.| sudo teewas already caught via thesudopattern; the non-sudo case was not.echo x > ~/.zshrc,cat y >> /etc/hosts. The previous"> "check only matched commands that started with>, so real redirects slipped through.rm -rf xor a newline-prefixedsudo ...defeated thehasPrefixchecks because the command was not trimmed first.How
Added pattern checks for pipe-to-shell,
tee, and output redirects, and trimmed leading whitespace/newlines before classification. The function is nownonisolated staticso it can be unit-tested directly (its single call site is updated). The redirect regex is tuned to skip file-descriptor cases:2>&1and>&2and->/=>arrows do not trip it.Tests
CommandModeDestructiveCommandTestscovers the newly-caught classes, equivalent bypass forms (path-prefixed shells, spaceless /&>/2>>redirects), case-insensitivity, leading-whitespace prefixes, a regression guard over the existing detections, and safe-command non-triggers (2>&1,| ssh,| shuf,| /usr/bin/shasum, quoted arrows, input redirects).xcodebuild testis green andswiftlint --strictis clean.Accepted limitations
The redirect heuristic is intentionally token-blind, so it over-flags two harmless shapes (an extra confirmation prompt, never a missed destructive command): a quoted
>comparison likeawk '$1 > 10' data.csv, and a redirect to a discard target likecmd > /dev/null. That is the right bias for a confirm-before-execute gate, and shell-quote parsing was deliberately avoided to keep the heuristic simple and avoid introducing false negatives.