Skip to content

fix(shell): validate cwd parameter against workspace boundary#524

Merged
Hmbown merged 1 commit into
Hmbown:mainfrom
shentoumengxin:fix/shell-cwd-boundary-enforcement
May 4, 2026
Merged

fix(shell): validate cwd parameter against workspace boundary#524
Hmbown merged 1 commit into
Hmbown:mainfrom
shentoumengxin:fix/shell-cwd-boundary-enforcement

Conversation

@shentoumengxin

Copy link
Copy Markdown
Contributor

The shell tool's cwd / working_dir JSON parameter was accepted as-is
without workspace boundary validation. All file tools (read_file,
write_file, list_dir, etc.) already go through ToolContext::resolve_path()
to enforce workspace boundaries, but exec_shell skipped this check entirely.
This meant the AI model could run shell commands from arbitrary directories
outside the user's workspace.

This PR adds a single resolve_path() call on the cwd parameter, bringing
the shell tool in line with the existing file tool security model:

  • Paths outside the workspace root are rejected with ToolError::PathEscape
  • trust_mode = true still bypasses the check (consistent with file tools)
  • trusted_external_paths entries are respected automatically
  • Default behavior (no cwd argument → use workspace root) is unchanged
  • OS-level sandboxing (Seatbelt/Landlock) remains a separate, orthogonal layer

Changes

  • crates/tui/src/tools/shell.rs — Replace raw string extraction of cwd
    with a context.resolve_path(dir)? validated version (+9 / -2 lines)

The shell tool's `cwd` / `working_dir` parameter was accepted raw
without any workspace boundary check, unlike file tools which all go
through `ToolContext::resolve_path()`.  This allowed the AI model to
execute shell commands from arbitrary directories outside the workspace.

Reuse the existing `resolve_path()` validation so that:
- Paths outside the workspace root are rejected with `PathEscape`
- `trust_mode = true` still bypasses the check (consistent behavior)
- `trusted_external_paths` entries are respected automatically
- Default behavior (no cwd argument) remains unchanged

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces path validation for the working_dir (or cwd) parameter in the shell tool to ensure it respects workspace boundaries. The review feedback suggests a more idiomatic implementation using transpose() and identifies a limitation where the validated working directory is currently ignored during foreground execution, meaning the parameter only functions for background or interactive commands.

Comment on lines +1563 to +1574
let working_dir = match input
.get("cwd")
.or_else(|| input.get("working_dir"))
.and_then(serde_json::Value::as_str)
.map(str::to_string);
{
Some(dir) => {
// Validate cwd against workspace boundary (same as file tools)
let resolved = context.resolve_path(dir)?;
Some(resolved.to_string_lossy().to_string())
}
None => None,
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

This logic can be simplified using transpose() to handle the Result inside the Option more idiomatically.

Additionally, note that while working_dir is correctly validated here, it is currently ignored in the foreground execution path (lines 1602-1610 in the full file) because execute_foreground_via_background does not accept a working directory argument and hardcodes None when calling the manager. This means the cwd parameter currently only functions for background or interactive commands.

        let working_dir = input
            .get("cwd")
            .or_else(|| input.get("working_dir"))
            .and_then(serde_json::Value::as_str)
            .map(|dir| context.resolve_path(dir))
            .transpose()?
            .map(|p| p.to_string_lossy().to_string());

@Hmbown

Hmbown commented May 3, 2026

Copy link
Copy Markdown
Owner

great find - thank you so much

@Hmbown Hmbown added the v0.8.10 Targeting v0.8.10 label May 4, 2026
@Hmbown Hmbown added this to the v0.8.10 milestone May 4, 2026
@Hmbown Hmbown merged commit 3e56f35 into Hmbown:main May 4, 2026
8 checks passed
@Hmbown Hmbown mentioned this pull request May 4, 2026
4 tasks
Hmbown added a commit that referenced this pull request May 4, 2026
Picks up the v0.8.10 patch release contents:
* Daemon API quartet for whalescale-desktop integration (#561-#564,
  PR #567).
* Bug cluster: macOS seatbelt cargo registry (#558), MCP SIGTERM
  shutdown (#420), Linux PR_SET_PDEATHSIG (#421).
* npm install on older glibc fix (#555/#560 via #556 + #565).
* Shell cwd workspace-boundary validation (#524).
* Memory help/docs polish (#497 via #569).
* Onboarding language picker (#566).
* Whale nicknames interleaved with Simplified Chinese.

First-time contributors credited in CHANGELOG: @staryxchen,
@shentoumengxin, @Vishnu1837, @20bytes.

Workspace `Cargo.toml`, all 9 internal path-dep version pins, and
`npm/deepseek-tui/package.json` all bumped to 0.8.10. `Cargo.lock`
regenerated and committed alongside.

Verified locally:
* cargo fmt --all -- --check
* cargo clippy --workspace --all-targets --all-features --locked -- -D warnings
* cargo test --workspace --all-features --locked
* bash scripts/release/check-versions.sh

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
MMMarcinho pushed a commit to MMMarcinho/DeepSeek-TUI that referenced this pull request May 6, 2026
…#524)

The shell tool's `cwd` / `working_dir` parameter was accepted raw
without any workspace boundary check, unlike file tools which all go
through `ToolContext::resolve_path()`.  This allowed the AI model to
execute shell commands from arbitrary directories outside the workspace.

Reuse the existing `resolve_path()` validation so that:
- Paths outside the workspace root are rejected with `PathEscape`
- `trust_mode = true` still bypasses the check (consistent behavior)
- `trusted_external_paths` entries are respected automatically
- Default behavior (no cwd argument) remains unchanged

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
MMMarcinho pushed a commit to MMMarcinho/DeepSeek-TUI that referenced this pull request May 6, 2026
Picks up the v0.8.10 patch release contents:
* Daemon API quartet for whalescale-desktop integration (Hmbown#561-Hmbown#564,
  PR Hmbown#567).
* Bug cluster: macOS seatbelt cargo registry (Hmbown#558), MCP SIGTERM
  shutdown (Hmbown#420), Linux PR_SET_PDEATHSIG (Hmbown#421).
* npm install on older glibc fix (Hmbown#555/Hmbown#560 via Hmbown#556 + Hmbown#565).
* Shell cwd workspace-boundary validation (Hmbown#524).
* Memory help/docs polish (Hmbown#497 via Hmbown#569).
* Onboarding language picker (Hmbown#566).
* Whale nicknames interleaved with Simplified Chinese.

First-time contributors credited in CHANGELOG: @staryxchen,
@shentoumengxin, @Vishnu1837, @20bytes.

Workspace `Cargo.toml`, all 9 internal path-dep version pins, and
`npm/deepseek-tui/package.json` all bumped to 0.8.10. `Cargo.lock`
regenerated and committed alongside.

Verified locally:
* cargo fmt --all -- --check
* cargo clippy --workspace --all-targets --all-features --locked -- -D warnings
* cargo test --workspace --all-features --locked
* bash scripts/release/check-versions.sh

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v0.8.10 Targeting v0.8.10

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants