From 8be22dac33f22952b84ec507c0d80d42e87bca73 Mon Sep 17 00:00:00 2001 From: Flo Date: Mon, 30 Mar 2026 10:11:08 +0200 Subject: [PATCH] Add multi-tool support: Claude Code, Gemini CLI, Codex - Add adapter layer with normalize_input/format_output per tool - Define common internal payload and verdict formats - Map event names across tools (PreToolUse/BeforeTool) - Map payload fields across tools (tool_name, tool_input, cwd) - Adapter-specific response formatting: - Claude: hookSpecificOutput.permissionDecision - Gemini: flat decision field - Codex: exit code 2 + stderr for deny - Shell shim takes --adapter flag to select tool - install.sh auto-detects all installed tools and registers hooks - Hook registration examples for all three tools - Add adapters/ directory to daemon source tree Co-Authored-By: Claude Opus 4.6 (1M context) --- .../specs/2026-03-26-security-hooks-design.md | 192 +++++++++++++++--- 1 file changed, 166 insertions(+), 26 deletions(-) diff --git a/docs/superpowers/specs/2026-03-26-security-hooks-design.md b/docs/superpowers/specs/2026-03-26-security-hooks-design.md index 860141b..246e4d3 100644 --- a/docs/superpowers/specs/2026-03-26-security-hooks-design.md +++ b/docs/superpowers/specs/2026-03-26-security-hooks-design.md @@ -1,6 +1,6 @@ -# Security Hooks for Claude Code +# Security Hooks for AI Coding Agents -A general-purpose, distributable set of Claude Code hooks that catch prompt injection, agent autonomy drift, supply chain attacks, and data exfiltration. Ships as a single binary (Elixir daemon via Burrito) with a shell shim, a custom rule DSL, and layered command analysis (regex + AST parsing). +A general-purpose, distributable set of security hooks for AI coding agents (Claude Code, Gemini CLI, Codex) that catch prompt injection, agent autonomy drift, supply chain attacks, and data exfiltration. Ships as a single binary (Elixir daemon via Burrito) with a shell shim, adapter layer for multi-tool support, a custom rule DSL, and layered command analysis (regex + AST parsing). ## Threat Model @@ -11,18 +11,26 @@ A general-purpose, distributable set of Claude Code hooks that catch prompt inje ## Architecture -Three components: +Four components: ### 1. Shell shim (`security-hook`) -A short bash script that Claude Code invokes as a hook command. It: +A short bash script that AI coding tools invoke as a hook command. It: +- Accepts an `--adapter` flag to specify the calling tool (`claude`, `gemini`, `codex`) - Reads the JSON hook payload from stdin -- Sends it to the daemon over a Unix socket -- Prints the daemon's JSON response to stdout +- Passes the payload and adapter name to the daemon over a Unix socket +- Prints the daemon's response to stdout (formatted for the calling tool) The shim is deliberately simple — it does not manage daemon lifecycle. That responsibility belongs to the platform service manager (see Daemon Lifecycle below). +Usage: +``` +security-hook --adapter claude pre bash # Claude Code +security-hook --adapter gemini pre bash # Gemini CLI +security-hook --adapter codex pre bash # Codex +``` + **Fail-closed policy:** If the shim cannot reach the daemon within its timeout, it exits with code 2 (blocking error) and writes a deny reason to stderr. The system never fails open. **Timeouts:** Two configurable values: @@ -49,7 +57,85 @@ Components: - **Config manager** — loads `config.toml`, merges `config.local.toml` overrides (see Configuration) - **Logger** — writes JSONL to `$XDG_STATE_HOME/security-hooks/hook.log` (macOS: `~/Library/Logs/security-hooks/hook.log`) -### 3. Rule files +### 3. Adapter layer + +The adapter layer lives inside the daemon and handles the differences between Claude Code, Gemini CLI, and Codex. Each adapter module implements two functions: + +1. **`normalize_input/1`** — transforms the tool-specific JSON payload into a common internal format +2. **`format_output/2`** — transforms the daemon's internal verdict into the tool-specific response format + +#### Common internal payload + +All adapters normalize to this structure: + +```json +{ + "adapter": "claude", + "event": "pre_tool_use", + "tool": "bash", + "input": {"command": "rm -rf /"}, + "cwd": "/project", + "session_id": "abc123" +} +``` + +#### Common internal verdict + +The rule engine returns: + +```json +{ + "decision": "deny", + "rule": "destructive-rm", + "match_type": "ast", + "reason": "destructive rm detected", + "nudge": "Use trash-cli or move to a temp directory" +} +``` + +#### Adapter output differences + +**Claude Code adapter** — wraps verdict in `hookSpecificOutput`: +```json +{ + "hookSpecificOutput": { + "hookEventName": "PreToolUse", + "permissionDecision": "deny", + "permissionDecisionReason": "destructive rm detected", + "additionalContext": "Use trash-cli or move to a temp directory" + } +} +``` + +**Gemini CLI adapter** — uses flat `decision` field: +```json +{ + "decision": "deny", + "reason": "destructive rm detected", + "message": "Use trash-cli or move to a temp directory" +} +``` + +**Codex adapter** — uses exit code 2 for deny, writes reason to stderr, no stdout JSON needed. For allow, exits 0 with empty stdout. + +#### Event name mapping + +| Internal event | Claude Code | Gemini CLI | Codex | +|---------------|------------|------------|-------| +| `pre_tool_use` | `PreToolUse` | `BeforeTool` | `PreToolUse` | +| `post_tool_use` | `PostToolUse` | `AfterTool` | `PostToolUse` | +| `session_start` | `SessionStart` | `SessionStart` | `SessionStart` | + +#### Payload field mapping + +| Internal field | Claude Code | Gemini CLI | Codex | +|---------------|------------|------------|-------| +| `tool` | `tool_name` | `tool_name` | `tool_name` | +| `input` | `tool_input` | `tool_input` | `tool_input` | +| `cwd` | `cwd` | `cwd` (or `$GEMINI_CWD`) | `cwd` | +| `session_id` | `session_id` | `session_id` (or `$GEMINI_SESSION_ID`) | `session_id` | + +### 4. Rule files Two kinds: - **Pattern rules** in `.rules` files using a custom DSL (see Rule Format below). Rules can use regex patterns for simple matching or AST functions for structural analysis. @@ -81,10 +167,14 @@ security-hooks/ │ │ │ ├── socket_listener.ex │ │ │ ├── rule_engine.ex │ │ │ ├── rule_loader.ex -│ │ │ ├── bash_analyzer.ex # AST parsing via bash Hex package +│ │ │ ├── bash_analyzer.ex # AST parsing │ │ │ ├── file_watcher.ex │ │ │ ├── config.ex │ │ │ ├── logger.ex +│ │ │ ├── adapters/ # tool-specific adapters +│ │ │ │ ├── claude.ex +│ │ │ │ ├── gemini.ex +│ │ │ │ └── codex.ex │ │ │ └── validators/ │ │ │ ├── unknown_executable.ex │ │ │ ├── dependency_mutation.ex @@ -96,34 +186,80 @@ security-hooks/ └── README.md ``` -## Hook Events & Claude Code Integration +## Hook Registration -Hooks are registered via `install.sh` into Claude Code's `settings.json`: +`install.sh` auto-detects which AI coding tools are installed and registers hooks for each. -### PreToolUse hooks (can allow/deny/ask) +### Claude Code (`~/.claude/settings.json`) -**Bash** (matcher: `Bash`): -``` -security-hook pre bash +```json +{ + "hooks": { + "PreToolUse": [ + { + "matcher": "Bash", + "hooks": [{"type": "command", "command": "security-hook --adapter claude pre bash"}] + }, + { + "matcher": "Edit|Write", + "hooks": [{"type": "command", "command": "security-hook --adapter claude pre edit"}] + }, + { + "matcher": "mcp__.*", + "hooks": [{"type": "command", "command": "security-hook --adapter claude pre mcp"}] + } + ] + } +} ``` -**Edit/Write** (matcher: `Edit|Write`): -``` -security-hook pre edit +### Gemini CLI (`~/.gemini/settings.json`) + +```json +{ + "hooks": { + "BeforeTool": [ + { + "matcher": "shell", + "hooks": [{"type": "command", "command": "security-hook --adapter gemini pre bash"}] + }, + { + "matcher": "edit|write", + "hooks": [{"type": "command", "command": "security-hook --adapter gemini pre edit"}] + }, + { + "matcher": "mcp_.*", + "hooks": [{"type": "command", "command": "security-hook --adapter gemini pre mcp"}] + } + ] + } +} ``` -**MCP** (matcher: `mcp__.*`): -``` -security-hook pre mcp +### Codex (`~/.codex/hooks.json`) + +```json +{ + "hooks": { + "PreToolUse": [ + { + "matcher": "Bash", + "hooks": [{"type": "command", "command": "security-hook --adapter codex pre bash"}] + } + ] + } +} ``` +Note: Codex currently only supports Bash hooks in PreToolUse/PostToolUse. Edit and MCP hooks will be added when Codex expands its hook events. + ### PostToolUse hook -Deferred to a future version. Post-tool-use linting is project-specific and requires detecting the project's toolchain, choosing the right linter, handling timeouts, and distinguishing agent-introduced errors from pre-existing ones. This deserves its own design pass. +Deferred to a future version. Post-tool-use linting is project-specific and requires its own design pass. ### Response format -The daemon returns JSON matching Claude Code's hook output spec. +The adapter layer translates the daemon's internal verdict into tool-specific responses. The examples below show Claude Code format; see the Adapter Layer section for Gemini and Codex formats. PreToolUse allow: ```json @@ -704,15 +840,19 @@ The install script: 2. Installs the binary and shell shim to `~/.local/bin/` (or user-specified location) 3. Copies default rules and config to `~/.config/security-hooks/` 4. Creates `config.local.toml` from a template if it does not exist -5. Auto-detects installed MCP servers from Claude Code config and pre-populates the MCP allowlist in `config.local.toml` +5. Auto-detects installed MCP servers from Claude Code/Gemini CLI config and pre-populates the MCP allowlist in `config.local.toml` 6. Detects the platform and installs the appropriate service manager integration: - Linux/WSL with systemd: installs user units, enables socket activation - macOS: installs launchd plist, loads agent - Fallback: prints instructions noting the shim will manage the daemon directly -7. Merges hook entries into Claude Code's `~/.claude/settings.json` (preserving existing hooks) -8. Prints a summary of what was configured +7. Auto-detects installed AI coding tools and registers hooks for each: + - Claude Code: merges into `~/.claude/settings.json` + - Gemini CLI: merges into `~/.gemini/settings.json` + - Codex: merges into `~/.codex/hooks.json` + - Preserves existing hooks in all tools +8. Prints a summary: which tools were detected, how many rules loaded, which adapter(s) registered -**Existing hooks:** Claude Code supports multiple hooks per event. `install.sh` appends security-hooks entries without removing existing user hooks. Both run on each tool call. +**Existing hooks:** All three tools support multiple hooks per event. `install.sh` appends security-hooks entries without removing existing user hooks. **Uninstall:** `./install.sh --uninstall` removes hook entries from settings and optionally removes the config directory and binary.