Security
Threat model
Matter's living threat model. Every named threat carries a control, every control carries a test. Updated with every security-relevant PR. Published externally for enterprise transparency.
Last updated
This document is the Matter platform's living threat model. Every named threat below has a control (the mechanism that prevents or detects it) and a test (the automated check that catches a regression). The model is updated by every PR that touches a security-relevant primitive; the audit-trail of changes lives in git history.
Treat this document as the source of truth. The customer security-questionnaire library at questionnaire-responses references back to specific threats here.
Trust boundaries
The system's trust boundaries (where untrusted input crosses into trusted code):
- Edge (Vercel / WAF) → apps/api. TLS-terminated; client certificate optional via mTLS; rate-limited per IP/ASN.
- apps/api middleware → service packages. Auth validated; scope enforced; mode resolved; request validated against spec; PII redacted.
- Service packages → Prisma. Tenant-scoped reads via the mode-aware Prisma extension; raw SQL banned outside
@repo/database. - Service packages → provider ACLs. External provider responses are typed; provider errors mapped into the Matter error taxonomy.
- Webhook delivery → customer endpoint. Outbound HTTPS; HMAC-SHA256 + nonce + monotonic counter; customer-provided URL validated against SSRF allowlist.
- Audit chain → Sigstore Rekor. Internal cryptographic primitives to a public WORM log.
- MCP server → customer LLM client. Tool catalogue filtered by tier +
never_expose; tool-call patterns monitored for anomaly.
Every threat below is anchored to one or more trust boundaries.
Threats and controls
Authentication & token compromise
T1 — Stolen bearer token. An attacker obtains a customer's sk_live_* or tok_* and uses it to impersonate the customer.
- Control: Token revocation propagates to in-flight workers via lease checks. Rotation overlap window. Optional device binding (
deviceBindingCnf). IP allowlist. Customer emergency-revoke atPOST /v1/tokens/{id}/emergency_revokerequires fresh OAuth. Token-usage anomaly detection auto-quarantines on high-confidence signals. - Test:
apps/api/__tests__/token-revocation.test.tsvalidates revocation propagation;packages/observability/__tests__/anomaly.test.tsvalidates quarantine.
T2 — Compromised webhook signing secret. An attacker captures the HMAC secret a customer's WebhookEndpoint uses to verify Matter's signatures and forges events.
- Control: Dual-signature delivery during rotation overlap (
v0=<previous>,v1=<current>). Signature carries nonce + monotonic counter. Customer rotates viaPOST /v1/webhook_endpoints/{id}/rotate_secret. - Test:
packages/webhooks/__tests__/dispatch.test.tsvalidates dual-sign + nonce + replay rejection.
T3 — Stolen session JWT. A short-lived session JWT (P0.J3) is captured by an attacker during multi-call flows.
- Control: 5-minute TTL.
jticlaim. Single-use replay-protection table. KMS-signed. - Test:
packages/auth-api-key/__tests__/session-jwt.test.tsvalidates replay rejection.
T4 — Agent token theft. A tok_* agent token is stolen and used by an attacker via the MCP server.
- Control: Scope policy constant-time evaluated; tier enforcement gates high-stakes operations to HITL. MCP tool-call anomaly detection flags suspicious sequences. Customer dashboard surfaces all
tok_*activity for review. - Test:
apps/api/__tests__/scope-policy.test.ts+apps/mcp/__tests__/anomaly.test.ts.
T5 — Audit-on-auth-failure brute force. An attacker repeatedly tries token suffixes against a known prefix.
- Control: Failed auth attempts above threshold per (prefix, IP) generate audit entries + anomaly events. Token-prefix lookup is timing-equalised (P0.F1, P0.F10). Argon2id hash verification adds computational cost.
- Test:
apps/api/__tests__/auth-timing.test.tsvalidates timing stddev across failure modes.
Cryptographic integrity
T6 — Audit-chain tampering. An attacker with DB access alters an AuditEntry row.
- Control: JCS-canonical hash chain (RFC 8785). Per-(org, mode) advisory lock during insert prevents interleaving races. Daily Merkle root anchored into Sigstore Rekor (public WORM) + an object-locked storage bucket. Prisma extension throws on UPDATE/DELETE against AuditEntry.
- Test:
apps/api/__tests__/audit-chain.test.ts(1000 concurrent → chain verifies);audit-rebuild-from-rekor.test.ts(reconstructs identical chain after synthetic wipe).
T7 — Compromised webhook signing secret (key material). Same threat as T2 but at the KMS layer — the long-term wrapping key is compromised.
- Control: KMS-resident hierarchy. KMS rotation drilled quarterly. Per-purpose KEKs limit blast radius. KMS rotation mid-failure recovery is itself a saga with typed compensation (P0.C15).
- Test:
packages/crypto/__tests__/rotation.test.tsper KEK +rotation-recovery.test.tsfor failure handling.
T8 — Genesis-block forgery. An attacker creates a forged "first" entry in the audit chain for a new (org, mode) and convinces the system to accept it.
- Control: Genesis ceremony requires an m-of-n threshold of KMS-resident master signers. Procedure documented at
apps/docs/content/docs/runbooks/audit-genesis-ceremony.mdx(lands P3.5). Rekor witnesses every genesis entry. - Test:
packages/crypto/__tests__/genesis.test.tsvalidates m-of-n threshold + Rekor entry.
T9 — Cryptographic primitive break. A future algorithm break (argon2id deprecated; SHA-256 weakened) invalidates existing material.
- Control: Cryptographic agility. Every hash / signature / encryption persists
algorithmVersion.packages/crypto/src/upgrade.tsmigrates lazily on next access. Algorithm upgrade is a configuration change, not a re-architecture. - Test:
packages/crypto/__tests__/upgrade.test.tsper primitive.
T10 — Sigstore Rekor offline. Rekor is unavailable when a daily anchor must be written.
- Control: Local cosigning fallback — the daily anchor is signed by an additional KMS-resident witness key and stored locally. Backfilled to Rekor on recovery. Customer-facing verifier checks both anchors.
- Test:
packages/crypto/__tests__/rekor-offline.test.tssimulates Rekor outage + recovery.
Data confidentiality
T11 — Insider DB dump (no KMS access). A rogue insider gains read-only DB access and dumps PII-bearing columns.
- Control: Per-tenant DEK envelope encryption wrapped under KMS KEK. Plain DB rows reveal nothing about PII content. Blind indexes (HMAC) support equality lookup without leaking plaintext.
- Test:
packages/database/__tests__/encrypted-field.test.ts+ audit drill confirming dumped rows contain ciphertext only.
T12 — Insider with KMS access (rogue engineer with full credentials). A privileged insider with both DB and KMS access tries to decrypt or tamper.
- Control: Audit-chain genesis ceremony requires m-of-n split signers. KMS rotation requires two-party approval. Audit-on-read for Highly Restricted columns logs every access. Quarterly access review. KMS API call rate is a leading indicator.
- Test:
packages/crypto/__tests__/access-review.test.tsexercises the access-review workflow.
T13 — Idempotency-record PII leak. Stored idempotency-record responses contain PII even though the live API response would have been redacted.
- Control: Redaction-at-write. Every stored response runs through the PII path codegen (P0.F13) before persistence.
- Test:
apps/api/__tests__/idempotency-pii-redaction.test.ts.
T14 — Webhook payload PII leak. A fat webhook payload contains a PII field that should have been redacted.
- Control: Same PII path codegen redacts webhook payloads.
WebhookEndpoint.includecontrols fat vs thin; thin payloads have no PII by construction. - Test:
packages/webhooks/__tests__/payload-pii.test.ts.
T15 — GDPR Article 17 erasure failure. A right-to-erasure request leaves PII recoverable.
- Control: Erasure replaces PII columns with tombstones and destroys the per-row DEK. Audit chain stays intact (sealed entry records the erasure); data is unrecoverable. Customer-facing endpoint at
POST /v1/stakeholders/{id}/eraserequires fresh OAuth + escalated tier. - Test:
packages/database/__tests__/erasure.test.ts— erasure-then-verify drill confirms (a) PII unrecoverable, (b) chain still verifies, (c) erasure event in Rekor.
Tenant isolation
T16 — Cross-tenant access via direct ID guess. An attacker with a valid token for org A guesses a resource ID belonging to org B.
- Control: Typed-ID parser is constant-time. Every list endpoint scopes by
(orgId, mode, region). Direct lookups return 404 (not 403) for cross-tenant — never leak existence. Optimistic concurrency on TOCTOU-exposed resources. - Test:
apps/api/__tests__/tenant-isolation/per-resource red-team suite (extended each phase).
T17 — Cross-mode read attempt. A sk_test_* token tries to read a livemode: true row.
- Control: Prisma client extension injects
modepredicate into every read on tables that carry the column. Raw SQL ban outside@repo/database. Per-mode unique constraint onAuditEntry((orgId, livemode, mode, prevHash)). - Test:
apps/api/__tests__/mode-segregation.test.ts— red-team via every access pattern (single-id, list, expand, search) returns 404.
T18 — Cross-portfolio data exposure. A token scoped to portfolio A accesses entities in portfolio B.
- Control: Portfolio membership controls token visibility at the auth-resolution layer. Every entity-scoped query filters by
portfolio_idderived from the token's scope. - Test:
apps/api/__tests__/portfolio-isolation.test.ts.
Volumetric & abuse
T19 — DDoS / volumetric attack. Edge flooded with requests.
- Control: Edge WAF (Vercel / Cloudflare). IP/ASN rate limit at edge. Per-token sliding window at app. Surge mode auto-engages on 5× traffic spike (degrades non-critical features; preserves writes). DNSSEC + CAA + HSTS on the API domain.
- Test:
tooling/load/scenarios/ddos-surge.ts+apps/api/__tests__/surge-mode.test.ts.
T20 — Behavioural anomaly. A token suddenly hammers one endpoint, shifts geography, or shifts time-of-day signature.
- Control: Anomaly detection rules over rolling windows in
packages/observability/src/anomaly.ts. High-confidence anomalies auto-quarantine the token. Customer notified. - Test:
packages/observability/__tests__/anomaly.test.ts— synthetic anomaly drill.
T21 — Thundering herd on rate-limit reset. All tokens reset at the same window boundary, surging the API.
- Control: Jittered reset boundaries — different tokens reset at different microseconds within a second. Sliding window with rolling counters absorbs the residual.
- Test:
apps/api/__tests__/rate-limit-jitter.test.ts— 10k concurrent reset surge drill.
T22 — Idempotency-key replay attack with stolen token. An attacker captures an Idempotency-Key + token, replays.
- Control: Idempotency-Key is token-scoped (different tokens cannot collide on keys). 24h TTL. Replay returns original response only; new body with same key returns 409. Token revocation invalidates outstanding keys.
- Test:
apps/api/__tests__/idempotency.test.ts— all five cases including stolen-token replay.
Provider & supply chain
T23 — Compromised dependency. An attacker compromises an upstream npm/PyPI/Go package in Matter's dependency tree.
- Control: SLSA L3 build + signed provenance. Dependency review automation. Weekly upgrade cadence. Lockfile audit on every PR. Sigstore verify on every
npm installin CI. The supply-chain runbook (supply-chain-runbook) covers response. - Test:
.github/workflows/security.ymlruns Semgrep + Snyk + osv-scanner + trufflehog + gitleaks on every PR.
T24 — Compromised provider. A filing provider, IRS endpoint, signing provider, or bank is compromised.
- Control: Anti-corruption layer wraps every provider in
packages/api-providers/<provider>/acl.ts. Provider response shapes are typed; drift fails the ACL conformance suite. Per-state circuit breakers + failover (P0.E9). Per-provider rate limits. - Test:
packages/api-providers/__tests__/acl-conformance/per ACL.
T25 — SSRF via customer-supplied URL. A customer registers a webhook URL pointing at internal infrastructure (RFC 1918, link-local, metadata service).
- Control: Webhook URL validation in
packages/webhooks/src/url-validation.tsrejects RFC 1918, link-local, metadata service, IPv6 ULA, and other internal ranges. - Test:
packages/webhooks/__tests__/url-validation.test.tsper attack class.
Agent surfaces
T26 — Prompt injection (mail categorisation, intent resolution). A malicious actor crafts mail content or intent payload to manipulate AI processing.
- Control: Input length cap. Structural validation. Output schema validation (parser rejects responses outside the expected schema). Sandboxed prompt template. No tool-calling inside untrusted-input contexts.
- Test:
apps/api/__tests__/prompt-injection/adversarial corpus.
T27 — MCP tool exposure to compromised LLM client. A compromised LLM client (or a malicious customer integration) attempts to invoke tools beyond its allowed surface.
- Control: Constant-time scope evaluation.
x-matter-mcp.never_expose: truefor sensitive operations. MCP tool-call anomaly detection flags suspicious sequences. Tier-aware tool visibility — agents only see tools at or below their tier. - Test:
apps/mcp/__tests__/scope.test.ts+anomaly.test.ts.
Concurrency & correctness
T28 — TOCTOU race in cap-table mutation. Two concurrent writes to the same entity's cap table interleave incorrectly.
- Control: Optimistic concurrency control with
versioncolumn on Entity, EquityPlan, ShareClass, Document. Conflicting writes return 409state_optimistic_lock_conflict. Property tests on 10k random mutation sequences validate invariants hold. - Test:
packages/equity-math/__tests__/properties/+apps/api/__tests__/cap-table-concurrency.test.ts.
T29 — Audit-chain insertion race. Two concurrent writes to the same (org, mode) compute identical prevHash and try to insert sibling chain links.
- Control: Per-(org, mode) advisory lock acquired before chain insert, released at transaction end. Combined with the unique constraint, race-free serialisation guaranteed.
- Test:
apps/api/__tests__/audit-chain.test.ts— 1000 concurrent.
T30 — SagaInstance state recovery on persistence loss. SagaInstance state lost mid-saga (DB corruption, restoration from backup older than the saga).
- Control: Every saga step writes
causation_id+correlation_idto the canonical event log. SagaInstance state can be reconstructed from the event log alone — event sourcing pays off. - Test:
packages/saga/__tests__/state-recovery.test.ts— synthetic SagaInstance wipe + recovery from event log.
Bug bounty & disclosure
Public policy at https://mattermode.com/security. HackerOne (or equivalent) program. Tiered rewards based on CVSS:
- Critical (CVSS 9.0–10.0): up to $50k.
- High (7.0–8.9): up to $15k.
- Medium (4.0–6.9): up to $3k.
- Low (0.1–3.9): up to $500.
Triage SLO: 24 hours. CVE coordination via MITRE.
In-scope: every customer-facing surface of Matter (api.mattermode.com, mcp.mattermode.com, app.mattermode.com, mattermode.com, docs.mattermode.com, all SDKs, CLI).
Out of scope: phishing, social engineering, denial-of-service against rate-limit surfaces (the rate limit is the feature), reports against version-pinned older minors that have been fixed in current.
Coordinated disclosure: 90-day window from triage to public disclosure. Earlier disclosure with mutual agreement.
How this document changes
Every PR that adds, modifies, or removes a security control updates this document. The four-audits Security lens at every phase exit reviews this document against the actual implementation. Annual external pentest + quarterly internal purple-team validate against this model.
The threat IDs (T1–T30+) are stable references; new threats append. The apps/docs/content/docs/security/threat-model.mdx is the source of truth; PRs that touch security primitives are blocked by CI if they do not update this document.
See also
- Data classification — what's Public / Confidential / Restricted / Highly Restricted.
- Retention policy — what we keep, how long, where.
- Certification roadmap — SOC 2, ISO 27001, HIPAA-readiness.
- Supply-chain runbook — what to do during a dependency compromise.
- Severity matrix — how a security incident gets classified.