Skip to content

fix(command-mode): flag pipe-to-shell, output-redirect, and whitespace-prefixed destructive commands in the confirm gate#434

Open
postoso wants to merge 1 commit into
altic-dev:mainfrom
postoso:fix/command-mode-destructive-detection
Open

fix(command-mode): flag pipe-to-shell, output-redirect, and whitespace-prefixed destructive commands in the confirm gate#434
postoso wants to merge 1 commit into
altic-dev:mainfrom
postoso:fix/command-mode-destructive-detection

Conversation

@postoso

@postoso postoso commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

(Reopens #428, which was closed by accident on my end after a force-push; same work, healthy branch.)

What

CommandModeService.isDestructiveCommand powers the "confirm before running" safety gate in Command Mode. It missed several destructive command shapes that would auto-run without a confirmation prompt:

  • Pipe-to-shell: 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 / | dd were caught.
  • Pipe-to-tee: echo ... | tee ~/.zshrc / tee -a writes (or appends) to files. | sudo tee was already caught via the sudo pattern; the non-sudo case was not.
  • Output redirects mid-command: echo x > ~/.zshrc, cat y >> /etc/hosts. The previous "> " check only matched commands that started with >, so real redirects slipped through.
  • Leading whitespace: rm -rf x or a newline-prefixed sudo ... defeated the hasPrefix checks 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 now nonisolated static so it can be unit-tested directly (its single call site is updated). The redirect regex is tuned to skip file-descriptor cases: 2>&1 and >&2 and -> / => arrows do not trip it.

Tests

CommandModeDestructiveCommandTests covers 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 test is green and swiftlint --strict is 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 like awk '$1 > 10' data.csv, and a redirect to a discard target like cmd > /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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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 {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

@postoso

postoso commented Jun 26, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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 {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge 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 👍 / 👎.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant