# Threat model

Organized by trust boundary, not by feature. A feature-organized threat model
reliably misses the attacks that live in the gaps between features, which is
where the two real ones in this system live: the trust-root race and the
split-view.

Diagram: `../diagrams/trust-boundaries.mmd`.

| Boundary | Document | The claim it defends |
| --- | --- | --- |
| Client to relay | `relay-boundary.md` | An operator, including us, cannot read message bodies, ticket text or media. |
| Control plane to relay | `control-plane-boundary.md` | The control plane cannot become a workspace participant. |
| Provisioning handoff | `bootstrap-handoff.md` | The party who pays is the party who becomes trust root. |
| Third parties | `third-party-boundary.md` | Stripe and Clerk never learn workspace identity. |

## Method

STRIDE per boundary, per data flow. Each entry names the threat, the existing
control, the residual risk, and the artifact that proves the control works. An
entry with no proof artifact is an aspiration and is marked as one.

## The standing rule

Every threat here is answered by one of exactly three things:

1. **A key we do not hold.** The strongest answer. Cannot be undone by a bug.
2. **A property of the deployment.** Strong. `WEALD_RELAY_MIN_ENC=mls` is not
   configurable on hosted, so a hosted operator cannot receive plaintext even by
   accident.
3. **A control we implement.** Weakest, because it is code, and code has bugs.
   Every answer in this class must name its test.

When a review proposes a mitigation, the first question is which of the three it
is. A mitigation that moves an answer from class 1 to class 3 is a downgrade
even when it adds code, and needs an ADR.
