4.6 KiB
name, description
| name | description |
|---|---|
| infra-repo-security-review | Full-repo security review of an infra/config repo (serverAppConfigs-shaped: shell deployers, quadlets, systemd, Caddy edge, bats/Playwright) — slice fan-out, mechanical censuses, and subagent-claim verification. |
Whole-repo security review (infra/config repo)
For "full security review of this repo" on a deployment-config repo (shell scripts + podman quadlets + systemd units + Caddy + tests). NOT for a diff review — that is code-review. NOT for a secrets-only question — that is git-repo-secret-audit.
1. Slice, then fan out in ONE task batch
Five security-reviewer slices, each with the repo's own laws pre-injected in context so no agent re-derives them (AGENTS.md policy text + exclusions: docs prose, .AGENTTRASH/**, binaries, package-lock.json):
- Shell — all
*.sh+.githooks/+tests/run.sh: injection, quoting,mktempvs/tmp/fixed,set -e,curl -f, secrets in argv,rm/git clean/systemctl disable. - Python — every
*.py:subprocess(shell=True),verify=False, unboundedread(), path traversal on request-supplied filenames, fixed/tmpwrites, which HTTP servers exist and which are unauthenticated. - Container + systemd posture — every
*.container/.network/.service/Containerfile. - Edge + web — Caddyfile vhost inventory, Anubis/Authentik chain, response headers, static-asset XSS sinks, service configs (
limiter.toml,prometheus.yml,alertmanager.yml). - Tests — vacuous assertions + credentials/secrets in fixtures + harness safety.
Set a yield deadline in the prompt. Subagents here get killed at task.maxRuntimeMs (30 min) and the dead ones produced zero output. When one runs long, write early: write agent://<id> "yield now, unconfirmed items prefixed UNVERIFIED" — the resumed agent returns its partial report instead of losing it. Two of four needed this.
2. Verify every citation before reporting
verify-subagent-review-findings applies in full: re-grep each cited file:line and compare text. Expect ~40% of lines to be off (e.g. an agent described a 13-line hook; the file is 6) and several files to be hallucinated entirely (.githooks/compose/*.container did not exist). Discard mismatches; publish a Discarded subagent claims section. Re-grep your own citations too — two of mine were off by two lines and had to be corrected in an addendum.
3. Mechanical censuses (cheap, high yield — run these yourself)
- Port binding: every
^PublishPort=line, classify127.0.0.1:/192.168.0.x:/ no address prefix (= implicit0.0.0.0). The unprefixed set is the auth-bypass surface: a LAN client hits the container port directly and skips the Caddy→Anubis→Authentik chain that the vhost enforces. Report as one consolidated finding listing all unprefixed lines, and cite the repo's own correct examples (127.0.0.1:9090,192.168.0.143:8080) as the fix pattern. - Image pinning: count
^Image=lines with vs without@sha256:(this repo: 4 of 54 pinned) — floating tags +Pull=never= unreproducible edge. - Secret modes: grep every
chmod 600in the deployer and diff against every file it writes from an.env; a>redirect without thechmodis the finding (the Caddy/Porkbun env file). - Systemd hardening:
User=/ProtectSystem/PrivateTmp/NoNewPrivilegesper unit, andExecStartPre=+lines — a+hook on a root unit running a checkout-writable script is a local privesc. - Vhost table: vhost → upstream → guarded? → exemption documented? Cross-check each upstream against the quadlet
PublishPortlist; unguarded vhost with no exemption comment is a policy violation even when the service self-authenticates. - Hook wiring:
git config core.hooksPath+ls "$(git rev-parse --git-path hooks)". A repo can ship.githooks/pre-committhat git never executes — and its own test can assert the file is executable and still miss that it isn't installed. - Secret sweep:
git cat-file --batch-all-objects --batch(all history + dangling), match known values read from~/.config/secrets; reportprefix…(len N, sha 10)only, never the value. Watch for the split-concat disguise (PW="as""set"): their own regex can't see it, butgrep -Fon a fragment finds it and history keeps it forever → rotation required.
4. Report
Severity-ranked table (Critical→Low, one line each: location + flaw), then prose detail for Critical/High only, then the Discarded-claims section, then remaining coverage gaps, then an ordered fix set and an offer to branch. Do not create a report file unless asked — chat is the deliverable.