Command-line safety layer for AI coding assistants.
SafeShell intercepts shell commands from AI tools like Claude Code, evaluates them against configurable rules, and enforces decisions before execution. Protect your system from accidental rm -rf, prevent exposure of sensitive files, and maintain control over what AI assistants can do on your machine.
AI coding assistants are powerful but operate with your shell permissions. Without guardrails:
- A misunderstood request could delete important files
- Sensitive data (SSH keys, credentials) could be exposed
- Destructive git operations could corrupt repositories
- System configurations could be modified unexpectedly
SafeShell provides:
- Rule-Based Protection: YAML-configured rules match commands by pattern, context, or bash conditions
- Approval Workflow: Require human approval for risky operations
- Real-Time Monitoring: Watch all commands in a terminal UI
- Context Awareness: Different rules for AI tools vs human operators
SafeShell uses a hybrid shim + daemon architecture for comprehensive command interception:
flowchart TB
subgraph Sources["Command Sources"]
Human["Human Terminal"]
Claude["Claude Code"]
Warp["Warp AI Agent"]
Scripts["Shell Scripts"]
end
subgraph Install["Installation (choose one)"]
UV["uv tool install (recommended)"]
Pipx["pipx install"]
UVDev["uv sync (dev)"]
end
subgraph Interception["Interception Layer"]
Shims["Command Shims<br/>~/.safeshell/shims/"]
Wrapper["Shell Wrapper<br/>safeshell-wrapper"]
Hook["Claude Code Hook<br/>~/.safeshell/hooks/"]
end
subgraph Core["SafeShell Daemon"]
Server["Unix Socket Server"]
Rules["Rules Engine"]
Approval["Approval Manager"]
Memory["Session Memory"]
end
subgraph Actions["Enforcement"]
Allow["✓ Allow"]
Deny["✗ Deny"]
Require["? Require Approval"]
end
Monitor["Monitor TUI<br/>safeshell monitor"]
UV --> Wrapper
Pipx --> Wrapper
UVDev --> Wrapper
Human --> Shims
Claude --> Hook
Claude --> Wrapper
Warp --> Shims
Warp --> Wrapper
Scripts --> Shims
Shims --> Server
Wrapper --> Server
Hook --> Server
Server --> Rules
Rules --> Allow
Rules --> Deny
Rules --> Approval
Approval --> Memory
Memory --> Require
Server -.-> Monitor
Monitor -.->|approve/deny| Approval
| Component | Purpose |
|---|---|
| Command Shims | Symlinks in ~/.safeshell/shims/ that intercept external commands (git, rm, docker, etc.) |
| Shell Function Overrides | Override shell builtins (cd, source, eval) via init.bash |
| Shell Wrapper | AI tools set SHELL=safeshell-wrapper to intercept all commands |
| Daemon | Unix socket server that loads rules, evaluates commands, and manages approvals |
| Monitor TUI | Real-time terminal UI for command visibility and approval workflow |
- Shim-based interception for external commands (git, rm, docker, etc.)
- Shell function overrides for builtins (cd, source, eval)
- AI tool hooks for Claude Code integration
- Transparent pass-through for allowed commands
- YAML configuration with global and per-repository rules
- Bash conditions for complex matching logic
- Context-aware filtering with
ai_onlyandhuman_onlyrules - Action types: allow, deny, require_approval, redirect
- Real-time event stream of all intercepted commands
- Approval workflow with approve/deny buttons
- Debug mode for rule evaluation visibility
- Keyboard navigation for efficient operation
- Claude Code via PreToolUse hook
- Shell integration via init.bash sourcing
# From source (recommended during development)
git clone https://github.com/be-wise-be-kind/safeshell.git
cd safeshell
uv syncsafeshell initThis creates:
~/.safeshell/config.yaml- Global configuration~/.safeshell/rules.yaml- Global rules~/.safeshell/shims/- Command shims
safeshell daemon startAdd to your ~/.bashrc or ~/.zshrc:
source ~/.safeshell/init.bashTo intercept commands from Claude Code, add the following to ~/.claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "~/.safeshell/hooks/claude_code_hook.py"
}
]
}
]
}
}Note: If you installed SafeShell from source with Poetry, point the hook to the source file instead:
"/path/to/safeshell/src/safeshell/hooks/claude_code_hook.py"
After editing settings.json, restart Claude Code for the hook to take effect.
# Check a command without executing
safeshell check "rm -rf /"
# Start the monitor to see real-time events
safeshell monitor- Python 3.11 or higher
GNOME's default focus-stealing prevention blocks SafeShell's approval window from appearing in front of other windows. To allow the approval dialog to grab focus when a command needs approval:
gsettings set org.gnome.desktop.wm.preferences focus-new-windows 'smart'This changes the setting from strict (blocks all focus stealing) to smart (allows new windows to take focus when appropriate).
# Install system-wide into an isolated environment
uv tool install safeshell
safeshell initpipx install safeshell
safeshell initgit clone https://github.com/be-wise-be-kind/safeshell.git
cd safeshell
uv sync
# Run tests
uv run pytest
# Run linting
uv run ruff check src/The Claude Code hook automatically finds safeshell-wrapper in this order:
- PATH - Searches all directories in
$PATH - Common locations -
~/.local/bin/safeshell-wrapper,/usr/local/bin/safeshell-wrapper - pyenv versions - Direct lookup in
~/.pyenv/versions/*/bin/(handles version mismatch) - uv - Falls back to
uv runfor development
SafeShell uses YAML configuration files at two levels:
~/.safeshell/config.yaml:
socket_path: /tmp/safeshell.sock
log_level: INFO
approval_timeout_seconds: 300
condition_timeout_ms: 100~/.safeshell/rules.yaml (global) and .safeshell/rules.yaml (per-repo):
rules:
- name: block-rm-rf
description: Block recursive force delete
pattern: "rm -rf"
action: deny
- name: approve-git-push-force
description: Require approval for force push
pattern: "git push --force"
action: require_approval
- name: block-sensitive-files
description: Block access to SSH keys
conditions:
- "echo $SAFESHELL_COMMAND | grep -q '\\.ssh'"
action: deny
ai_only: trueSee Rules Guide for complete documentation.
| Command | Description |
|---|---|
safeshell init |
Initialize configuration and shims |
safeshell check "<cmd>" |
Check if a command would be allowed |
safeshell status |
Show daemon status |
safeshell refresh |
Regenerate shims from rules |
| Command | Description |
|---|---|
safeshell daemon start |
Start the background daemon |
safeshell daemon stop |
Stop the daemon |
safeshell daemon restart |
Restart the daemon |
safeshell daemon status |
Check daemon status |
| Command | Description |
|---|---|
safeshell monitor |
Launch the monitor TUI |
safeshell monitor --debug |
Launch with debug panes visible |
SafeShell integrates with Claude Code via a PreToolUse hook that intercepts Bash commands before execution.
Setup:
Add the hook to your ~/.claude/settings.json (see Quick Start step 5 for the full configuration). The hook is located at ~/.safeshell/hooks/claude_code_hook.py (or in the source tree at src/safeshell/hooks/claude_code_hook.py for development installs).
Requirements:
- SafeShell daemon must be running (
safeshell daemon start) - Claude Code must be restarted after changing
settings.json
Behavior:
allowrules: Command executes normallydenyrules: Command is blocked with explanationrequire_approvalrules: Command waits for approval in Monitor TUI
Fail-open design: If the daemon is not running or the hook encounters an error, commands are allowed through. This prevents SafeShell from blocking your workflow if the daemon crashes.
SafeShell detects Warp's AI agent mode via the WARP_AI_AGENT environment variable. When WARP_AI_AGENT=1 is set, SafeShell treats commands as AI-originated, enabling ai_only rules.
Setup:
Create a Warp Rule (global or project-level) that instructs Warp's AI agent to set the environment variable before every command:
Option 1: Global Rule (applies to all projects)
In Warp, open Warp Drive > Personal > Rules and add a new global rule:
Always prefix shell commands with `WARP_AI_AGENT=1` to enable SafeShell safety checks.
For example: `WARP_AI_AGENT=1 git push --force`
Option 2: Project Rule (applies to a specific project)
Add a WARP.md file to your project root:
Always prefix shell commands with `WARP_AI_AGENT=1` to enable SafeShell safety checks.
For example: `WARP_AI_AGENT=1 git push --force`Requirements:
- SafeShell daemon must be running (
safeshell daemon start) - Shell integration must be loaded (
source ~/.safeshell/init.bashin your shell config)
Behavior: Same as Claude Code — allow, deny, and require_approval rules all apply. Rules with ai_only: true will match commands from Warp's AI agent.
Contributions are welcome! Please follow these steps:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Run tests and linting (
uv run pytest && uv run ruff check src/) - Commit your changes
- Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
- Write tests for new features
- Follow existing code style (enforced by Ruff)
- Add type hints to all functions
- Update documentation for user-facing changes
MIT License
- Issues: https://github.com/be-wise-be-kind/safeshell/issues
- Documentation: .ai/docs/ and .ai/howtos/
Built with: