# Threat model contract

The run-scoped threat model is the frame every security finding is calibrated against. It is produced by the security-auditor at Phase 3 Step 2.7 (or by `/multi-agent:security-review` running standalone), read by any later step that revisits security, and mirrored to `state.threatModel.path` so a resume re-uses it instead of re-deriving it.

## Where it lives

`.pipeline/threat-model.md` in the run's worktree, keyed to repo + branch. One per run. If a fresh one already exists for this repo+branch (a prior step or a Phase 1 pass wrote it), read it; do not overwrite. If it is absent, produce it before emitting any finding.

Mirror the resolved path and a content hash to `state.threatModel = { path, sha, producedAt, producedBy }` so resume and Phase 5 can find it without re-reading the tree.

## The four sections (all required, in order)

A threat model with a missing section is not a threat model; the auditor treats a missing section as "produce it," not "skip it."

### 1. Attacker

The one realistic adversary for THIS change. Name it concretely: an unauthenticated internet caller, an authenticated low-privilege user, a malicious or compromised dependency, a co-located app on the device, a user with physical access. Not "attackers" in the abstract - the specific actor whose capability makes this diff interesting. If the diff has no plausible attacker, say so; that is a valid, short threat model and most findings then cap at `suggestion`.

### 2. Trust boundaries

Where untrusted data crosses into trusted code within the changed surface: a request body or query parameter, a deep link or universal link, a WebView `postMessage`, a file the app did not write, an environment variable an attacker can set, a response from a third-party service treated as safe. List the boundaries the diff touches, each with the `file:line` where the crossing happens.

### 3. Attack surface

What this diff actually added or touched: a new endpoint, a new query or ORM call, a new deserialization, a new permission, a new dependency, a new crypto usage, a new storage write. A finding outside this surface is out of scope unless the diff made it reachable - and if it did, say how. This section is what keeps the audit anchored to the change instead of drifting into a whole-repo review.

### 4. Severity calibration

The assumption each severity rests on, stated so a reader disputes the assumption rather than the number. "Critical assumes this route is unauthenticated in production; if it is admin-only the same finding is medium." "High assumes the secret reaches a log that ships off-device." Every `blocking` finding must trace to an assumption named here; a blocker resting on an unstated assumption is miscalibrated.

## Shape

Plain Markdown, four `##` sections with those names, human-readable. It is evidence for a person and context for the auditor, not a machine artifact - no schema. Keep it short: a page, not a report. Cite `file:line` where a boundary or surface item has one.

## What it is not

- Not a whole-repo model. It is scoped to the diff under review.
- Not a dynamic test plan. This is a static, read-only posture: no live target, no payloads, no exploitation. `fixVerification` on a finding names the empirical check a human or a later dynamic pass would run; the threat model does not run it.
- Not embedded in the analysis document. The security-review command must run standalone, so the model lives in its own file and is produced on demand.
