Skip to content

fix(command-mode): close three confirm-gate bypasses: absolute path, find -delete/-exec rm, diskutil - #862

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

fix(command-mode): close three confirm-gate bypasses: absolute path, find -delete/-exec rm, diskutil#862
manjunathbhaskar wants to merge 1 commit into
altic-dev:mainfrom
manjunathbhaskar:fix/command-mode-destructive-command-gaps

Conversation

@manjunathbhaskar

Copy link
Copy Markdown

Description

isDestructiveCommand (Sources/Fluid/Services/CommandModeService.swift) only matched bare command names, so a model reaching for an absolute path (/bin/mv, /usr/bin/sudo, /bin/chmod, /usr/bin/killall, etc.) skipped the confirm gate its bare-name equivalent triggers. find -delete/find -exec rm and diskutil's erase/reformat subcommands were never covered at all. None of these need anything adversarial-looking from the model — an absolute path, find, or diskutil are all ordinary tool choices.

Resolves the leading command to its bare name the same way a shell would (last path component of the first token) so any path prefix is recognized uniformly, plus dedicated checks for find and diskutil. Scoped narrowly: diskutil list/diskutil info (read-only) stay unflagged — verified by a dedicated false-positive test.

Relationship to #434: that PR does extensive, independent hardening on this same function (pipe-to-shell, redirects, whitespace-prefix). I checked rather than assumed it doesn't cover this — fetched #434's actual branch, mechanically extracted its patched code, and reran these same adversarial inputs against it before writing this fix; all three classes here survive #434's hardening unchanged. Filed as issue #861 first with the same evidence, since I didn't want to push a competing PR onto the same lines without flagging the overlap. Happy to rebase onto #434 if it merges first, or however you'd prefer to sequence the two.

Type of Change

  • 🐞 Bug fix
  • ✨ New feature
  • 💥 Breaking change
  • 🧹 Chore
  • 📝 Documentation update

Related Issue or Discussion

Closes #861

Testing

  • Tested on Apple Silicon Mac
  • Tested on Intel Mac
  • Tested on macOS version: 26.1
  • Ran linter locally: swiftlint lint --strict --config .swiftlint.yml Sources/Fluid/Services/CommandModeService.swift — 0 violations
  • Ran formatter locally: swiftformat --config .swiftformat --lint — clean on the changed region (the file carries 37 pre-existing formatting violations elsewhere, confirmed against the unmodified original before touching anything; none of them are in this diff)
  • Ran tests locally:

New test file CommandModeDestructiveCommandGapTests.swift, 5 tests: the three fixed bypass classes, a regression check that original bare-command detection still works, and a false-positive check on benign commands (diskutil list, find . -name '*.txt', /usr/bin/python3 --version).

Test Suite 'CommandModeDestructiveCommandGapTests' passed.
	 Executed 5 tests, with 0 failures (0 unexpected)

Full-suite regression check: ran the complete test suite with and without this change (git stash to get a clean baseline, same xcodebuild test invocation both times) and diffed the exact failure sets — identical 27 pre-existing failures both times (audio/microphone-hardware tests that need real CoreAudio device state this environment doesn't have; unrelated to Command Mode). Zero regressions from this change.

xcodebuild -project Fluid.xcodeproj -scheme Fluid -configuration Debug build on macOS 26.1 / Xcode 26.3 (Apple Silicon): BUILD SUCCEEDED, no new warnings.

Screenshots / Video

  • No UI/visual changes; screenshots/video are not applicable.

Notes

Not a live bug today — find/diskutil aren't reachable through anything else in the app, and the confirm gate is defense-in-depth for the model's own mistakes rather than the only line of defense (Command Mode already grants real shell access by design). The value is closing the gap before a plausible model output lands in it, not fixing something currently broken.

…find -delete/-exec rm, diskutil

isDestructiveCommand only matched bare command names, so an absolute-path
invocation (/bin/mv, /usr/bin/sudo, /bin/chmod, etc.) skipped the confirm
gate a bare-name equivalent would trigger. find -delete / find -exec rm
and diskutil's erase/reformat subcommands were never covered at all.

None of these three classes need anything adversarial-looking from the
model -- an absolute path, find, or diskutil are all ordinary tool
choices, so a plausible model output can land in any of them silently.

Resolves the leading command to its bare name the same way a shell
would (last path component of the first token) so any path prefix is
recognized uniformly, plus dedicated checks for find and diskutil.
Scoped narrowly: diskutil list/info (read-only) stay unflagged.

Verified the gap survives PR altic-dev#434's independent hardening pass on the
same function (fetched its branch, reran the same adversarial inputs
against its actual patched code) before writing this, so this is a
distinct gap, not overlapping work -- see altic-dev#861.

Closes altic-dev#861

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Aug 14, 2026

Copy link
Copy Markdown

Greptile Summary

This PR broadens Command Mode’s destructive-command confirmation classifier and adds regression coverage.

  • Resolves unquoted absolute executable paths to their basename.
  • Adds detection for find deletion actions and destructive diskutil operations.
  • Exposes the classifier as a static test seam and registers the new test file with the Xcode project.

Confidence Score: 4/5

The quoted-executable bypass should be fixed before merging because zsh executes these commands destructively while the confirmation classifier does not recognize them.

The new basename extraction operates on raw shell syntax, so ordinary quoting leaves quote characters in the derived command name and preserves a confirmation bypass; diskutil matching also introduces non-blocking false positives by scanning all arguments.

Files Needing Attention: Sources/Fluid/Services/CommandModeService.swift, Tests/FluidDictationIntegrationTests/CommandModeDestructiveCommandGapTests.swift

Fix all with Greploop

Fix All in Codex

Prompt To Fix All With AI
### Issue 1
Sources/Fluid/Services/CommandModeService.swift:610-613
**Quoted executables bypass confirmation**

When a destructive executable is quoted, such as `"/bin/rm" -rf ~/Documents`, this parser retains the closing quote and derives `rm"` instead of `rm`. The classifier therefore returns false, while `/bin/zsh -c` resolves and executes `/bin/rm` without confirmation.

### Issue 2
Sources/Fluid/Services/CommandModeService.swift:639
**Diskutil arguments trigger false positives**

Searching the entire command for each subcommand name also matches ordinary arguments, so a benign command such as `diskutil info /Volumes/EraseDisk` is suspended behind manual confirmation. Parse and compare the actual diskutil subcommand token instead.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "fix(command-mode): close three confirm-g..." | Re-trigger Greptile

Comment on lines +610 to +613
let leadingToken = cmd
.drop(while: { $0 == " " || $0 == "\t" })
.prefix(while: { $0 != " " && $0 != "\t" })
let commandName = (leadingToken as NSString).lastPathComponent

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 Quoted executables bypass confirmation

When a destructive executable is quoted, such as "/bin/rm" -rf ~/Documents, this parser retains the closing quote and derives rm" instead of rm. The classifier therefore returns false, while /bin/zsh -c resolves and executes /bin/rm without confirmation.

Knowledge Base Used: AI Enhancement Pipeline

Prompt To Fix With AI
This is a comment left during a code review.
Path: Sources/Fluid/Services/CommandModeService.swift
Line: 610-613

Comment:
**Quoted executables bypass confirmation**

When a destructive executable is quoted, such as `"/bin/rm" -rf ~/Documents`, this parser retains the closing quote and derives `rm"` instead of `rm`. The classifier therefore returns false, while `/bin/zsh -c` resolves and executes `/bin/rm` without confirmation.

**Knowledge Base Used:** [AI Enhancement Pipeline](https://app.greptile.com/altic/-/custom-context/knowledge-base/altic-dev/fluidvoice/-/docs/ai-enhancement.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Codex

"erasedisk", "erasevolume", "secureerase",
"reformat", "partitiondisk", "zerodisk", "unmountdisk",
]
if destructiveDiskutilSubcommands.contains(where: { cmd.contains($0) }) {

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 Diskutil arguments trigger false positives

Searching the entire command for each subcommand name also matches ordinary arguments, so a benign command such as diskutil info /Volumes/EraseDisk is suspended behind manual confirmation. Parse and compare the actual diskutil subcommand token instead.

Knowledge Base Used: AI Enhancement Pipeline

Prompt To Fix With AI
This is a comment left during a code review.
Path: Sources/Fluid/Services/CommandModeService.swift
Line: 639

Comment:
**Diskutil arguments trigger false positives**

Searching the entire command for each subcommand name also matches ordinary arguments, so a benign command such as `diskutil info /Volumes/EraseDisk` is suspended behind manual confirmation. Parse and compare the actual diskutil subcommand token instead.

**Knowledge Base Used:** [AI Enhancement Pipeline](https://app.greptile.com/altic/-/custom-context/knowledge-base/altic-dev/fluidvoice/-/docs/ai-enhancement.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Codex

@altic-dev

Copy link
Copy Markdown
Owner

please fix the issues and I can check if I Can merge it. THanks!

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.

isDestructiveCommand's confirm gate has three bypass classes PR #434 doesn't cover: absolute-path invocation, find -delete/-exec rm, and diskutil

2 participants