Files
stack/docs/scratchpads/p8-016-tool-hardening.md
Jason Woltje 8d511ddb67
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
feat(gateway): tool path hardening + sandbox escape prevention (P8-016)
Introduces path-guard.ts with guardPath (symlink-aware) and guardPathUnsafe
(lexical-only) that throw SandboxEscapeError on any escape attempt. Replaces
weak containment checks in file-tools, git-tools, and shell-tools with strict
guards. Adds 12 unit tests covering traversal, absolute-path, and sibling-dir
escape vectors.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-15 20:58:15 -05:00

56 lines
1.6 KiB
Markdown

# P8-016: Security — Tool Path Hardening + Sandbox Escape Prevention
## Status: in-progress
## Branch: feat/p8-016-tool-hardening
## Issue: #169
## Scope
Harden file, git, and shell tool factories so no path operation escapes `sandboxDir`.
## Files to Create
- `apps/gateway/src/agent/tools/path-guard.ts` (new)
- `apps/gateway/src/agent/tools/path-guard.test.ts` (new)
## Files to Modify
- `apps/gateway/src/agent/tools/file-tools.ts`
- `apps/gateway/src/agent/tools/git-tools.ts`
- `apps/gateway/src/agent/tools/shell-tools.ts`
## Analysis
### file-tools.ts
- Has existing `resolveSafe()` function but uses weak containment check (relative path)
- Replace with `guardPath` (for reads/lists on existing paths) and `guardPathUnsafe` (for writes)
- Error pattern: return `{ content: [{ type: 'text', text: 'Error: ...' }], details: undefined }`
### git-tools.ts
- Has `clampCwd()` that silently falls back to sandbox root on escape attempt
- Replace with strict `guardPath` that throws SandboxEscapeError, caught and returned as error
- Also need to guard the `path` parameter in `git_diff`
### shell-tools.ts
- Has `clampCwd()` same silent-fallback approach
- Replace with strict `guardPath` that throws SandboxEscapeError
## Key Design Decisions
- `guardPath`: uses `realpathSync.native` to resolve symlinks, requires path to exist
- `guardPathUnsafe`: lexical only (`path.resolve`), for paths that may not exist yet
- Both throw `SandboxEscapeError` on escape attempt
- Callers catch and return error result
## Verification
- pnpm typecheck
- pnpm lint
- pnpm format:check
- pnpm test