hl-security-audit · git:20261013.59162d2 · 2026-10-13 · sha256 239d16cc88deba07
hl-security-audit git:20261013.59162d2A
Immutable. This exact content never changes and is served at /api/v1/blob/239d16cc88deba07.
--- name: hl-security-audit description: "Complete read-only security audit of a codebase or a path in it. Runs real scanners for evidence, traces untrusted input to dangerous sinks, scores by CWE/CVSS, writes a prioritized report. Use when the user asks for a security review or audit, or whether an attacker can abuse a repository, file, or endpoint. Not for reviewing a single diff (use `hl-code-review`)." --- # Security Audit Input: `[quick|standard|deep] [<path>] [--report]` Audit a whole codebase for vulnerabilities. Assume the worst: the input is malicious, the auth check fails open, the secret is in the repo. Every value crossing a trust boundary is an attack until proven safe. Read-only on source; the report is the deliverable. Two lanes: the mechanical lane runs real read-only scanners whose output is evidence; the review lane judges whether a hit is reachable and exploitable, after you open the code. A finding ships only when a scanner hit or a traced path backs it and you have read the cited line. There is no independent verifier; the verifier is you reading the code with the scanner output in hand. Confidence an LLM assigns itself is not evidence. ## Stance - Every input is hostile. Trace each value from where it enters (request body, query param, header, upload, queue message, deserialized blob, webhook) to where it lands (SQL, shell, file path, template, `eval`, response HTML, redirect, auth decision). - Hunt the unstated assumption: "user is always logged in", "this ID belongs to the caller", "this header is trusted". - Silent failures fail open: swallowed exceptions, unawaited promises, unchecked null, ignored returns, auth that falls through to allow. ## Hard rules 1. Never modify source. The only file you may write is the report (Phase 5). 2. Read-only execution only. Run scanners, audits, `grep`, `tsc --noEmit`. Never install, build, write files other than the report, commit, or hit the network for side effects. 3. Never reproduce a secret value. Cite `file:line` and credential type, recommend rotation. 4. Vet before reporting. Scanners over-report; LLM traces hallucinate. For every CRITICAL and HIGH, open the cited code and confirm the path. Fix line numbers, drop false positives, merge cross-lane duplicates. 5. If `security-exceptions.json` exists, read it. Matching findings go to the Accepted risk section with rationale and expiry, not the main list. ## Phase 1, Recon and threat model Read README, AGENTS.md or the equivalent instruction file, config, CI. Identify languages, frameworks, package managers. Find the trust boundaries: every place untrusted input enters. Note existing defenses (parameterized ORMs, framework auto-escaping) so you don't flag what is already safe. Name the crown jewels: auth, payment, PII, admin paths, key material. Write a one-paragraph threat model; every later phase scopes to it. A `<path>` argument scopes the audit to that subtree. ## Phase 2, Mechanical pass Detect the stack, run the read-only scanners that apply, capture raw output as evidence. Skip any tool not installed and record it in the report: a silent gap reads as "clean" when it isn't. - Dependencies and CVEs: `npm audit --json`, `pnpm audit`, `pip-audit`, `composer audit`, `cargo audit`, `osv-scanner`, `govulncheck`. Group by severity; separate direct from transitive. - Secrets: `gitleaks detect` or `trufflehog`, else grep key/token/password assignment patterns. Report `file:line` and type, never the value. - SAST: `semgrep --config auto`, `bandit`, `gosec`, `brakeman`, eslint security plugins, whichever the stack supports. - Pattern grep where no SAST covers it: string-built SQL, `exec`/`system` with variables, `eval`/`unserialize`/`pickle.load` on input, `dangerouslySetInnerHTML`, XML parse without entity restrictions, `md5`/`sha1` on passwords, `rand`/`mt_rand` for security values, file ops on user-controlled paths. ## Phase 3, Review pass For each mechanical hit and each entry point, trace untrusted input to its sink and decide whether it is reachable and exploitable. A hit in dead code or behind a hard auth gate is not a finding. For a full repo at `standard` or `deep`, fan out parallel read-only subagents, one per category, when the host supports them; otherwise review the categories yourself, one at a time. Subagents don't inherit this context: each prompt carries the threat model, the recon facts, and the category's mechanical hits, and returns candidate findings only (no fixes, no file dumps) as `file:line | <input → sink> | category | why exploitable | confidence H/M/L`. Depth follows the level (default `standard`): | | quick | standard | deep | |---|---|---|---| | Mechanical | deps + secrets | + SAST + pattern grep | + pattern grep on the whole tree | | Review categories | injection, authn/authz, secrets | all 8 | all 8 | | Breadth | entrypoints | input paths | everywhere | | Findings | CRITICAL + HIGH | + MEDIUM | + MEDIUM, + Investigate | ### Categories 1. Injection (CWE-89/78/611/90): SQLi, command injection, XXE, NoSQL/LDAP injection, template injection, path traversal. 2. Authn/authz (CWE-287/639/862): auth bypass, missing ownership check (IDOR), privilege escalation, session/JWT flaws, checks that fail open. 3. Crypto and secrets (CWE-798/327/338): hardcoded keys, weak algorithms, bad key storage, predictable randomness for security values, cert validation bypass. 4. Code execution and deserialization (CWE-502/94): RCE via unsafe deserialization (pickle, unsafe YAML, gadget chains, `phar://`), `eval` on untrusted input. 5. XSS (CWE-79): reflected, stored, DOM. React/Angular only via `dangerouslySetInnerHTML` or `bypassSecurityTrust*`. 6. Data exposure (CWE-200/532): secrets or PII logged or returned, debug leakage, over-exposed endpoints. 7. SSRF, host or protocol control only (CWE-918). Path-only control is not a finding. 8. Vulnerable dependencies: a CVE from Phase 2 is a finding only if its vulnerable code path is reachable from the app. ## Phase 4, Vet, score, filter Vet per hard rule 4. Score each finding by impact times likelihood into CRITICAL, HIGH, or MEDIUM, and map its CWE; drop anything lower, and at `quick` drop MEDIUM too. Dedupe across lanes: a SAST hit and a manual trace of the same line are one finding. Apply the filter below; default to dropping when the path isn't concrete. A CRITICAL or HIGH finding not confirmed, or any finding below confidence H, goes to Investigate at `deep` and is dropped otherwise. Drop automatically: - DoS and resource exhaustion (memory, CPU, file descriptors). - Secrets at rest on disk if otherwise secured. - Rate limiting and overload. - Missing validation on non-security-critical fields with no proven impact. - GitHub Action workflow injection unless concretely triggerable by untrusted input. - Absence of a hardening measure. Concrete vulns only. - Theoretical race and timing issues. Concrete and exploitable only. - Memory safety (overflow, use-after-free) in memory-safe languages (Rust, Go, JS, Python). - Findings only in unit-test or fixture files. - Log spoofing and logging unsanitized non-secret input. - SSRF controlling only the path. - User-controlled content in AI system prompts. - Regex injection and regex DoS. - Findings in documentation files. - Absence of audit logging. Precedents: - Logging a high-value secret in plaintext is a vuln. Logging a URL is safe. - UUIDs are unguessable; no need to validate them. - Env vars and CLI flags are trusted. An attack that needs to control them is invalid. - Tabnabbing, XS-Leaks, prototype pollution, and open redirect only at very high confidence. - React and Angular are XSS-safe unless using the unsafe HTML methods above. - Client-side missing authn/authz is not a vuln. The backend owns validation. - Command injection in a shell script only with a concrete untrusted-input path. - A MEDIUM ships only if obvious and concrete. ## Phase 5, Report With `--report`, or for a whole-repository `standard` or `deep` run, write `security-audit.md` in the repository root. Otherwise print the report. ```markdown # Security Audit: <repo> Verdict: <count of findings per severity, or the found-nothing line from the Coverage gate> Date: <date> · Commit: <short SHA> · Scope: <path / whole repo> · Depth: quick|standard|deep Scanners: npm audit ✓ · semgrep ✓ · gitleaks ✓ · bandit n/a (mark what didn't run) Assurance: HIGH | MEDIUM | LOW: what was actually verified, and the blind spots a reader must not assume are clean. ## Executive summary 3 to 5 bullets a decision-maker can act on. Each: the risk, the impact if unaddressed, the fix. ## Findings | # | Severity | Finding (CWE) | Evidence | Fix | |---|---|---|---|---| | 01 | CRITICAL | SQLi (CWE-89): sort param concatenated into ORDER BY | api/users.py:42 + semgrep | Parameterize; whitelist columns | ## Detail ### Vuln 1: SQL Injection, api/users.py:42 Severity CRITICAL · CWE-89 · Confidence H Input: request.args["sort"] (HTTP query param) Path: query param → string-built ORDER BY → DB Exploit: ?sort=id;DROP TABLE users-- runs attacker SQL on prod. Fix: parameterized ordering; whitelist columns (see api/orders.py:88). ## Dependencies | Package | CVE (severity) | Direct/Transitive | Reachable | Fix version | |---|---|---|---|---| ## Accepted risk Findings matched by security-exceptions.json, with rationale and expiry. ## Investigate `deep` only. Candidates not confirmed or below confidence H, with what would confirm each. ## Considered and dismissed Candidates investigated and dropped, with the reason, so the next audit doesn't re-raise them. ``` ## Coverage gate Set Assurance from what actually ran, never implying safety you didn't verify. - HIGH: SAST plus dependency and secret scans ran on the full scope, and you hand-vetted every CRITICAL and HIGH. - MEDIUM: some scanners ran, or scope was partial. Name what wasn't covered. - LOW: no SAST or dependency scanner was available, so the audit is LLM-only and unconfirmed by tooling. List the tools to install (`semgrep`, the stack's dependency auditor, `gitleaks`) and rerun. Found nothing? You may only write `No high-confidence exploitable vulnerabilities in <scope>` at HIGH assurance. Below HIGH, write `No vulnerabilities surfaced by the checks that ran` and list the unchecked surface. Don't manufacture findings to look thorough, and don't let "found nothing" and "barely looked" read the same.