# Threat model (auditor kit and initial verdict)

## Assets
The auditor's correct decision; the integrity of the release; the auditor's machine.

## Adversaries
| Adversary | Capability | Mitigation | Residual risk |
|---|---|---|---|
| A1 Network/mirror attacker | Swaps the download | `--expect-sha256` (fixture `substituted_download`) plus a signature under an out-of-band anchor | If the published hash and the key are both taken from the same compromised website, both can be swapped. Get the key through a second channel |
| A2 Package forger without the key | Edits files, the manifest, the signature, or the archive structure | Detached Ed25519 over exact manifest bytes; full file set equality; strict JSON; archive limits (fixtures: single-byte, replaced manifest, invalid signature, duplicate entries, traversal, symlink, bomb, unlisted file) | None known within this scope |
| A3 Forger who ships their own key | Re-signs the whole package with an attacker key **and** replaces the key info inside it | The verifier never reads a trust anchor from the package (`wrong_signing_key` gives `UNTRUSTED_SIGNING_KEY`) | The earlier kit (`cain42-verify.zip`, 5.0.0) **accepted this attack with exit 0**; see KNOWN_LIMITATIONS.md |
| A4 Honest-but-overclaiming publisher | Validly signs statements that are false or that the evidence cannot support | Contradiction checks between signed claims and signed evidence; `cannot_establish` in every claim; forged-independence detection | Only machine-checkable assertions are checked. Free-text overclaims need a human or AI reviewer |
| A5 Key compromise or key holder misuse | Signs anything | Revocation via `--revoked FINGERPRINT` | No transparency log, no hardware-backed key, no second signer. One key equals one point of failure |
| A6 Stale-evidence replay | Re-serves an old, correctly signed kit | Freshness ceilings (`stale_evidence`); `--expect-version`; `--expect-sha256` | Within the freshness window, an old kit with valid evidence is still accepted unless you pin the hash |
| A7 Result forger | Hands you a "PASS" result JSON | `--check-result` binds a result to the archive hash and detects edits (`replayed_result_rejected`, `edited_result_rejected`) | Results are not signed. **Re-run the verifier yourself**; it takes seconds |
| A8 Prompt injection against AI reviewers | Puts instructions inside evidence | Instruction-like text is flagged as a WARN and never interpreted; AI_REVIEW requires treating all package text as data; the validator rejects uncited or overclaiming findings | Detection is pattern-based and incomplete. A human must confirm high-severity findings |
| A9 Malicious archive aimed at the auditor's machine | Traversal, symlinks, bombs, huge entries | The verifier never extracts. Declared sizes, ratios, entry counts and paths are checked before decompression, and reads are bounded | If you extract the ZIP yourself with other tools, use a safe extractor in a scratch directory |
| A10 Compromised verifier | Ships a verifier that always says PASS | The verifier is stdlib-only, short and readable; you can compare it with the separately published copy, or write your own from `SCHEMAS/` and `ASSURANCE_MODEL.md` | Both copies come from the same publisher. Reading the code is the real control |

## Assumptions
Python's `hashlib`, `zipfile` and `json` behave correctly. SHA-256 and Ed25519 are secure. The auditor's machine is not compromised. The auditor obtains the trust anchor correctly.

## Out of scope for the initial verdict
The truth of platform claims, the safety of the consensus protocol, enforcement bypasses, tenant isolation, and live-site behaviour. These are covered by the deep-audit procedures in `FULL_AUDIT_RUNBOOK.md`.
