Files
member-console/docs/agent-runner.md
T
cgalo5758 b245bdb0a9 Fix security audit findings from 2026-09-21 scan
Remediate six confirmed security issues: deployment-only config keys,
bounded provider responses, short-lived registration sessions, private
init file mode, FedWiki workflow authorization, and switch preview
gates.

- Add DeploymentOnly config key declaration; refuse runtime overrides
  for keys that decide where secrets are sent
- Create httplimit package; bound all provider response reads at 8 MiB
- Set fifteen-minute deadline on /register sessions
- Write mc-config.yaml with 0600 permissions
- Derive FedWiki workflow IDs from site IDs; re-authorize sites before
  mutating activities
- Apply switch authorization gates to the proration preview
2026-09-21 13:57:57 -05:00

167 lines
8.7 KiB
Markdown

---
title: "Agent Runner"
audience: [developer]
summary: "How models from other families review this repository: what contains them, how a task is shaped, and what a finding must carry before it counts."
---
# Agent runner
A repeatable method for putting this repository in front of models that did not
write it. Two lanes use it. The **security lane** audits a named slice of the
code against a threat model. The **ideation lane** answers a design brief with
a proposal and a mockup. Both run the same containment and the same rules about
what comes back.
The runner is [`scripts/agent-runner/`](../scripts/agent-runner/README.md),
which documents the commands and the environment variables. This page states
the method.
## The problem this solves
The model that built a flow cannot audit it. It knows which inputs it already
validated and which branch it already considered, so the gap it left is the gap
it cannot see. A model from another family lacks that memory, and it also
carries different priors: published evaluations put the same frontier model far
apart on different bug classes, so the family that misses a class is not the
family that catches it.
The cost of that instrument is over-reporting. Models reading code unaided
invent vulnerabilities, cite lines that do not say what they claim, and rate
hardening advice as critical. Every rule below exists to keep that cost
bounded.
## Containment
The model never sees the working tree. The runner unpacks `git archive HEAD`
into a disposable copy, mounts that copy as the container's working directory,
and deletes it at teardown. The model may edit anything it likes; the edits go
nowhere, and uncommitted work is not exposed.
The model container sits on a Docker network marked `internal`, which has no
route off the machine. Its only exit is a proxy sidecar that denies every host
except an allowlist of the model APIs. The arrangement is fail-closed: a
misconfigured proxy costs the run its network instead of leaking the code.
One secret reaches a container: the sign-in file of the coding agent that lane
runs. For opencode that is the auth file holding the model-provider keys,
mounted read-only. For Codex CLI it is a copy of the ChatGPT token in a
throwaway home directory, writable because codex rewrites the file when it
refreshes the token, and removed with the lane. No other credential, and no
live database, identity provider or payment surface, is in the run at all.
## Shaping a task
A task names a **small set of files**, states a **one-sentence threat model**,
and asks **falsifiable questions** that each end in a yes, a no, or an explicit
"undetermined" with the code that settles it. The questions are the floor: the
model then hunts freely inside the slice, in whatever category it finds
something.
A task is not a bug-class checklist. A checklist inherits the reviewing model's
blind spots, because a class the model scores badly on stays badly scored when
it is listed, and it anchors the model away from whatever the list omits. One
benchmark scored a frontier model at F1 0.000 on authentication failures while
the same model scored 0.689 on SSRF; a checklist would have hidden that gap
behind a line item marked done.
The system context is supplied verbatim rather than left to the model to
assemble. Models are poor at gathering their own context, so the auth model,
the request pipeline in its actual middleware order, and the trust boundaries
are written out as fact, with file and line references the model can verify,
and prepended to every task. Prescriptive about slice and stakes; open about
bug class.
That shape fits a model that cannot hold the repository: the slice bounds what
it must read, the questions guarantee coverage, and a fresh container per slice
keeps its context clean. For a model that can hold the repository and plan its
own coverage, the same shape costs something. The system context is the
builder's map, and it names which couplings are deliberate, so the auditor
inherits the builder's judgment about the one place a fresh reader might
disagree. Slice boundaries are where cross-cutting bugs hide: a finding that
needs the exemption list and a webhook receiver belongs to neither slice. The
**open brief** drops both: no map, no file list, no questions, one run over
the whole repository. `open/bare.md` says only what to do; `open/stakes.md`
adds what the system is and from which positions it is attacked. Both keep
`REPORT.md` whole, because the evidence toll constrains the report, not the
search, and a looser search makes the toll matter more. The two shapes are run
side by side and their findings combined, under the union rule below.
A third shape delegates both search and report to a vendor's own audit agent:
the **Codex Security** task runs OpenAI's plugin over the same disposable copy,
under the same containment, and the plugin writes its own artifacts (a
findings file with a schema, a coverage file, a generated report). It is a
different instrument, not a variant of ours: it validates and proposes fixes,
and its report contract is not `REPORT.md`, so a task runs one or the other,
never both. Its output enters the same adjudication as every other candidate
list.
The ideation lane shapes a task the same way: the brief names the surface, what
survives any redesign, and the constraints the repository enforces, and it does
not name candidate solutions. A brief that lists the shapes it expects gets
those shapes back.
## Method rules
- **Union, never intersection.** Findings from several models are combined, not
filtered by agreement. Coverage is the reason to run more than one model, and
consensus filtering throws that coverage away: a model has reported a real,
later-confirmed finding on one pass and reported nothing on the same slice on
the next, so requiring a second reporter would have discarded it.
- **No self-review inside a run.** A model never re-checks or argues with its
own findings in the same context. Self-refinement loops measurably raise the
count of reported critical vulnerabilities without raising the count of real
ones. Adjudication is a separate pass by a different reader, against the
source.
- **Every claim is spot-checked before it counts.** A finding is a claim about
this repository, and it is checked against this repository, at the cited
path and line, before it enters any report or ledger. Claims fail this check
in practice: one model reported a committed `node_modules` directory that
does not exist here.
- **Candidates are not findings.** What a run produces is a candidate list.
Only what survives adjudication and the spot check becomes an issue on
the forge tracker.
## The evidence toll
A finding that does not pay all four is deleted before the report is written.
1. An exact `path:line-range` **and a verbatim quote** of the code. A model
that cannot quote the line has not found the bug.
2. The **full path from attacker-controlled input to the dangerous operation**,
naming each function it passes through. "Probably reachable" is not a path.
3. The **preconditions**: what the attacker must already hold, such as no
session, a member session, an operator session, a webhook signing key, or
control of DNS.
4. A **failure scenario** with concrete inputs and state, and the wrong outcome
they produce.
Severity and difficulty are rated on two independent axes rather than combined
into a CVSS score, because the one number hides which of the two a reader
disagrees with. Hardening and defence-in-depth are reported in a separate
section from vulnerabilities, so advice cannot inflate the vulnerability count.
## Security audits: the scanners run first
The deterministic scanners run before any model reads the code, offline, over
the same snapshot; the model then triages their hits as one of its tasks. That
order is deliberate. A model adjudicating scanner output removes most of the
scanner's false positives at a small cost in recall, while a model reading code
unaided over-reports. [`testing.md`](testing.md) names the scanners and fixes
the order.
Scanner output is a lead, not a finding. A hit confirmed in the source becomes
a candidate and pays the evidence toll like any other; a hit the model judges
false is reported as false, with the reason.
## Definition of done for a review
1. Every task in the lane has run for every model in the set, and the run
manifest records the audited commit, the model ids and the per-lane budget.
2. Each candidate finding has been adjudicated against the source by a reader
who did not produce it.
3. Each surviving finding has been spot-checked at its cited path and line.
4. Survivors are filed as issues on the forge tracker; the rest are
dropped, and a finding dropped for being unreproducible is recorded as
such rather than silently deleted.
5. `teardown.sh` has run, and its residue check is clean.