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
+78
View File
@@ -0,0 +1,78 @@
#!/usr/bin/env bash
# ============================================================
# Structure locks — security-auditor agent + grafts (lot 3)
# Deterministic greps on load-bearing doctrine: an edit that
# drops one (pinned rulesets, DEGRADED-still-checks, PROOF,
# block-HIGH-only, anti-gaming, the two SKILL grafts) reds here.
# ============================================================
set -u
REPO="$(cd "$(dirname "$0")/../.." && pwd)"
AGT="$REPO/agents/security-auditor.md"
ONB="$REPO/skills/onboard/SKILL.md"
ADL="$REPO/skills/audit-delta/SKILL.md"
PASS=0; FAIL=0
tf() { # tf <label> <file> <fixed-string>
if grep -qF -- "$3" "$2" 2>/dev/null; then
echo " PASS $1"; PASS=$((PASS+1))
else
echo " FAIL $1 — missing: $3"; FAIL=$((FAIL+1))
fi
}
tr_() { # tr_ <label> <file> <ERE>
if grep -qE -- "$3" "$2" 2>/dev/null; then
echo " PASS $1"; PASS=$((PASS+1))
else
echo " FAIL $1 — no match: $3"; FAIL=$((FAIL+1))
fi
}
tn() { # tn <label> <file> <ERE> (must NOT match)
if grep -qE -- "$3" "$2" 2>/dev/null; then
echo " FAIL $1 — forbidden match: $3"; FAIL=$((FAIL+1))
else
echo " PASS $1"; PASS=$((PASS+1))
fi
}
echo "── security-auditor.md locks ──"
if [ -f "$AGT" ]; then
echo " PASS agent exists"; PASS=$((PASS+1))
else
echo " FAIL agent missing: $AGT"; FAIL=$((FAIL+1))
fi
tr_ "frontmatter name" "$AGT" "^name: security-auditor$"
tr_ "tools incl Write (audit)" "$AGT" "^tools: Read, Grep, Glob, Bash, Write$"
tf "verdict grammar" "$AGT" "SECURITY — VERDICT: PASS | BLOCK(n) | ERROR(<reason>)"
tf "ruleset security-audit" "$AGT" "p/security-audit"
tf "ruleset secrets" "$AGT" "p/secrets"
tf "ruleset owasp required" "$AGT" "p/owasp-top-ten"
tf "no config auto stated" "$AGT" "never \`--config auto\`"
tf "no auto login" "$AGT" "never \`semgrep login\`"
tf "secrets to CRITICAL" "$AGT" "p/secrets | CRITICAL"
tf "block ERROR threshold only" "$AGT" "blocking threshold is ERROR"
tf "medium low reported" "$AGT" "MEDIUM/LOW are REPORTED, never"
tf "degraded still checks" "$AGT" "STILL RUN STEP 3"
tf "degraded vacuous pass named" "$AGT" "vacuous pass"
tf "anti-gaming suppression" "$AGT" "NEW suppression comment"
tf "anti-gaming micro-gate" "$AGT" "[gated <date>]"
tf "proof mandatory" "$AGT" "\`PROOF\` is MANDATORY"
tf "mute never a pass" "$AGT" "NEVER a PASS"
tf "write rule-locked audit" "$AGT" "writable path is \`REPORT\`"
tf "gate mode write forbidden" "$AGT" "\`Write\` is FORBIDDEN in this mode"
tf "blind no history" "$AGT" "NEVER receive iteration history"
tf "reverify request first" "$AGT" "re-verify the REQUEST first"
tf "max 3 security iters" "$AGT" "Max 3 security iterations"
echo "── onboard graft locks ──"
tf "onboard dispatches auditor" "$ONB" "subagent_type=\"security-auditor\""
tf "onboard report path" "$ONB" ".onboard-audit/semgrep.md"
tf "onboard verify incl semgrep" "$ONB" "code-clean,cso,semgrep,doc"
echo "── audit-delta graft locks ──"
tf "audit-delta dispatches" "$ADL" "subagent_type=\"security-auditor\""
tf "audit-delta semgrep first" "$ADL" "FIRST run the semgrep SAST pass"
echo ""
echo "security-auditor structure locks: $PASS pass, $FAIL fail"
[ "$FAIL" -eq 0 ]