Skip to content
Docs

Workspace Scope Guard

The workspace scope guard enforces hard boundaries around your current project. In a multi-project workspace, it ensures that ALL file operations and bash commands target only the project you’re working in — preventing reads, writes, and command execution outside the project directory.

The plugin registers hooks across multiple events:

Hook EventScriptPurpose
PreToolUseguard-workspace-scope.pyBlock out-of-scope file and bash operations
PreToolUseinject-workspace-cwd.pyInject CWD context alongside enforcement
SessionStartinject-workspace-cwd.pySet scope context at session begin
UserPromptSubmitinject-workspace-cwd.pyRemind scope on every prompt
SubagentStartinject-workspace-cwd.pyEnsure subagents know their scope

The scope guard intercepts every file-related tool call (Read, Write, Edit, NotebookEdit, Glob, Grep) and Bash commands. Before the tool executes, guard-workspace-scope.py resolves target paths and checks whether they fall within the current working directory.

All violations are hard-blocked (exit code 2):

OperationOut-of-Scope Behavior
Write, Edit, NotebookEditBlocked with error message
Read, Glob, GrepBlocked with error message
BashBlocked — two-layer path detection
Unknown toolsBlocked — fail closed

/workspaces/.devcontainer/ is permanently blocked for ALL operations — reads, writes, and bash commands. This is the single most common scope escape: Claude writing to the workspace-root devcontainer instead of the project’s own .devcontainer/.

The blacklist:

  • Runs before all other checks (scope, allowlist, cwd bypass)
  • Cannot be overridden, even when cwd is /workspaces
  • Blocks the exact path and everything under it

Everything under the current working directory:

/workspaces/projects/MyProject/ -- project root (cwd)
/workspaces/projects/MyProject/src/ -- in scope
/workspaces/projects/MyProject/tests/ -- in scope
/workspaces/projects/MyProject/.specs/ -- in scope

Anything outside the project root is blocked:

/workspaces/projects/OtherProject/ -- blocked (sibling project)
/workspaces/.devcontainer/ -- BLOCKED (blacklisted — always)
/workspaces/projects/MyProject2/ -- blocked (different project)
/home/vscode/ -- blocked (outside workspace)
/etc/hosts -- blocked (system path)

A minimal set of paths are always allowed:

Allowed PathReason
~/.claude/Claude config, plans, rules
/tmp/System temp directory

Bash commands receive two-layer scope enforcement:

20+ regex patterns extract file paths from write operations: redirects (>), cp, mv, touch, mkdir, rm, ln, rsync, chmod, chown, dd, wget -O, curl -o, tar -C, unzip -d, gcc -o, sqlite3, and more. Each extracted target is resolved and scope-checked.

System command exemption: Commands like git, pip, npm get a Layer 1 exemption ONLY when ALL write targets resolve to system paths (/usr/, /bin/, etc.). Any /workspaces/ write target outside cwd cancels the exemption.

A regex scans the entire command for any /workspaces/ path string. This catches everything Layer 1 misses:

  • Inline scripts: python3 -c "open('/workspaces/...')"
  • Variable assignments: DIR=/workspaces/.devcontainer; ...
  • Quoted paths in any context
  • Tool-specific flags: pip install --target /workspaces/...

Layer 2 always runs — no exemptions, no system command bypass.

Terminal window
# ALLOWED — no /workspaces/ paths, system commands
pip install requests
git status
npm test
# BLOCKED — writes to blacklisted path
echo test > /workspaces/.devcontainer/foo
cp file /workspaces/.devcontainer/
# BLOCKED — references blacklisted path (Layer 2)
python3 -c "open('/workspaces/.devcontainer/f','w')"
npm install --prefix /workspaces/.devcontainer/
# BLOCKED — workspace path outside cwd (Layer 2)
DIR=/workspaces/.devcontainer; echo > $DIR/foo

The guard inspects different fields per tool:

ToolPath Field Inspected
Readfile_path
Writefile_path
Editfile_path
NotebookEditnotebook_path
Globpath
Greppath
Bashcommand (multi-path extraction)

When a tool doesn’t specify a path (e.g., Glob without a path parameter), it defaults to the current working directory, which is always in scope.

The guard fails closed on all errors:

ScenarioBehavior
JSON parse failureBlocked (exit 2)
Any exceptionBlocked (exit 2)
Hook timeoutFails open (Claude Code runtime limitation) — mitigated by 10s timeout and pure computation

inject-workspace-cwd.py fires on SessionStart, UserPromptSubmit, PreToolUse, and SubagentStart to inject:

  • The current working directory
  • A reminder that /workspaces/.devcontainer/ is blacklisted
  • The correct project-relative path to use instead

This ensures Claude always knows the correct scope, even across subagent boundaries.