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 62b1963..041dd17 100644 --- a/docs/superpowers/specs/2026-03-26-security-hooks-design.md +++ b/docs/superpowers/specs/2026-03-26-security-hooks-design.md @@ -58,7 +58,7 @@ security-hooks/ │ ├── bash.rules # bash command rules │ ├── edit.rules # file edit rules │ ├── mcp.rules # MCP tool rules -│ ├── post.rules # post-tool-use checks +│ ├── post.rules # post-tool-use checks (deferred, reserved) │ └── validators/ # complex Elixir validators │ ├── unknown_executable.ex │ ├── dependency_mutation.ex @@ -149,7 +149,14 @@ PreToolUse ask (tier: suspicious): } ``` -PostToolUse block (note: PostToolUse uses top-level `decision`/`reason` fields, unlike PreToolUse which nests under `hookSpecificOutput`): +PostToolUse responses (note: PostToolUse uses top-level `decision`/`reason` fields, unlike PreToolUse which nests under `hookSpecificOutput`): + +PostToolUse allow (no issues found): +```json +{} +``` + +PostToolUse block: ```json { "decision": "block", @@ -202,7 +209,7 @@ comment := '#' rule := tier SP name NL clauses tier := "block" | "suspicious" name := '"' '"' -clauses := matcher nudge [condition]* +clauses := matcher nudge matcher := match | match_any | match_not_in | validator match := INDENT "match " NL match_any := INDENT "match_any" NL (INDENT2 NL)+ @@ -279,7 +286,7 @@ Rules are evaluated in file order. First match wins. Place specific rules before **Tier: suspicious** - Edits to CI/CD config: `.github/workflows/`, `.gitlab-ci.yml`, `Jenkinsfile` - Edits to `Dockerfile`, `docker-compose.yml` -- Edits to lockfiles: `package-lock.json`, `mix.lock`, `Cargo.lock`, `poetry.lock` +- Edits to lockfiles: `package-lock.json`, `pnpm-lock.yaml`, `yarn.lock`, `mix.lock`, `Cargo.lock`, `poetry.lock`, `Gemfile.lock`, `go.sum`, `composer.lock` - Dependency field changes in manifest files (validator: `DependencyMutation`) ### mcp.rules @@ -344,6 +351,9 @@ sensitive = [ "/etc/passwd", ] +[meta] +version = "1.0.0" + [daemon] idle_timeout_minutes = 30 log_format = "jsonl" @@ -363,7 +373,7 @@ Merges on top of `config.toml`: **Starting:** The shell shim starts the daemon on first hook call. The daemon writes its PID to `$XDG_RUNTIME_DIR/security-hooks/pid` (Linux/WSL) or `$TMPDIR/security-hooks/pid` (macOS) and opens the Unix socket. First-ever startup with a Burrito binary may take 1-3 seconds (unpacking); subsequent starts are ~300ms (BEAM boot). The install script pre-warms the daemon to avoid first-call latency. -**Health check:** The shim checks socket connectivity. If the PID file exists but the socket is dead, the shim kills the stale process and restarts. +**Health check:** The shim checks socket connectivity. If the PID file exists but the socket is dead, the shim kills the stale process and restarts. If two sessions race to start the daemon simultaneously, the second will get `EADDRINUSE` — the shim handles this by retrying the socket connection (the first daemon won the race and is now available). **Idle shutdown:** The daemon exits after 30 minutes of inactivity (configurable via `daemon.idle_timeout_minutes`). Handles `SIGTERM` gracefully. @@ -376,6 +386,8 @@ Merges on top of `config.toml`: {"ts":"2026-03-26T14:02:05Z","event":"PreToolUse","tool":"Bash","input":"mix test","rule":null,"decision":"allow"} ``` +Log rotation and size limits are deferred — users can manage this with external tools (logrotate, etc.) since the log path is well-defined. + Future: streaming connectors for centralized logging (stdout, webhook, syslog). ## Installation