feat(agents): security-auditor SAST gate + onboard/audit-delta grafts (verify-loops lot 3)

agents/security-auditor.md: fresh read-only-on-code SAST gate. Pinned
rulesets p/security-audit + p/secrets + p/owasp-top-ten (owasp REQUIRED —
measured: the 2-ruleset baseline misses SQLi + path-traversal entirely on
realistic Flask code), never --config auto, never auto login (BDR-048).
Severity map: secrets ERROR → CRITICAL, other ERROR → HIGH (block),
WARNING/INFO → reported. gate mode (diff, no Write) vs audit mode (Write
only to REPORT, rule-locked). DEGRADED (semgrep absent) still runs the
checklist and still blocks — never a vacuous pass (LRN-048). Anti-gaming:
a new un-gated nosemgrep suppression is BLOCKING. PROOF mandatory, mute
auditor never a PASS, blind (no iteration history), blocks HIGH/CRITICAL
only (LRN-047).

Grafts: onboard STEP 6 L3a dispatches it in audit mode (report
.onboard-audit/semgrep.md) in BOTH gstack branches — complement to cso
(cso is a gstack submodule, unmodifiable); synthesis picks it up via the
existing .onboard-audit/ sweep. audit-delta security axis runs the SAST
pass first, folds findings into the existing gate/fix/re-verify flow.

lib/tests/security-auditor.test.sh: 28 structure locks green, shellcheck
clean. Behavioral dogfood (fresh agents on a planted fixture):
BLOCK(9) on the vuln commit (2 secrets→CRITICAL, semgrep+checklist
complementarity — checklist caught the 6 semgrep missed off-context);
BLOCK(1) on a new nosemgrep suppression (understood semgrep's 0 was the
mask); DEGRADED → BLOCK(7) on grep-detectable secrets with semgrep hidden.
FP measured on real repos (faunosteo, game): owasp adds only hygiene
findings, contained by diff-scoping.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XpphkdTosUzokBDNG7PToS
This commit is contained in:
Bastien Chanot
2026-07-03 19:13:02 +02:00
co-authored by Claude Opus 4.8
parent ff13abfda5
commit 2b297bd44a
4 changed files with 278 additions and 8 deletions
+15 -6
View File
@@ -221,12 +221,21 @@ Then offer to capitalize (per CLAUDE.md): recurring finding patterns →
## Axis specs (subagent prompts)
- **security** — scoped to the delta: hardcoded secrets/tokens/keys (also
in comments), injection (SQL/XSS/command — string concat into
queries/shells), authN/authZ gaps on new endpoints, fail-open error
paths, secrets/PII in logs, new dependencies in lockfiles (name them +
known CVEs), unguarded destructive shell (`rm -rf` with unquoted or
un-`:?`-guarded vars).
- **security** — FIRST run the semgrep SAST pass, THEN the reasoned checks
below on the same delta (the SAST is a deterministic floor, the reasoned
pass covers what grep/rules miss):
```
Agent(subagent_type="security-auditor", description="audit-delta security — semgrep SAST",
prompt="MODE: audit\nSCOPE: <delta file list>\nREPORT: .claude/audits/.audit-delta-semgrep.md\nFollow agents/security-auditor.md exactly. Pinned rulesets, no login. Write ONLY to REPORT. End with REPORT_WRITTEN: <path>.")
```
Fold its BLOCKING (CRITICAL/HIGH) + REPORTED findings into this axis'
finding list before the 3c gate. semgrep ABSENT → DEGRADED (checklist
only) is surfaced, not a blocker. Reasoned checks (also scoped to the
delta): hardcoded secrets/tokens/keys (also in comments), injection
(SQL/XSS/command — string concat into queries/shells), authN/authZ gaps
on new endpoints, fail-open error paths, secrets/PII in logs, new
dependencies in lockfiles (name them + known CVEs), unguarded destructive
shell (`rm -rf` with unquoted or un-`:?`-guarded vars).
- **errors** — bugs in changed code: logic errors, off-by-one, unhandled
edge cases (empty/null/unicode/concurrent), race conditions, swallowed
errors, resource leaks (missing trap/close/finally). Improvements only
+26 -2
View File
@@ -486,6 +486,29 @@ bash $HOME/.claude/lib/toggle-external.sh list 2>/dev/null | grep -E "^gstack\s+
)
```
#### Dispatch semgrep SAST — `security-auditor` (TOUJOURS, complément de cso)
En complément de cso (ON) OU du fallback (OFF) — un moteur SAST déterministe
à côté de l'audit grep/raisonné. cso est un submodule gstack non modifiable ;
semgrep vit dans cet agent local. Lancé dans les DEUX branches gstack.
```
Agent(
subagent_type="security-auditor",
description="Onboard — semgrep SAST audit (report-only)",
prompt="""
MODE: audit
SCOPE: <PROJECT_ROOT>
REPORT: <PROJECT_ROOT>/.onboard-audit/semgrep.md
CONTEXT: <PROJECT_ROOT>/.onboard-audit/archetype-context.md
Follow agents/security-auditor.md exactly. Pinned rulesets only, no login.
Write ONLY to the REPORT path. End stdout with REPORT_WRITTEN: <path>.
"""
)
```
Si semgrep absent → l'agent rend DEGRADED (checklist seule) + recommande
`make plugin` ; NON bloquant en onboard (audit, pas gate).
#### Dispatch doc-syncer (si `doc` dans audit_stack)
```
Agent(
@@ -511,9 +534,10 @@ Agent(
### Après les 3 dispatches
Attendre la fin des 3 subagents. Vérifier que les 3 fichiers existent et sont non vides :
Attendre la fin des subagents. Vérifier que les fichiers existent et sont non vides
(semgrep.md inclus — DEGRADED reste non vide : il porte le résultat checklist) :
```bash
for f in .onboard-audit/{code-clean,cso,doc}.md; do
for f in .onboard-audit/{code-clean,cso,semgrep,doc}.md; do
[ -s "$f" ] && echo "OK $f" || echo "MISSING $f"
done
```