# Security Reviewer A **security analyst** for code changes. Where [`code-reviewer`](../code-reviewer/README.md) asks *"is this code good?"* and [`adversary`](../adversary/README.md) asks *"is this the code the plan asked for?"*, `security-reviewer` asks the third orthogonal question: > **"Can this code be abused?"** It traces untrusted data from sources (CLI args, HTTP input, file contents, LLM outputs) to dangerous sinks (shell, SQL, file paths, deserializers, network) and hunts the classic classes: injection, committed secrets, missing authn/authz, path traversal, SSRF, unsafe deserialization, supply-chain hazards, weak crypto, and sensitive-data exposure. ## Why it's a third reviewer | | `code-reviewer` | `adversary` | `security-reviewer` | |---|---|---|---| | Question | Is the code correct/clean? | Does the code match the plan? | Can the code be abused? | | Unit of analysis | per-file diffs (fan-out) | criteria ↔ diff mapping | **data flows across files** | | Blind spot it covers | slop, bugs, coupling | skipped criteria, scope drift | sourceβ†’sink paths, secrets, authz gaps | | Output | severity-tagged findings | `CONFORMS` / `DIVERGES` | `PASS` / `FAIL` (posture-gated) | Security flaws live in the path between an input in one file and a sink in another β€” exactly what a per-file review fans out past, and what acceptance criteria almost never mention. ## Posture-gated blocking Not every project needs production strictness β€” a POC shouldn't be blocked on missing rate limiting. The `security_posture` variable (or an explicit posture in the spawn prompt) sets the blocking threshold: | Posture | Blocks (FAIL) | Intended for | |---|---|---| | `prototype` | πŸ”΄ Critical only | POCs, spikes, demos, localhost-only tools | | `standard` (default) | πŸ”΄ Critical + 🟠 High | Anything deployed, shared, or built upon | | `hardened` | πŸ”΄ + 🟠 + 🟑 Medium | Auth, payments, secrets handling, public-facing, multi-tenant | Two invariants that do not bend with posture: 1. **Critical always blocks.** A committed secret is Critical in a prototype too β€” git history outlives the prototype. Same for code that endangers the host machine or third-party systems. 2. **Posture gates the verdict, not the report.** Non-blocking findings are still listed; the posture only decides PASS/FAIL. Severity itself is calibrated by **reachability Γ— blast radius**, not vulnerability class: SQL injection in a localhost-only debug script is not High, and a "small" secret in a repo is Critical. ## Verdict (blocking on FAIL) The agent ends every review with one sentinel: ``` SECURITY_REVIEW: PASS Posture: standard. Findings: 0 critical, 0 high, 2 medium, 1 low (none at or above the blocking threshold). ``` ``` SECURITY_REVIEW: FAIL Posture: standard. Findings: 0 critical, 1 high, 1 medium, 0 low. Blocking findings: 1. 🟠 Path traversal β€” export.rs:88 β€” 'name' from the HTTP body is joined into the output path with no canonicalization; '../../.ssh/authorized_keys' escapes the export root β€” canonicalize and verify the prefix before writing Non-blocking findings: 1. 🟑 Sensitive data in logs β€” auth.rs:41 β€” bearer token logged at debug level β€” redact before logging ``` A `FAIL` verdict **blocks** completion, exactly like adversary's `DIVERGES`. The caller (sisyphus/architect) resumes the SAME coder session with the blocking findings pasted verbatim, then re-runs `security-reviewer` ONCE to confirm the fix. Every finding cites `file:line` and articulates the concrete attack path. Vague findings are not emitted. ## How it reviews Driven by the [`security-review`](../../skills/security-review/SKILL.md) skill: 1. **Sourceβ†’sink tracing** per hunk: where does untrusted data enter, what does it reach, and is the mediation between them real (read the sanitizer, don't trust its name)? 2. **Ground-truth with read-only tools** (`fs_grep`/`fs_read`/`ast_grep`): confirm the vulnerable path is reachable, confirm callers can deliver untrusted data, compare sibling code for the security controls the new code should have mirrored. 3. **Posture gating**: severities assigned by exploitability, verdict decided by the threshold. It is **read-only** β€” it produces a verdict, never a fix. ## Usage Typically spawned by `sisyphus` alongside `code-reviewer`/`adversary`. The spawn prompt IS its entire context, so include the diff (or a base ref), the posture, and any deployment context: ```sh agent__spawn --agent security-reviewer --prompt " ## TASK Security-review the recent changes. Return PASS/FAIL. ## POSTURE standard # or: prototype (this is a throwaway POC) / hardened (this touches auth) ## DIFF Run get_diff (or --base main), or: ## DEPLOYMENT CONTEXT " ``` Direct invocation for ad-hoc use: ```sh coyote -a security-reviewer --agent-variable security_posture prototype \ --agent-variable project_dir /path/to/repo \ "Review the staged changes. This is a localhost-only spike." ``` ### Tools - `get_diff [--base ]` β€” staged β†’ unstaged β†’ `HEAD~1` fallback (or an explicit base/PR branch). - `get_changed_files [--base ]` β€” quick map of the attack surface. - Plus read-only `fs_*` and `ast_grep` for ground-truth checks. ## Related - [`security-review`](../../skills/security-review/SKILL.md) β€” the methodology it runs on. - [`code-reviewer`](../code-reviewer/README.md) β€” the quality reviewer it runs alongside. - [`adversary`](../adversary/README.md) β€” the plan-conformance reviewer it runs alongside.