fix(mcp): sandbox tool handlers to allowed filesystem roots#112
Merged
Conversation
Before this commit, every MCP tool handler accepted \`request.path\` verbatim — \`let path_buf = PathBuf::from(request.path)\` — and the only validation was an existence check. An agent connected over MCP could ask ck to index or semantically search \`/etc\`, \`/Users/<other>\`, or any readable path on the host. McpContext stored a \`cwd\` but nothing enforced it. Changes: - McpContext gains a canonical \`allowed_roots\` vector. cwd is canonicalized at construction so symlink-based escape doesn't slip past containment. Operators can extend the sandbox with the \`CK_MCP_ALLOWED_ROOTS\` env var (colon-separated, like \`PATH\`). - New \`McpContext::resolve_request_path(raw) -> Result<PathBuf, ErrorData>\` parses the request string, joins relative paths against cwd, rejects empty/non-existent paths with \`invalid_params\`, canonicalizes the result, and verifies it lives under at least one allowed root. - All six tool handlers (semantic, lexical, regex, hybrid, index_status, reindex) replace their \`PathBuf::from(path)\` + ad-hoc existence check with one call to \`resolve_request_path\`. - Seven sandbox tests cover: accepted absolute + relative inside-root paths, rejected absolute-outside, rejected \`..\` traversal, rejected symlink-pointing-outside (unix), rejected empty path, rejected nonexistent path. All pass under \`--no-default-features\` so they're exercised on every CI matrix slot. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The sandbox refactor changed the not-found error from "Path does not exist" to "path does not exist" (lowercase, since all other invalid_params messages are lowercase). Update the test assertion to match on the case-stable substring "does not exist" so it stays robust to either form. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
runonthespot
added a commit
that referenced
this pull request
May 23, 2026
Ships the bug fixes and security work merged today: - #111 fix: scoped semantic search returned [] when global top_k was consumed by chunks outside the requested path scope - #112 security: MCP tool handlers were sandbox-escapable via any readable host path; added allowed_roots + canonicalize check - #106 fix: MCP tool schemas now Gemini-API compatible (no more union types in JSON Schema) - #100 fix: oneshot 0.1.13 patches a use-after-free race - #99 chore: docs-site + ck-vscode dev-dep bumps See CHANGELOG.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem (security: HIGH)
Every MCP tool handler accepted `request.path` verbatim:
```rust
let path_buf = PathBuf::from(request.path);
```
The only validation was an existence check. An agent connected over MCP could ask ck to index or semantically search any path readable by the host — `/etc`, another user's home, anything. `McpContext` stored a `cwd` field but nothing in the request pipeline enforced it.
Found via code review (task #9).
Fix
Tests (all 7 pass under `--no-default-features`)
Notes
Test plan
🤖 Generated with Claude Code