From 51f9846ac69605fb10a341d17ffbc730e5b701e1 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 21 Oct 2026 04:37:51 -0700 Subject: [PATCH] live: heartbeat sync 2026-10-21T04:37:51-07:00 (Refs #11) --- .../infra-repo-security-review/SKILL.md | 34 +++++++++++++++++++ .../state.json | 2 +- .../state.json | 2 +- .../state.json | 2 +- .../state.json | 2 +- .../state.json | 2 +- 6 files changed, 39 insertions(+), 5 deletions(-) create mode 100644 agent/managed-skills/infra-repo-security-review/SKILL.md diff --git a/agent/managed-skills/infra-repo-security-review/SKILL.md b/agent/managed-skills/infra-repo-security-review/SKILL.md new file mode 100644 index 0000000..8a55d01 --- /dev/null +++ b/agent/managed-skills/infra-repo-security-review/SKILL.md @@ -0,0 +1,34 @@ +--- +name: infra-repo-security-review +description: "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`): + +1. **Shell** — all `*.sh` + `.githooks/` + `tests/run.sh`: injection, quoting, `mktemp` vs `/tmp/fixed`, `set -e`, `curl -f`, secrets in argv, `rm`/`git clean`/`systemctl disable`. +2. **Python** — every `*.py`: `subprocess(shell=True)`, `verify=False`, unbounded `read()`, path traversal on request-supplied filenames, fixed `/tmp` writes, **which HTTP servers exist and which are unauthenticated**. +3. **Container + systemd posture** — every `*.container`/`.network`/`.service`/Containerfile. +4. **Edge + web** — Caddyfile vhost inventory, Anubis/Authentik chain, response headers, static-asset XSS sinks, service configs (`limiter.toml`, `prometheus.yml`, `alertmanager.yml`). +5. **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://` "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, classify `127.0.0.1:` / `192.168.0.x:` / **no address prefix** (= implicit `0.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 600` in the deployer and diff against every file it writes from an `.env`; a `>` redirect without the `chmod` is the finding (the Caddy/Porkbun env file). +- **Systemd hardening**: `User=`/`ProtectSystem`/`PrivateTmp`/`NoNewPrivileges` per unit, and `ExecStartPre=+` 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 `PublishPort` list; 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-commit` that 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`; report `prefix…(len N, sha 10)` only, never the value. Watch for the **split-concat disguise** (`PW="as""set"`): their own regex can't see it, but `grep -F` on 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. diff --git a/agent/memories/sharpshooter/wt-20261019-125509-7c8a99d-me8scwe1t2nf/state.json b/agent/memories/sharpshooter/wt-20261019-125509-7c8a99d-me8scwe1t2nf/state.json index cc203b4..07ddb59 100644 --- a/agent/memories/sharpshooter/wt-20261019-125509-7c8a99d-me8scwe1t2nf/state.json +++ b/agent/memories/sharpshooter/wt-20261019-125509-7c8a99d-me8scwe1t2nf/state.json @@ -1,6 +1,6 @@ { "v": 1, - "lastConsolidatedAt": 1792578705824, + "lastConsolidatedAt": 1792582605853, "lastResult": { "at": 1792573818108, "sessions": 1, diff --git a/agent/memories/sharpshooter/wt-20261020-125416-6d1fc1f-3cjcsma2m7gk5/state.json b/agent/memories/sharpshooter/wt-20261020-125416-6d1fc1f-3cjcsma2m7gk5/state.json index 032ed95..686c008 100644 --- a/agent/memories/sharpshooter/wt-20261020-125416-6d1fc1f-3cjcsma2m7gk5/state.json +++ b/agent/memories/sharpshooter/wt-20261020-125416-6d1fc1f-3cjcsma2m7gk5/state.json @@ -1,6 +1,6 @@ { "v": 1, - "lastConsolidatedAt": 1792578921668, + "lastConsolidatedAt": 1792582581678, "lastResult": { "at": 1792574016725, "sessions": 1, diff --git a/agent/memories/sharpshooter/wt-20261020-144605-e7c60f6-31xsw83t7yjc6/state.json b/agent/memories/sharpshooter/wt-20261020-144605-e7c60f6-31xsw83t7yjc6/state.json index 69c3dca..ccc49ae 100644 --- a/agent/memories/sharpshooter/wt-20261020-144605-e7c60f6-31xsw83t7yjc6/state.json +++ b/agent/memories/sharpshooter/wt-20261020-144605-e7c60f6-31xsw83t7yjc6/state.json @@ -1,6 +1,6 @@ { "v": 1, - "lastConsolidatedAt": 1792578938739, + "lastConsolidatedAt": 1792582538777, "lastResult": { "at": 1792556461435, "sessions": 1, diff --git a/agent/memories/sharpshooter/wt-20261020-155034-1b9ba40-iffz605wjz2v/state.json b/agent/memories/sharpshooter/wt-20261020-155034-1b9ba40-iffz605wjz2v/state.json index e5c7a86..e5dc0ba 100644 --- a/agent/memories/sharpshooter/wt-20261020-155034-1b9ba40-iffz605wjz2v/state.json +++ b/agent/memories/sharpshooter/wt-20261020-155034-1b9ba40-iffz605wjz2v/state.json @@ -1,6 +1,6 @@ { "v": 1, - "lastConsolidatedAt": 1792578870072, + "lastConsolidatedAt": 1792582470124, "lastResult": { "at": 1792566095222, "sessions": 1, diff --git a/agent/memories/sharpshooter/wt-20261021-021543-6d1fc1f-o9a35ouamphx/state.json b/agent/memories/sharpshooter/wt-20261021-021543-6d1fc1f-o9a35ouamphx/state.json index 79a1fb7..fa870ad 100644 --- a/agent/memories/sharpshooter/wt-20261021-021543-6d1fc1f-o9a35ouamphx/state.json +++ b/agent/memories/sharpshooter/wt-20261021-021543-6d1fc1f-o9a35ouamphx/state.json @@ -1,4 +1,4 @@ { "v": 1, - "lastConsolidatedAt": 1792578883678 + "lastConsolidatedAt": 1792582483704 }