12. Threat model
A living, adversarial view of the system: what is worth attacking, where the trust boundaries are, who the adversaries are, and which attacks an identity provider must withstand — each mapped to the decision that mitigates it. The discipline — abuse-case tests, per-milestone and cadenced / event-triggered reviews, dependency-advisory hygiene, parser fuzzing, external review, and incident feedback — is ADR-0027; this section is the model itself. The frameworks are named there: STRIDE classifies (§12.4), OWASP ASVS v5.0 is the verification rubric, and OWASP Threat Dragon maintains the model as code (reserved until code begins). It is v0/evolving and hardens as surfaces are built — a threat model is never "complete."
12.1 Assets
| Asset | Why it matters |
|---|---|
| Signing keys | Forge any token if stolen; custody-trait-held, HSM/KMS, per-tenant separation (ADR-0003, ADR-0023). |
| Credentials (passwords, passkeys, TOTP secrets, client secrets) | Impersonate a principal; core-resident, never hot-path-read (ADR-0015). |
| Tokens & sessions | Bearer access; short-TTL, status-list-revocable, sender-constrainable (ADR-0005, ADR-0016). |
| The directory (principals, PII, group membership) | Privacy + account takeover; tenant-partitioned, envelope-encrypted at rest (ADR-0011). |
| Relation tuples (Forseti store) | Privilege escalation; the second consistency domain (ADR-0026). |
| The audit / identity event stream | Tamper to hide attacks; append-only, Wyrd-backstopped (ADR-0022). |
12.2 Trust boundaries
Boundaries from least- to most-trusted; the diagram is diagrams/trust-boundaries.mermaid.
- Internet — relying parties, end-users, upstream IdPs. Untrusted.
- Edge (Vör) — least-trusted internal tier: holds only public key material and cached projections (JWKS, status list). The rule-1 hot path.
- Regional admission (Modgud) — stateful: sessions, login ceremonies, and core writes. More trusted than the edge; the front-channel attack surface lives here.
- Core — the most-trusted tier: the Wyrd identity core, the Forseti relation store, KeyCustody (HSM/KMS), and the event log.
- Admin plane — operator surface, authorized by Forseti itself (ADR-0021).
- The revocation/projection transport (NATS + projection-distribution pulls) — a named boundary of its own, mirroring ADR-0027 §2: the highest-value spoof-or-suppress target inside the perimeter, mTLS/workload-identity authenticated (ADR-0035) and integrity-protected by projection signatures and freshness (ADR-0043).
- Tenant isolation — a cross-cutting boundary; no cross-tenant strong read on any hot path (rule 3, ADR-0012).
Edge-compromise blast radius (a stated property). An attacker who fully compromises an edge (Vör) node gains no signing keys, no credentials, and no directory — only public material, caches, and one transport credential: the edge's subscribe-only NATS credential (ADR-0035). They can deny service, serve stale (never forged) revocation, or observe the revocation stream — a privacy consideration for a compromised edge — but they cannot publish or suppress on it, and the projections the edge consumes are signature- and freshness-protected (ADR-0043). This bounded blast radius is a security payoff of the constitution's edge/core split (ADR-0027).
12.3 Adversaries
Anonymous internet attacker · malicious or compromised relying party · malicious end-user (privilege escalation) · malicious or compromised tenant administrator · compromised upstream IdP · a co-tenant (isolation breach) · network MITM · compromised edge node · malicious insider / operator · supply-chain attacker.
12.4 STRIDE, per boundary
| Edge (Vör) | Admission (Modgud) | Core / Forseti | |
|---|---|---|---|
| Spoofing | token forgery → signature + pinned algs (ADR-0010) | credential theft, phishing → passkeys, MFA (ADR-0020) | mTLS service identity |
| Tampering | cache poisoning → signed projections | request tampering → PKCE, signed state | append-only log (ADR-0022) |
| Repudiation | — | login/issuance events | the event stream is the audit truth |
| Information disclosure | only public material at edge | session/PII handling | envelope encryption; tenant partition |
| Denial of service | edge scales horizontally | the write-path DoS target → rate limits (ADR-0012) | per-tenant op-rate fairness |
| Elevation of privilege | none (no secrets) | step-up assurance in token | New-Enemy → consistency token (ADR-0004, ADR-0026) |
12.5 The identity attack catalog
| Attack | Mitigation | Owned by |
|---|---|---|
Token forgery / alg:none / RS↔HS algorithm confusion |
verifier pins the algorithm and key, rejects unknown/none; format version checked |
ADR-0010, ADR-0003 |
| Token theft / replay | short-TTL-dominant + status list; DPoP (reserved) sender-constrains | ADR-0005, ADR-0014 |
| Refresh-token theft (the one long-lived bearer) | rotation with reuse detection — replay of a consumed refresh token revokes the whole family and raises risk | ADR-0014, ADR-0031 |
| Authorization-code injection / replay | PKCE mandatory for all clients | ADR-0014 |
| OAuth/OIDC mix-up / IdP confusion | issuer + audience validation, nonce, state | ADR-0014, ADR-0019 |
| Open redirect / redirect manipulation / code interception | exact redirect-URI matching against a registered allowlist (no wildcards) | ADR-0038, ADR-0014 |
| Stolen / leaked client secret | confidential-client secret rotation with overlap; verify-only storage; client revocation kills its tokens | ADR-0038, prop 0004 |
| CSRF on the authorization endpoint | state + same-site session cookies |
ADR-0014, ADR-0016 |
| SAML signature wrapping (XSW) / XXE / XML attacks | confined to Modgud; hardened parser, schema + signature/canonicalization validation | ADR-0017 |
| Session fixation / hijacking | opaque server-side session, secure cookies, rotate on auth | ADR-0016 |
| Incomplete single-logout (IdP session ends, RP session persists) | OIDC back-channel / front-channel logout notifies relying parties; short token TTL backstops a missed notification | ADR-0016 |
| Phishing | WebAuthn / passkeys are origin-bound and phishing-resistant | ADR-0020 |
| Federation account-takeover (email collision) | explicit, verified account linking — never implicit email match | ADR-0019 |
| Privilege escalation / the "New Enemy" problem | decision evaluated at-or-after the consistency-token fence (composite across both domains) | ADR-0004, ADR-0026, ADR-0006 |
| Cross-tenant isolation breach | four boundaries — namespace + crypto + quota + rate (defense-in-depth) | ADR-0012 |
| Account enumeration | uniform responses + admission rate limits | ADR-0009 |
| Credential stuffing / brute force | Argon2id, per-tenant rate limits, MFA | ADR-0015, ADR-0020 |
| SSRF (JWKS / SCIM / upstream-IdP fetch) | egress allowlists; no internal-network fetch | ADR-0017, ADR-0018, ADR-0019 |
| Write-path DoS (login-storm) | per-tenant rate limits + sharded write path across both domains | ADR-0012, ADR-0026 |
| Signing-key compromise | KeyCustody (HSM/KMS); rotation; per-tenant key separation bounds blast radius | ADR-0003, ADR-0023 |
| Quantum (CRQC) signing-key recovery → token forgery | the public JWKS makes this a forge-all event at CRQC-time (not HNDL); defended by signature-suite agility + hybrid migration before a CRQC, paced to NIST | ADR-0042, ADR-0027 |
| Supply-chain compromise | permissive-only dependency criteria, cargo-deny advisories, DCO |
ADR-0008 |
| Malicious insider / operator | append-only audit stream; audited break-glass; envelope encryption (operator cannot read the directory) | ADR-0022, ADR-0021 |
| Account-recovery abuse / reset-flow ATO | proven contact control + recovery codes + rate-limited, session-invalidating reset that never bypasses required assurance | prop 0003 |
| Account-creation / bot abuse (CIAM) | anti-automation + per-tenant creation limits + contact verification | prop 0003, prop 0006 |
| SMS toll fraud / pumping (outbound-cost abuse) | per-destination/prefix velocity limits + country allow/deny + per-realm send-cost cap; pattern feeds risk pipeline | prop 0011, ADR-0031 |
| Secret-at-rest exposure (client / upstream / SCIM secrets) | verify-only secrets hashed; usable secrets envelope-encrypted under KeyCustody; no secrets in logs | prop 0004 |
| Resource exhaustion / crypto- & parser-amplification | input size/depth/entity limits; cost-before-auth rate-limiting; XML entity-expansion disabled | prop 0006 |
| Build / release-pipeline compromise | signed releases + SLSA provenance + pinned Actions + protected, signed source | prop 0005, ADR-0008 |
| SAML assertion replay | one-time assertion-ID tracking + NotOnOrAfter enforcement at the SP / Modgud |
ADR-0017 |
| Disaster / data loss; incoherent cross-domain restore | version-stamped per-domain backups + the restore offset invariant (authz never leads identity); projections rebuilt, not restored | ADR-0040, ADR-0026 |
| Cross-relying-party user correlation (privacy / linkability) | minimal claims; opaque internal id; pairwise subject identifiers (reserved seat) | ADR-0041, ADR-0011 |
| Inconsistent freshness budget silently breaking the kill-now guarantee | the budget set is validated against its ordering invariants at config load; an inconsistent profile fails closed | ADR-0039 |
12.6 Abuse-case tests
The catalog above is made mechanical (ADR-0027): each defended attack is asserted
to fail, seed-reproducible, from the milestone where its surface exists — the
security analog of the edge-independence property test (ADR-0010). Representative:
a forged alg:none token is rejected (M0); a token presented to the wrong tenant is
rejected (M4); a SAML assertion with a wrapped signature is rejected (M6); an
authorization decision after a just-revoked grant reflects the revocation when fenced
(M3, V9). A defended attack that ever succeeds becomes a permanent regression test. The full
testing plan — the tiers, the CI gates, and the threat→test map — is
§13 (ADR-0028).
12.7 Residual and accepted risks
- The bearer-token validity window — a stolen token is valid until expiry / status-list refresh; the deliberate cost of edge-statelessness, bounded by short TTL + DPoP (ADR-0005).
- Bounded revocation freshness — "kill now" is within a budget, not instantaneous (ADR-0005).
- Cold-start availability — a cold edge during a core outage fails closed (safe, but a stated availability cost) (ADR-0010). A warm edge's autonomy is bounded the same way: the core-outage survival window is the projection freshness budget, past which it too fails closed (ADR-0032, ADR-0039).
12.8 Gaps and their disposition
The threat model records its own holes — and tracks them to closure. The gaps a posture review surfaced are now dispositioned.
Addressed as proposals (they fit the existing architecture): account recovery & contact verification (0003), secret handling (0004), build/release integrity (0005), resource-exhaustion hardening (0006), and credential policy & breached-password protection (0013).
Decided in (Proposed) ADRs (they affect the architecture):
- Status-list freshness / replay — the verifier enforces max-age + a monotonic counter (ADR-0032).
- Clock / time-source trust — bounded skew + authenticated time + implausible-clock fail-closed (ADR-0033).
- Right-to-erasure — crypto-shredding against the immutable log (ADR-0034).
- Internal service-to-service trust — mTLS / SPIFFE for Vord's planes (ADR-0035).
- Key-compromise emergency response — wholesale
kidrevocation + forced re-issue (ADR-0036). - Risk-based / adaptive auth, device trust & token-theft detection (ADR-0031).
- Consent & scope-grant enforcement (ADR-0037).
- OAuth client model — registration, confidential-client authentication and secret rotation, and exact-match redirect-URI validation (ADR-0038).
- The freshness / time-budget system — the coupled budget set, its ordering invariants, and a fail-closed config-load validation gate (ADR-0039).
- Backup, restore & disaster recovery — version-stamped per-domain backups, the cross-domain restore offset invariant, and rebuilt projections (ADR-0040).
- Privacy threat model (LINDDUN) & privacy-by-design — the privacy pass parallel to STRIDE, with data-minimization commitments and reserved pairwise subject identifiers (ADR-0041), taking up the pass ADR-0027 §5 reserved.
- Post-quantum cryptographic posture — signature-suite agility plus a reserved hybrid migration seat, the exposure framed as CRQC-time forge-all (not HNDL) and paced to NIST (ADR-0042).
Still genuinely open: the [OPEN] items inside the ADRs (the CIAM principal extension,
ADR-0011; cross-tenant sharing, ADR-0012; org/crate-name availability, ADR-0001/0009), and
the free-tooling CI workflow files (CodeQL, OSS-Fuzz, Scorecard), which land with code at M0
(proposal 0005). The privacy pass that was reserved is now taken up in
ADR-0041.