Skip to content

feat: add opt-in bounded shell execution - #100

Merged
avifenesh merged 3 commits into
mainfrom
feature/opt-in-shell-execution
Aug 25, 2026
Merged

feat: add opt-in bounded shell execution#100
avifenesh merged 3 commits into
mainfrom
feature/opt-in-shell-execution

Conversation

@avifenesh

Copy link
Copy Markdown
Collaborator

Summary

  • add a run_shell MCP tool for trusted remote MCP deployments
  • keep the tool absent by default; register it only when the operator starts the server with COMPUTER_USE_LINUX_ENABLE_SHELL=1
  • execute through fixed /bin/sh with a canonical cwd, cleared/minimal inherited environment, visible explicit env additions, bounded timeout/output, process-group cleanup, and SHA-256 audit records
  • mark the tool destructive, non-idempotent, and open-world; document that it is same-user arbitrary code execution, not a sandbox
  • extend the MCP safety gate to prove the default surface remains 18 tools, the opt-in surface is 19, ambient test secrets are excluded, and explicit env values work

Closes #99

Threat model

The realistic attacker controls MCP tool arguments through untrusted model/context input but cannot set the server process environment. The security invariant is that discovery alone never grants process execution. Operator opt-in plus host approval are prerequisites; after both, same-user file and network access is intended authority, while default exposure, ambient credential inheritance, unbounded execution/output, or failed process-tree cleanup remain security defects.

Verification

  • cargo test
  • cargo clippy --all-targets -- -D warnings
  • cargo build
  • python3 scripts/mcp_safety_check.py --binary target/debug/computer-use-linux --repo .
  • cargo fmt --all -- --check
  • git diff --check

@revuto-review revuto-review 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.

This is an auto review done by revuto.


Reviewed the full opt-in shell execution surface (src/server.rs, scripts/mcp_safety_check.py, docs, deps). The load-bearing security claims check out against source:

  • Process-tree cleanup: command_runner::output_with_input sets kill_on_drop(true) + process_group(0), and ProcessGroupGuard SIGKILLs -pgid on timeout/cancel (src/command_runner.rs:134-148, 382-434), with existing tests covering pipe-holding descendants.
  • Bounded execution/output: timeout clamped to 1..=120s; read_pipe hard-caps collection at 8 MiB/stream before bounded_shell_stream truncates responses to 512 KiB — so "bounded collection" in the README is accurate, not just response-side truncation.
  • Default surface preserved: ToolRouter.map is public, the route removal is real, and the safety script now asserts exactly 18 tools by default / 19 with opt-in, verifies annotations, excludes an ambient test secret, and exercises explicit env — enforced in CI.
  • Version/schema coherence: no existing tool's parameters changed; run_shell is additive and absent by default, so no breaking MCP contract. sha2 is well-known RustCrypto with a small lockfile delta.

One robustness defect found (inline below): the environment-inheritance loop uses std::env::vars(), which panics on any non-UTF-8 variable in the server's environment, taking down the connection instead of returning the structured error every other failure path produces. Everything else looks solid.

Comment thread src/server.rs Outdated

@revuto-review revuto-review 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.

This is an auto review done by revuto.


Reviewed the opt-in shell execution surface end to end: tool gating, env construction, execution path via command_runner, docs, and the extended safety gate.

Verified as sound:

  • Default surface stays at 18 tools: router.map.remove("run_shell") at registration and a runtime re-check in execute_shell — discovery alone cannot reach execution, matching the stated invariant.
  • Timeout enforcement, process-group cleanup (process_group(0) + ProcessGroupGuard kill-on-drop), kill_on_drop, and stdin Stdio::null() all come from command_runner::output_with_timeout, so the "process-tree cleanup" claim holds.
  • Memory is bounded during collection at 8 MiB/stream, so a yes-style firehose cannot OOM the server within the 120 s ceiling.
  • Follow-up commit correctly switched to env::vars_os() with per-entry into_string() fallthrough (no panic on non-UTF-8 ambient env); env name validation blocks =/- injection into environ.
  • Error contract matches the existing Json<Output> + error: Option<String> convention used by every other tool; the audit record is digest-keyed and never echoes command text.
  • sha2 = "0.11.0" is well-known RustCrypto (pure Rust + cpufeatures), no sys/FFI surface. Version handling follows the repo's land-under-[Unreleased] convention; the default tool surface is unchanged, so no migration note is required.

Two findings where the implementation doesn't match the contract this PR documents for itself — both inline. Neither blocks the design; both are about the stated environment/output guarantees.

Comment thread src/server.rs Outdated
Comment thread src/server.rs

@revuto-review revuto-review 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.

This is an auto review done by revuto.


Revuto completed the review and found no evidence-backed concerns.

@avifenesh
avifenesh merged commit b03e9d1 into main Aug 25, 2026
22 checks passed
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.

Shell execution support

1 participant