View source on GitHub
What’s in the Box
Thesafety-guardian/ plugin bundles:
- Hooks (
hooks/hooks.json) - PreToolUse hook that intercepts terminal commands - Skill (
skills/safety-guardian/SKILL.md) - Documentation about what’s protected - Plugin manifest (
.claude-plugin/plugin.json) - Standard Claude Code plugin format
How It Works
Protected Patterns
The hook blocks:
All other commands work normally - only these specific dangerous patterns are blocked.
The
rm -rf and chmod 777 rules only fire on system directories
(/etc, /usr, /var, /home, /bin, /lib, /root, /dev, …). Ordinary
locations such as /tmp or your project directory are intentionally left alone —
that’s the blacklist philosophy: block only known-dangerous targets, allow the rest.
(So rm -rf /tmp is not blocked; use the curl … | bash demo below to see a block.)Try It
- Load via API
- Launch via badge
Use the companion
load-plugin example:The Hook
The magic happens inhooks/hooks.json:
safety-guardian/hooks/hooks.json
PreToolUse- Runs before the terminal tool executesmatcher: "terminal"- Only applies to shell commands (not file edits, etc.)type: "command"- Thecommandis a shell script run by the hook runner (via/bin/sh -c). Keep it POSIX-compatible and inline — see the note below on why these examples don’t reference external.shfiles.- Exit codes:
0= Allow the command2= Block the command (with reason in JSON output)- Other = Log error, but allow (non-blocking)
- Reads the tool invocation JSON from stdin (
input=$(cat)) - Uses
grep -qEto check for dangerous patterns - Prints
{"decision": "deny", "reason": "..."}to stdout if blocked - Returns exit code 2 to enforce the block
Why inline, not a bash -c wrapper or an external script?
Why inline, not a bash -c wrapper or an external script?
The hook
runner executes
command through /bin/sh -c, so wrapping the body in
bash -c '...' makes any apostrophe in a message (I've, that's) terminate
the quote and break the script. We also can’t point command at a bundled
hooks/scripts/*.sh: when this runs as a plugin, hooks execute with the
working directory set to the agent’s workspace (not the plugin directory) and
there is no plugin-root path variable, so a relative script path won’t resolve.
Inlining a plain POSIX-sh script avoids both traps.Blacklist vs. Whitelist
This example uses a blacklist approach:- ✅ Pro: Most commands work normally
- ✅ Pro: Easier to get started
- ❌ Con: Can’t catch every dangerous pattern
- ❌ Con: Clever variations might slip through
command-whitelist example that shows the whitelist approach (only allow explicitly approved commands).
Hook Types
Hooks can intercept different lifecycle events:Plugin Structure
- OpenHands Cloud plugin launcher
- Claude Desktop plugin marketplace
- Any system supporting the
.claude-pluginspec
Related
OpenHands Hooks Guide
Full hook documentation
Plugin System
How plugins work
load-plugin
Programmatic plugin loading
launch-plugin-badge
No-code plugin launcher
command-whitelist
Whitelist approach (opposite strategy)
Real-World Use Cases
- Onboarding agents - Prevent trainees from dangerous operations
- Shared environments - Protect against accidental damage
- Compliance - Enforce security policies automatically
- Education - Teach safe command practices
- Testing - Prevent test scripts from harming the host
Extending the Example
Want to add your own patterns? Edithooks/hooks.json and add another if block:

