View source on GitHub
What’s in the Box
Thesandbox-enforcer/ plugin bundles:
- Hooks manifest (
hooks/hooks.json) - Two PreToolUse hooks (one forterminal, one forfile_editor) whose validation logic is inlined as POSIX-shcommands - Reference scripts (
hooks/scripts/*.sh) - The same logic as standalone files, kept for readability/diffing (see the note under Hook Scripts on why the manifest inlines them rather than referencing these files) - Skill (
skills/sandbox-enforcer/SKILL.md) - Documentation about isolation rules - Plugin manifest (
.claude-plugin/plugin.json) - Standard Claude Code plugin format
The Problem This Solves
Scenario: You’re running a local OpenHands instance with multiple conversations in parallel:- Agent 1 might accidentally
cd ../api-serverand modify the wrong project - Agent 2 could
rm -rf ../web-app/node_moduleswhile cleaning up - Agent 3 might write test output to
/tmpthat conflicts with other agents
- Each agent stays in its assigned directory
- Attempts to navigate or write outside are blocked with helpful messages
- Agents can still READ external files (with
# read-onlyescape hatch)
How It Works
Meanwhile, Agent 2 and Agent 3 work independently in their own workspaces without risk of collision.Protected Operations
The plugin enforces isolation for:Terminal Commands
- Navigation:
cd,pushd,popdto external directories - Write operations:
rm,mv,cp,chmod,mkdir,touch,dd,ln, etc. - Output redirection:
>,>>to external paths
File Editor Operations
- Write commands:
create,str_replace,insert,undo_editto external paths - Read commands:
viewis always allowed (can read anywhere)
Run It
- Local Development Setup
- Via API
- Via Badge
- Create multiple project directories:
- Load the plugin in each conversation with different workspace dirs:
- Try cross-contamination (it will be blocked):
Note: In cloud ephemeral workspaces, this isolation is less critical (each conversation gets its own container), but it still demonstrates the technique for local setups.
The # read-only Escape Hatch
Sometimes you need to READ system files or shared resources outside your workspace.
Add # read-only to your prompt:
# read-only comment and allows non-destructive operations that reference external paths.
Hook Scripts
Inline, not referenced. When a plugin’s PreToolUse hook fires, the hook
runner executes the
command through /bin/sh -c with the working directory
set to the agent’s workspace, not the plugin directory — and there is no
plugin-root path variable for hooks. A relative hooks/scripts/validate_*.sh
therefore won’t resolve at runtime, and {"type": "script", "path": ...} is
not a supported hook type (only command, prompt, agent). So
hooks/hooks.json inlines each validator as a POSIX-sh command. The
.sh files below are byte-for-byte the same logic, kept as readable reference
(and easy to lint/diff); edit them and the inline command together.1. Terminal Validation (validate_terminal.sh)
Checks terminal commands for:
- Navigation commands (
cd,pushd,popd) trying to leave workspace - Write commands (
rm,mv,cp,chmod,mkdir, etc.) targeting external paths - Output redirects (
>,>>) to external files
2. File Editor Validation (validate_file_editor.sh)
Checks file_editor operations:
- Always allows
view(read-only) - Blocks
create,str_replace,insert,undo_editto external paths
Workspace Detection
The hooks determine your workspace using (in order):OPENHANDS_PROJECT_DIR- Set by OpenHands Cloud/EnterprisePWD- Current working directory (fallback)
Plugin Structure
hooks.json (so it resolves at runtime as a plugin); the scripts/*.sh files
hold the identical logic for readability.
Comparison with Other Examples
All three can be combined for defense-in-depth!
When to Use This
✅ Use workspace isolation when:- Running multiple parallel conversations on one machine
- Each conversation works on a different project/directory
- You need to prevent cross-contamination between workspaces
- Building multi-tenant local development environments
- Teaching/educational scenarios with multiple students
- Using cloud ephemeral workspaces (already isolated by containers)
- Running only one conversation at a time
- The agent needs to work across multiple project directories intentionally
Real-World Example: LXA
This example is based on jpshackelford/lxa, a production tool that manages multiple OpenHands conversations in parallel:Limitations
This is a simplified, heuristic-based implementation with limitations:- No full shell parsing - Complex quoting might bypass detection
- No path canonicalization - Doesn’t resolve symlinks or
./..fully - Simplified command extraction - May not catch all edge cases
- Fails open - If hook can’t parse input, it allows the operation (for safety)
- Full path resolution with
pathlib - Symlink following
- Complex shell command parsing with
shlex - Proper handling of shell metacharacters and quoting
Extending the Example
Want to add more restrictions? Edit the inlinedcommand in hooks/hooks.json
(and keep the reference scripts/*.sh in sync):
Contributing
This example prioritizes clarity and accessibility over completeness. If you’re building a production system, refer to the lxa implementation for a more robust approach.Related
OpenHands Hooks Guide
Official documentation
jpshackelford/lxa
Production implementation
command-blacklist
Block dangerous commands
command-whitelist
Whitelist safe commands
load-plugin
How to load this plugin
launch-plugin-badge
No-code launcher

