Skip to main content

Threat Model (STRIDE)

Condensed summary — see docs/security/THREAT_MODEL.md in the repo for the full analysis (admin-console privileged-access model, WebAuthn/passkey ceremony detail, public-endpoint entropy/rate-limit accounting, self-service privacy-request execution flow, and residual-risk tracking). Last reviewed 2026-08-15, and several of its findings have been overtaken by fixes since — the corrections are noted inline below and in the full document. Covering the full live surface: the core passport module, the admin console, public QR/lost-pet endpoints, and self-service privacy requests.

ThreatExampleMitigation
Spoofingforged actor/tenantOIDC tokens; server-side membership; dev bypass off in prod
Tamperingediting another owner's animalOpenFGA checks; deny by default; parameterized SQL
Repudiationdenying a delegation grant, or an admin denying a data viewappend-only immutable audit; admin record-level reads require a logged justification (self-audit-on-read)
Information disclosurechip number / address via public QR; a compromised admin browsing cross-tenant dataseparate emergency projection (medical summary included, no identity numbers); masked relay; justification-before-reveal on admin detail views
Denial of servicebody floods, token enumeration, admin login brute forcebody caps; per-IP/per-actor rate limiting (surface-wide, report, relay-message, and WebAuthn-login limiters — see docs/security/CI_AND_WEB_HARDENING.md); uniform 404 on unknown/disabled tokens. Read the per-IP claim with ADR-030 in hand: until 2026-08-28 every one of those limiters was a single global bucket behind Traefik, and Keycloak's own brute-force protection was switched off — both fixed and verified that day. See Security Baseline
Elevation of privilegedelegate self-escalates; an admin approves their own sensitive requestcan_manage_delegates = owner/co-owner only (tested); dual-control approver-must-differ check (currently covers one action type, evidence export)

Admin console (new since the original baseline)​

A compromised platform_admin account is a cross-tenant threat, unlike a compromised owner account (OpenFGA-confined). Controls: justification-before-reveal on record detail views (not list views), dual control on evidence export (not yet on suspend/moderate/dispute actions — a known, tracked gap), and audit coverage on every write path.

The "optional MFA" finding this review raised has since been fixed, and this page said otherwise until 2026-08-28. internal/server/server.go now wraps admin.RegisterAuthenticated(r) in auth.RequireAuthMethod("webauthn"), landed 2026-08-18 — three days after this review's date, which is why the review did not see it.

The fix is conditional, and the condition is the residual finding. That gate sits inside if webauthnHandler != nil, so a deployment with no WebAuthn RP configuration (AdminWebAuthnRPID/RPOrigins, which config.Load deliberately does not hard-require outside development) still falls back to role-only gating — the else branch says so in words. Check the deployment's configuration before assuming the gate is active.

Domain abuse cases​

  • Stalking via lost-and-found — never expose exact owner/home/real-time tracker location; contact via masked relay; audit GPS access.
  • QR token guessing — ~244-bit unguessable tokens; enumeration yields uniform failure.
  • Anonymous relay-message spam — real vector, mitigated by a dedicated per-IP rate limit. That limit is 5 per hour, and until the trusted-proxy fix landed on 2026-08-28 the whole internet shared those five: a small budget is hurt worse by a shared bucket than a large one. It is now genuinely per-IP (ADR-030).
  • Self-service account deletion — creating a request never deletes data by itself; execution requires a separate admin-side action, gated and audited independently of the UI's own typed confirmation.