Threat model — STRIDE walkthrough¶
🔒 INTERNAL ONLY. Cites file paths, env vars, and architectural details that are not appropriate for external distribution.
This is a STRIDE-style threat model for the Evospin platform. Per category we list the existing controls (with code citations), gaps (cross-referenced to findings in docs/security/internal/findings.md where one exists), and proposed work. The model is a living document; revisit on every architectural change.
Trust boundaries.
┌────────────┐ HTTPS ┌─────────┐ Internal ┌────────┐
│ Player WB │─────────▶│ ebit-fe │────────────▶│ ebit- │ ┌──────────┐
│ (browser) │ WSS │ (Next) │ REST │ api │──▶│ Postgres │
└────────────┘─────────▶┌─────────┐ │ (Nest) │ └──────────┘
│ ebit-rt │ socket.io │ + bo, │ ┌──────────┐
│ (Nest) │ │ bj, sr │──▶│ Redis │
└─────────┘ └────────┘ └──────────┘
│
┌────────────┐ HTTPS ┌─────────────┐ Internal │
│ Operator │─────────▶│ ebit-admin- │ REST │
│ (browser) │ │ fe (Next) │──────────────┘
└────────────┘ └─────────────┘
External-trust boundary: edge load balancer (CDN + WAF — {{TBD: confirm vendor}}).
Privileged-trust boundary: admin-fe + bo are operator-only; player-fe + rt are public.
Storage-trust boundary: Postgres (primary truth), Redis cache (6379, pwd cache), Redis bot (6380, pwd bot).
S — Spoofing¶
Existing controls¶
- JWT-based session tokens. Issued at
auth.service.tssign-in/MFA-verify; signed withJWT_ACCESS_TOKEN_SECRET(HS256 currently). Expiry per env-config. - Refresh-token rotation. Stored server-side in Redis under
auth-session:<userId>:<jti>. Logout clears the key. - MFA. TOTP-based (
speakeasy); enforced bypermission.guard.tsfor admin operations (subject to SR-004 — SuperAdmin bypass). - CAPTCHA on sign-up.
RecaptchaService(subject to SR-021 — local bypass via'pass'token inNODE_ENV=local). - Session-per-instance map (
apps/rt/src/.../client.gateway.ts) keys websockets bysocket.user.idafter JWT validation in the WS handshake. - Email verification before first sign-in via signed token.
- Origin-based admin gate (subject to SR-051 — strippable).
Gaps¶
- SR-004 (High) — SuperAdmin bypasses MFA; documented. Compensating control: SuperAdmin promotion gated by change-management.
- SR-011 (High) — JWT-verify and password-reset share signing secret.
- SR-021 (Medium, Accepted) — CAPTCHA bypass local-env affordance.
- SR-051 (Low) — Origin-header admin gate.
- JWT-signing alg. HS256 + shared secret across services. Migrating to RS256 with a JWKS endpoint would let services validate without holding the signing key. Tracked: {{TBD: ADR}}.
- No device-binding. Stolen
access_tokencookies replay from any IP / UA. Considered in SR-013 (closed: sessions invalidated on reset) and the sign-out flow, but no per-device binding.
Proposed work¶
- Migrate to RS256 + JWKS (Q3-2026, owner platform-auth).
- Per-device binding via TLS-fingerprint or first-seen UA pinning ({{TBD}}).
- Strict admin sign-in: SR-051 fix to server-side session check rather than
Origin.
T — Tampering¶
Existing controls¶
- Postgres ACID. Bet-place runs in a
PrismaTransactional(@bebkovan/prisma-transactional) — balance debit, ledger insert, bet insert all atomic. - Provably-fair seed material. Server-seed hash committed before the round, revealed after —
popUserSeedin seed service. - Bet uniqueness.
Bet @@unique([roundId, userId])prevents double-settle (subject to SR-007 — generic 500 on conflict). - Wallet ledger immutability.
Transactionrows are append-only; no UPDATE path in the repository. - Vault
CHECK (vault_amount >= 0)at the schema level (but not on the primaryamountcolumn — SR-009). - Audit log (
AdminActionLog) for admin operations. - Class-validator on DTOs (subject to SR-016 — thin coverage).
Gaps¶
- SR-002 / SR-009 (Critical / High) — wallet primary balance can go negative (no schema-level CHECK).
- SR-007 (High) — duplicate-bet protection is constraint-only; no advisory lock.
- SR-008 (High) —
@PlaceBetLockRedis lock can TTL-expire mid-handler. - No request signing. Player-FE → API is bearer-token only; no HMAC body signing. Compensating: HTTPS-only + short token TTL.
- No tamper-proof audit log.
AdminActionLogis a regular Postgres table; an admin with DB access could rewrite. {{TBD: append-only / hash-chain audit?}}
Proposed work¶
- SR-009 schema CHECK migration (Q2-2026).
- SR-008 fairness lock — Postgres sequence or conditional nonce++ (Q3-2026).
- Hash-chain or external-immutable audit log for admin actions (longer-term).
R — Repudiation¶
Existing controls¶
AdminActionLog— every admin action recordsactorAdminId,targetUserId,action,params,createdAt.UserSessionrecords on every sign-in (with theisFromRegister=trueexception — see legacyWK-2/FM-S-4).- Bet ledger is append-only; full transaction history per bet.
- OpenTelemetry traces every HTTP and BullMQ job, persisted to Tempo via
otel-collector(seedocs/observability.md). - Sentry captures application errors with user context.
Gaps¶
- WK-2 / FM-S-4 — first-session sign-up has no
UserSessionrow; admin sees onlyRegistrationInfo. - FM-AUM-2 / SR-047 —
banUserignores the documentedadminparameter;safeLogswallows errors. - No central log-retention policy. {{TBD: confirm Loki retention; legacy
AF-7recently fixed Loki gap.}} - Audit log not exposed to users for their own actions (no "your activity" feed).
Proposed work¶
- SR-047 ban-audit fix (Q3-2026).
- WK-2 first-session record fix.
- {{TBD}} log retention SOP.
- {{TBD}} consider per-user activity feed in player-FE.
I — Information disclosure¶
Existing controls¶
- TLS at edge.
- JWT-guarded endpoints for player-private data.
- Role-based guards (
PermissionGuard) on admin endpoints. - PII segregation: KYC data lives in a separate schema {{TBD: confirm path}}; payment processor (3rd-party) holds card details — we never see PAN.
- Secrets via Doppler.
.envgitignored; templates only in repo. - Redis password-protected (
cacheandbotinstances).
Gaps¶
- SR-001 (Critical) — anonymous bet-detail endpoint leaks seed material.
- SR-005 / SR-006 (High) — cross-user bet detail.
- SR-014, SR-019, SR-020 (Medium) — email-enumeration channels (login timing, reset-cooldown timing, sign-up race error code).
- SR-031 (Low) — settle-side-effects queue can drop on Redis outage (information loss, not exposure).
- Logs. Sentry / Loki may capture sensitive payloads — {{TBD: confirm scrubbers in
sentry.*.config.ts; current scrub set is generic —password,token}}. - OTel attributes. No explicit sanitizer for
http.body; default OTel JS exporters don't attach body, but custom instrumentation might. Audit pending.
Proposed work¶
- SR-001/005/006 combined fix (Q2-2026).
- SR-014/019/020 constant-time / consistent-error remediation (Q3-2026).
- Sentry scrubber audit + extension list (Q3-2026).
D — Denial of service¶
Existing controls¶
- Throttle decorators (
@Throttle) on auth endpoints; CAPTCHA on sign-up. - WS throttler library (
@app/ws-throttler) — rate-limits per socket. DISABLE_RATE_LIMITINGenv-flag — production must befalse. {{TBD: CI assertion.}}- BullMQ rate-limiter on outbound jobs.
- Edge WAF — request size + RPS caps. {{TBD: confirm vendor configuration.}}
- Redis-backed lockout for repeated bad logins (subject to SR-015 — counter reset).
Gaps¶
- SR-010 / SR-018 (High / Medium) — bo route hangs 5 s; small DoS surface.
- SR-015 (Medium) — lockout-window attempts reset.
- SR-016 (Medium) — DTO validation does not bound payload size; oversized password reaches
bcrypt. - SR-017 / SR-030 (Medium / Low) — RT
O(n_sockets)fan-out; per-instance socket map blocks horizontal scale. - SR-024 (Medium) — speed-roulette queue deadlock on retry-exhaust.
@nestjs/microservices(dep advisory) — DoS via recursivehandleDataon TCP transport. We use Redis transport, not TCP, but the advisory crosses our boundary on patch.
Proposed work¶
- SR-016 DTO
@MaxLength(Q3-2026). - SR-017 socket.io rooms (Q3-2026).
- SR-030 Redis-backed presence + adapter (Q3-2026).
- SR-024 watchdog cron (Q3-2026).
- WAF rule-set audit +
DISABLE_RATE_LIMITINGCI gate.
E — Elevation of privilege¶
Existing controls¶
AdminGuard/PermissionGuardon all@admin/*endpoints, subject to SR-004.- Role enum with
Player,Admin,SuperAdmin;@Roles(...)decorators on protected handlers. - MFA gate for admin actions (modulo SR-004).
- Service isolation.
apps/api,apps/rt,apps/bo,apps/bj,apps/speed-rouletterun as separate processes with their own ports. Inter-app communication via Redis transport, not direct DB writes frombo. - Token scoping.
access_tokencookies are scoped by domain;admin-*cookies separate fromplayer-*.
Gaps¶
- SR-004 (High) — SuperAdmin without
mfaSecretbypasses MFA gate. - SR-010 (High) — bo HTTP route lacks guards; any JWT holder reaches the message broker.
- SR-050 / SR-051 (Low) — admin sign-in middleware silent fall-through and Origin-based gate.
- No fine-grained scopes. Admins are all-powerful within
Adminrole; no separation between, e.g. "view-only support" and "balance-adjust" personas. - DB credentials are shared across all five apps. {{TBD: per-app DB roles?}}
Proposed work¶
- SR-004 MFA fix (Q2-2026).
- SR-010 controller delete (Q2-2026).
- Sub-roles + scoped tokens (Q4-2026, design pending).
- Per-app DB roles + RLS where feasible.
Cross-cutting¶
- Secrets management. Doppler-injected; rotation cadence: {{TBD: confirm}}. JWT secrets currently rotated only on incident.
- Backups. Postgres backups: {{TBD: vendor + retention}}. Restore-test cadence: {{TBD}}.
- Disaster recovery. RTO/RPO documented in
docs/business/nfr-sla.md(pending). - Threat-model review cadence. Quarterly — owned by security on-call. Next review: {{TBD: 2026-07-15}}.
Cross-references¶
- Findings register:
docs/security/internal/findings.md - Dependency audit:
docs/security/internal/dependency-audit.md - Architecture weaknesses:
docs/weaknesses-register.md; service map + diagrams indocs/architecture/track - Observability:
docs/observability.md - Customer-facing risk register:
docs/security/client/risk-register.md