Skip to main content

View source on GitHub

An advanced hook example that enforces workspace isolation - preventing agents from navigating or writing outside their assigned directory. This is particularly useful when running multiple parallel conversations on a local machine, ensuring each conversation stays in its own sandbox without interfering with others. Based on the production implementation in jpshackelford/lxa.

What’s in the Box

The sandbox-enforcer/ plugin bundles:
  • Hooks manifest (hooks/hooks.json) - Two PreToolUse hooks (one for terminal, one for file_editor) whose validation logic is inlined as POSIX-sh commands
  • 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:
Without workspace isolation:
  • Agent 1 might accidentally cd ../api-server and modify the wrong project
  • Agent 2 could rm -rf ../web-app/node_modules while cleaning up
  • Agent 3 might write test output to /tmp that conflicts with other agents
With workspace isolation:
  • 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-only escape 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, popd to 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_edit to external paths
  • Read commands: view is always allowed (can read anywhere)

Run It

  1. Create multiple project directories:
  1. Load the plugin in each conversation with different workspace dirs:
  1. 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:
The hook detects the # 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_edit to external paths

Workspace Detection

The hooks determine your workspace using (in order):
  1. OPENHANDS_PROJECT_DIR - Set by OpenHands Cloud/Enterprise
  2. PWD - Current working directory (fallback)
This makes it work in both local and cloud environments.

Plugin Structure

This follows the Claude Code plugin format. The validation logic lives inline in 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
⏭️ Skip workspace isolation when:
  • 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:
The hooks ensure each job stays in its assigned directory, preventing interference.

Limitations

This is a simplified, heuristic-based implementation with limitations:
  1. No full shell parsing - Complex quoting might bypass detection
  2. No path canonicalization - Doesn’t resolve symlinks or . / .. fully
  3. Simplified command extraction - May not catch all edge cases
  4. Fails open - If hook can’t parse input, it allows the operation (for safety)
For production use, see the full Python implementation in lxa which handles:
  • 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 inlined command in hooks/hooks.json (and keep the reference scripts/*.sh in sync):
Keep the logic POSIX-sh and inline so it runs correctly when loaded as a plugin (see the note under Hook Scripts).

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.

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