-
Notifications
You must be signed in to change notification settings - Fork 0
Threat Modeling
Tiana_ edited this page May 30, 2026
·
1 revision
STRIDE-based threat model per service. Identifies trust boundaries, assets, threats, and mitigations. Companion to Architecture-Security, Risk-Register.
STRIDE (Microsoft):
- S - Spoofing identity
- T - Tampering with data
- R - Repudiation
- I - Information disclosure
- D - Denial of service
- E - Elevation of privilege
For each service: identify assets → identify trust boundaries → enumerate STRIDE threats → document mitigations.
Reviewed:
- On every new aggregate / API endpoint
- After every security-relevant change
- Annually as part of penetration test prep
┌──────────────────────────────┐
│ Public Internet │ ← UNTRUSTED
└─────────────┬────────────────┘
│ TLS 1.3, JWT
┌─────────────▼────────────────┐
│ API Gateway │ ← TRUSTED (semi)
│ (validates JWTs, rate-limits)
└─────────────┬────────────────┘
│ propagated JWT, cluster-internal
┌─────────────▼────────────────┐
│ Application Services │ ← TRUSTED
│ (Ledger, Payments, etc.) │
└─────────────┬────────────────┘
│ TLS, SASL
┌─────────────▼────────────────┐
│ Data plane │ ← TRUSTED
│ (Postgres, Redpanda, Redis)│
└──────────────────────────────┘
External boundaries:
- Bank Adapter ──→ External Bank Provider (signed HMAC, TLS, allowlisted)
- KYC Adapter ──→ External KYC Provider (signed HMAC, TLS)
- LLM Adapter ──→ External LLM Provider (TLS, no PII, prompt sanitization)
- Webhook Out ──→ Subscriber URL (HMAC-signed, allowlisted)
- Webhook In ──← External Provider (HMAC-verified, deduplicated)
- JWTs in flight
- Rate-limit counters
- TLS private keys
- JWKS cache
| ID | STRIDE | Threat | Mitigation |
|---|---|---|---|
| GW-S1 | Spoofing | Forged JWT (wrong signature) | RS256 + JWKS validation; reject fast |
| GW-S2 | Spoofing | JWT replay after revocation | Short TTL (5 min) limits exposure window |
| GW-S3 | Spoofing | TLS MITM on public boundary | TLS 1.3 mandatory, HSTS, cert pinning at gateway |
| GW-T1 | Tampering | JWT claim modification | RS256 signature includes claims; tampering breaks signature |
| GW-T2 | Tampering | Request body modified between Gateway and service | mTLS in cluster (Istio recommended) |
| GW-R1 | Repudiation | User denies making a request | Audit log includes JWT subject + correlation ID |
| GW-I1 | Info disclosure | JWT leaked via logs | Logback filter scrubs Authorization header |
| GW-I2 | Info disclosure | Error messages leak internal structure | RFC 7807 problem details - no stack traces in responses |
| GW-D1 | DoS | Volumetric DDoS | Cloud-front WAF + per-IP rate limit (Redis token bucket) |
| GW-D2 | DoS | Slowloris / connection exhaustion | Netty-level connection timeouts, max connections |
| GW-D3 | DoS | Expensive endpoint abuse (e.g., heavy report) | Per-endpoint rate limits |
| GW-E1 | Privilege escalation | Scope claim manipulation | Scope check happens after signature verification |
- WAF integration in deployment guide (Y1 H2)
- mTLS via Istio operator example (v0.5)
- Money (account balances)
- Immutable journal (transactions, entries)
- Audit log
| ID | STRIDE | Threat | Mitigation |
|---|---|---|---|
| LG-S1 | Spoofing | Service account compromised, posts unauthorized transactions | Vault-issued short-lived creds, audit on credential use |
| LG-T1 | Tampering | Direct DB UPDATE bypasses application logic | DB role separation: app-role has no UPDATE on entries, transactions (they're append-only); DBA-role audited |
| LG-T2 | Tampering | Trigger disabled to bypass invariant | Trigger enable/disable monitored; test runs nightly verifies trigger active |
| LG-T3 | Tampering | Maintenance script bypasses entries integrity | Mandatory PR review for any DB direct-edit script; CI checks |
| LG-R1 | Repudiation | Compliance officer denies posting reversal | Reverse requires reason (text), resolved_by (user id), audit log |
| LG-I1 | Info disclosure | Account names leaked to unauthorized user | RBAC LEDGER_READER required; row-level isolation by tenant (v1.5+) |
| LG-D1 | DoS | Posting-storm exhausts DB connection pool | Per-user rate limit + DB pool sized for 3× burst |
| LG-D2 | DoS | Materialized view refresh blocks reads | REFRESH MATERIALIZED VIEW CONCURRENTLY |
| LG-E1 | Privilege escalation | LEDGER_READER becomes LEDGER_POSTER via JWT mod | Mitigated by JWT signature; further scope check at endpoint |
| LG-E2 | Invariant violation | Race condition allows SUM != 0 | Deferred trigger blocks at COMMIT; SELECT FOR UPDATE on accounts |
-
SUM(entries.amount) = 0 per (transaction_id, currency)- DB-enforced - Transactions never deleted, only marked REVERSED - DB role + app discipline
- Accounts can only transition through allowed states - service-layer state machine
- Payment records
- Idempotency cache
- Provider-side payment refs
- Customer counterparty info (PII)
| ID | STRIDE | Threat | Mitigation |
|---|---|---|---|
| PA-S1 | Spoofing | Bank webhook signature forged | HMAC-SHA256 verification; reject if mismatched |
| PA-S2 | Spoofing | Replay attack on bank webhook |
processed_webhooks dedup; timestamp check |
| PA-T1 | Tampering | Provider-controlled fields injected | Whitelist of accepted webhook fields; rest ignored |
| PA-R1 | Repudiation | User denies initiating payment | Audit log + idempotency_key + JWT subject preserved |
| PA-I1 | Info disclosure | Counterparty IBAN leaked in logs | PII scrubbing in Logback (last-4 only) |
| PA-I2 | Info disclosure | Payment amounts visible cross-tenant | Tenant isolation (v1.5+); single-tenant per deployment in v0.1 |
| PA-D1 | DoS | Webhook flood from compromised provider | Rate limit per-providerId; alert on burst |
| PA-D2 | DoS | Retry storm exhausts bank-adapter executor | Bulkhead (Resilience4j), max 20 concurrent bank calls |
| PA-E1 | Privilege escalation | Cancel another user's payment | Owner-check enforced in service; JWT subject must match payment.created_by |
| ID | Threat | Mitigation |
|---|---|---|
| PA-IDEM-1 | Cache poisoning via spoofed Idempotency-Key | Per-user namespace in cache key; SHA-256 of body validated |
| PA-IDEM-2 | Idempotency replay across users |
idempotency_keys row stores created_by (creator); cross-user replay rejected |
- KYC evidence references (encrypted)
- PII (encrypted at rest, never in logs)
- AML alerts and cases
- Compliance officer credentials
| ID | STRIDE | Threat | Mitigation |
|---|---|---|---|
| CO-S1 | Spoofing | KYC provider webhook forged | HMAC verification per-provider |
| CO-S2 | Spoofing | Unauthorized user impersonates compliance officer | MFA mandatory for COMPLIANCE_RESOLVER role |
| CO-T1 | Tampering | Case decision modified after resolution | Resolved cases immutable (CHECK constraint + service guard) |
| CO-T2 | Tampering | Evidence pointer rewritten |
evidence_external_ref is in encrypted column; tampering breaks decryption |
| CO-R1 | Repudiation | Officer denies resolving case | Resolution requires reason, resolved_by, resolved_at - audit |
| CO-I1 | Info disclosure | KYC evidence leaked | At-rest encryption (AES-256-GCM), TLS in transit, log scrubbing |
| CO-I2 | Info disclosure | LLM prompts leak PII | Prompt sanitization; PII never sent to LLM (only IDs + structured context) |
| CO-D1 | DoS | AML rule complexity DoS | DSL evaluation timeout (50ms); rule complexity validated at insert |
| CO-E1 | Privilege escalation | Resolver becomes admin via JWT mod | Same mitigations as Gateway |
| CO-E2 | Privilege escalation | Hold case via never-resolving | Case TTL alert (open >7 days fires escalation) |
- Active rules
- Decision logs (regulatory retention)
| ID | STRIDE | Threat | Mitigation |
|---|---|---|---|
| DE-S1 | Spoofing | Unauthorized rule activation | DECISION_ADMIN role + audit; activation event published |
| DE-T1 | Tampering | Rule definition modified after activation | New version always created; old versions immutable |
| DE-T2 | Tampering | Decision log entry deleted | DB role: app has only INSERT, never DELETE on decision_logs
|
| DE-R1 | Repudiation | "I never approved that decision" | Decision log has full input, matched rules, output, latency, invokedBy |
| DE-I1 | Info disclosure | Rule definitions visible to non-DECISION_VIEWER | RBAC enforced |
| DE-D1 | DoS | Pathological context blows up evaluator | DSL operators bounded (no recursive ops); evaluator timeout 100ms |
- Subscriber secrets (encrypted)
- Delivery history
- Subscriber URLs (potential SSRF target)
| ID | STRIDE | Threat | Mitigation |
|---|---|---|---|
| WH-S1 | Spoofing | Subscriber URL forged | URL validation: HTTPS only, public IP only (anti-SSRF) |
| WH-S2 | Spoofing | HMAC secret leaked enables forged "deliveries" | Subscriber controls secret rotation; encrypted at rest |
| WH-T1 | Tampering | Delivery history tampered | DB role: append-only INSERT on webhook_deliveries
|
| WH-D1 | DoS | Subscriber requests trigger amplification | Per-subscription rate limit; max delivery attempts (7) |
| WH-I1 | Info disclosure | Webhook payloads contain PII unintentionally | Schema validation on every event type; PII filter at outbox publish |
| WH-E1 | SSRF - internal services scanned via subscription URL | URL allowlist, private IP blocked, metadata service blocked (169.254.x.x) |
| Threat | Mitigation |
|---|---|
| Dependency with vulnerability | Dependabot daily, OWASP DC weekly, Trivy in CI |
| Dependency with malicious code | Maven Central + GitHub-verified publishers; review all version bumps in PR |
| Compromised container base image | Distroless base updated monthly, Trivy scans on each build |
| CI runner compromised | GitHub-hosted only; no self-hosted runners in v0.1 |
| Stolen Maven publishing key | OIDC-based publishing (no static keys); cosign signing |
| Threat | Mitigation |
|---|---|
| Maintainer with full DB access compromised | Audited DB queries; separation of app-role vs DBA-role |
| Compromised contributor PR | Required code review; no force-push to main; signed commits |
| Stolen GitHub credentials | MFA enforced; signed commits; branch protection |
| Backdoor planted in patch release | Security review before patch tagging; community signoff for major changes |
| Threat | Mitigation |
|---|---|
| RSA private key compromised (JWT signing) | Short JWT TTL (5m); JWKS rotation 30 days |
| AES DEK compromised | Per-service DEK from Vault; KEK rotated annually; column re-encryption process documented |
| HMAC secret compromised | Per-provider/per-subscription; rotated quarterly; log forensics on usage |
When adding a new aggregate / endpoint / external integration:
- Trust boundary diagram updated
- STRIDE threats enumerated for the new asset
- Mitigations documented in this wiki page
- Endpoint added to
Architecture-Security#owasp-top-10matrix - Audit log row written for state-changing actions
- PII handling checked (encryption at rest, no logs)
- RBAC role/scope identified and enforced
- Rate limit applied if user-facing
- Idempotency required for mutations
- Tests for spoofed/tampered/replayed inputs
- Architecture-Security - implementation of mitigations
- Risk-Register - non-security risks (operational, market, regulatory)
- Architecture-Resilience - DoS mitigations operationally
- Incident-Response - what to do when a threat materializes
- Overview
- Services
- Data Model
- Domain Model
- Event Flow
- Security
- Observability
- Resilience
- SLA / SLI / SLO