Known weaknesses — aggregated register¶
This is the canonical scoreboard for every known weakness in Evospin / dropbet. Architectural blind spots, security findings, flow-doc failure modes, and workspace-level gaps all roll up here.
ID prefixes:
- SF-* — security findings; lives in security-findings/ or a flow doc's §6.
- FM-* — flow-doc-local failure modes; not yet SF-promoted.
- AF-* — architectural blind spots that touch multiple flows.
- WK-* — workspace-level gaps.
Updated alongside the flow docs and
security-findings/. Re-paginate ID assignment after each ten-finding addition.
Architectural blind spots¶
| ID | Scope | Weakness | Source |
|---|---|---|---|
| AF-1 | admin-fe ↔ ebit-api | Four stacked bugs block every admin-fe→api cross-service trace: (a) cookie-name mismatch — api sets access_token, middleware reads jwt_access_token (middleware.ts:59-60, src/types/Auth.ts:36-40); (b) instrumentation.ts is Sentry-only, no @vercel/otel fallback → no traceparent injection; (c) propagateContextUrls absent; (d) hard-coded API host. User-directive: do not edit admin-fe source. All admin specs therefore drive ebit-api directly. |
flows/admin-sign-in.md, flows/admin-user-mgmt.md, flows/admin-bets.md |
| AF-2 | Inter-app RPC | @ExternalControllerClient / @GatewayMethod / @MessagePattern use Nest Redis pub/sub transport — W3C traceparent is NOT propagated. Callee (api when called from rt, or speed-roulette when called from api) emits orphan root traces. Cross-service latency breakdown requires Loki pivot on userId + timestamp. |
flows/rt-websocket.md §6 #1, flows/dropbet-speed-roulette.md §6 #1 |
| AF-3 | ebit-rt scale |
ClientGateway.clientSockets is a per-instance Map. Scaling rt past one replica without sticky-routing OR @socket.io/redis-adapter silently drops message.user-targeted emits and breaks per-room joins. |
flows/rt-websocket.md §6 #2, flows/dropbet-wallet.md SF-016 |
| AF-4 | Orphan ebit-bj |
apps/bj/ runs on 4002 with a full blackjack + EVO-wallet RPC, but the dropbet client goes through ebit-api's /casino/games/house/blackjack/* exclusively. Compose exposes the port; the service receives zero in-repo traffic. Delete-or-wire decision pending. |
flows/dropbet-blackjack.md §6 #4 |
| AF-5 | Online-count inflation | apps/api/src/user/online-tracker.service.ts:30-46 broadcasts zcard(ONLINE_USERS_KEY) + fakeUserOnline every 10 s via Server.UsersOnlineUpdated. fakeUserOnline init 500, drifts ±5, floor 180. UI counters and any dashboard reading this number are reading a padded figure — not the real one. Only getOnlineCount() exposes the raw zcard; no HTTP endpoint. |
flows/rt-websocket.md §6 #3 |
| AF-6 | RabbitMQ stubbed | apps/api/src/fast-track/rabbitmq/fast-track.rmq.module.ts:8 returns disabled = true. The broker runs in compose (vhost ft) but receives zero traffic. 11 producer call sites (bet.service.ts ×4 settlement paths + promo-effect.service.ts ×7 reward handlers) fire into a no-op stub — every settled bet and every promo effect silently drops its Fast Track event. Flipping disabled=false requires real FASTTRACK_JWT_PRIVATE_KEY/FASTTRACK_JWT_PUBLIC_KEY + a reachable Fast Track CRM sandbox (out of scope for local dev). Decision deferred: either wire sandbox credentials (option b) or rip the producer classes + 11 call sites entirely (option a). |
flows/dropbet-challenges.md §6 #3, docker-compose.yml comment block |
| AF-7 | ~~Loki coverage gap~~ fixed | EvoLogger (winston) records now reach Loki via the filelog/docker receiver added to observability/otel-collector.yml. The receiver scrapes /var/lib/docker/containers/*/*.log and tags each record with source: docker_filelog so they are distinguishable from OTLP-bridged pino records. Query: {source="docker_filelog"} \|= "EvoLogger". |
observability/otel-collector.yml, docker-compose.yml (volume mount) |
Security-tracked findings (SF-*)¶
Flow-doc SF numbering is continuous across all docs — each SF is a single line in the respective §6 with source pointer. Three promoted to security-findings/:
| ID | Title | Flow | Severity signal |
|---|---|---|---|
| SF-001 | Sign-in email-enumeration timing oracle (user.service.ts:715-720 returns before bcrypt on unknown email) |
flows/dropbet-sign-in.md |
Info-disclosure; combines with SF-003 |
| SF-002 | Lockout counter reset defeats escalating rate limit (handleLoginAttempt deletes attemptsKey when lockout arms; both share TTL) |
flows/dropbet-sign-in.md |
Auth-control bypass |
| SF-003 | SignInDto + VerifyMfaDto + referrer lack @MaxLength / @IsEmail / @Length(6,6) |
flows/dropbet-sign-in.md, flows/admin-sign-in.md, flows/dropbet-sign-up.md |
Request-validation gap |
| SF-004 | Double-settle backstop is only @@unique([roundId, userId]); generic ApiCode.INTERNAL on violation |
flows/dropbet-bet-place.md |
Correctness |
| SF-005 | Fairness seed race if @PlaceBetLock key TTL-expires mid-handler |
flows/dropbet-bet-place.md |
Fairness |
| SF-006 | Insufficient-funds guard relies on WHERE amount >= betAmount rowcount; no pg CHECK |
flows/dropbet-bet-place.md |
Correctness |
| SF-007 | bet_settled_queue is fire-and-forget; Redis outage loses side-effects after removeOnFail.age |
flows/dropbet-bet-place.md |
Data loss |
| SF-008 | Bet-detail endpoints have JwtGuard commented out — anon reads full seed material | flows/dropbet-bet-history.md |
High — info disclosure |
| SF-009 | BetInfoQuery.userId is dead — service ignores ownership filter |
flows/dropbet-bet-history.md |
Access control |
| SF-010 | Bet-detail cache key is betId-only; post-ban window leaks data |
flows/dropbet-bet-history.md |
Info disclosure |
| SF-011 | No covering index for bet list status filter; power-user degradation |
flows/dropbet-bet-history.md |
Performance |
| SF-012 | Sportsbook bets silently invisible in /bets/my/* (hard-coded != SPORTSBOOK) |
flows/dropbet-bet-history.md |
UX gap |
| SF-013 | UserBalanceRepository.toVault has no overdraft guard — balance goes negative (amount=1000 + vault=10003 → -9003) |
flows/dropbet-wallet.md |
High — correctness / financial |
| SF-014 | Zero caching on GET /accounting/balances; hot JWT path |
flows/dropbet-wallet.md |
Performance |
| SF-015 | Transaction ledger has no HTTP endpoint (rt Private.TransactionFindMany only) |
flows/dropbet-wallet.md |
Debuggability |
| SF-016 | ClientGateway.handleServerEvent is O(n_sockets) per BalanceUpdated |
flows/dropbet-wallet.md |
Scale |
| SF-017 | usdAmount is request-time FX, not row-stamped; two reads can disagree |
flows/dropbet-wallet.md |
Consistency |
| SF-018 | TETH indistinguishable from ETH at API layer |
flows/dropbet-wallet.md |
UX |
| SF-019 | RACE_ENABLED gates each leaderboard handler inline — easy to forget on new routes |
flows/dropbet-leaderboard.md |
Maintainability |
| SF-020 | LeaderboardQueueProducer has zero call sites; @Processor subscribes to a queue nobody writes |
flows/dropbet-leaderboard.md |
Dead code |
| SF-021 | leaderboardService.handleBet runs in BullMQ worker with no OTel span |
flows/dropbet-leaderboard.md |
Observability |
| SF-022 | @Cacheable is in-process (node-local Map); api vs bo serve stale to each other up to 60 s |
flows/dropbet-leaderboard.md |
Cache coherence |
| SF-023 | leaderboard_user_position_view recomputes ROW_NUMBER per query — full-board pagination scans partition per page |
flows/dropbet-leaderboard.md |
Performance |
| SF-024 | updateLeaderboards runs only at boot — DAILY race stays ACTIVE past midnight until restart |
flows/dropbet-leaderboard.md |
Correctness |
| SF-025 | Admin bet list runs count + findMany sequentially where player list uses Promise.all |
flows/admin-bets.md |
Performance |
| SF-026 | bo POST /bets proxies to Private.BetFindMany — no subscriber; every call 5 s hangs → 500 |
flows/admin-bets.md |
Dead route + DOS |
| SF-027 | BetHttpController has no @UseGuards — any JWT holder can trigger the 5 s hang |
flows/admin-bets.md |
Access control / DOS |
| SF-028 | No admin "adjust/void/rollback bet" endpoint despite task title — corrections go via balance adjustment + ad-hoc DB write | flows/admin-bets.md |
Capability gap |
| SF-029 | permission.guard.ts:24 — SuperAdmin role shortcuts BEFORE the MFA check at line 40; seeded admin@admin.com (no mfaSecret) bypasses MFA |
flows/admin-bets.md |
High — auth control |
| SF-030 | Admin response has no aggregates — total-wagered / GGR needs a separate endpoint or client-side reduction | flows/admin-bets.md |
UX |
Other flow-doc failure modes (FM — not yet SF-promoted)¶
- FM-Sign-up-1
RecaptchaServicebypasses onNODE_ENV=local && token === 'pass'; a mis-configured prodNODE_ENV=localdisables reCAPTCHA entirely.flows/dropbet-sign-up.md - FM-Sign-up-2 User committed even if verification email fails (
sendEmailVerificationLinkis a floating promise). Same shape forsendUserWelcomeEmail. - FM-Sign-up-3 Duplicate email/username race — losing
INSERTreturns500(P2002) instead ofAUTH_USERNAME_OR_EMAIL_TAKEN; bots can fingerprint live users via status-code divergence. - FM-Sign-up-4 First session has no
UserSessionrow (isFromRegister=trueskips BullMQ enqueue). - FM-Reset-1
JWT_VERIFICATION_TOKEN_SECRETsigns both email-verify and password-reset tokens — leak mints reset tokens for any account. - FM-Reset-2 Reset token is not one-use within its TTL — observed two successful PATCHes with the same token in the E2E.
- FM-Reset-3 Sessions are not invalidated after reset — phished access_token survives.
- FM-Reset-4 Email enumeration via cooldown key (written only inside the
user-existsbranch). - FM-Admin-user-mgmt-1
/admin/user/admin-audit?userId=Xfilters by ADMIN ACTOR, not target — undocumented. - FM-Admin-user-mgmt-2
banUser/unBanUsertake anadminparameter but don't persist it; the only "who banned whom" record is theadmin_action_logrow, andsafeLogswallows DB errors. - FM-Admin-user-mgmt-3 No multi-ban route (
admin.user.controller.ts:148 // TODO). - FM-Admin-user-mgmt-4 No idempotency on
/ban— repeat PATCH re-UPDATEs + re-publishes to profile-notifier + writes another audit row + double-kicks the socket. - FM-Admin-signin-3
middleware.ts:68-90— bad-JWTparseTokencatch istry { … } catch { /* TODO */ }with theleaveFromAccountredirect commented out → silent dashboard fall-through → blank page. - FM-Admin-signin-5 Admin-only gate (
auth.service.ts:145-156) isOrigin-header-based; stripping the header demotes the check. - FM-House-game-3 Limbo
randomMultiplier = 1.0collapses genuine bust vs near-one crash; no explicitdidWinin the response. - FM-House-game-4 Dice multiplier
< 1.01is a hard reject, not a clamp — UI must pre-validate. - FM-Challenges-1
POST /promo/public/:codeis unregistered — missing@Postdecorator atpromo.controller.ts:99; every FE claim 404s. - FM-Challenges-2 Challenges have no user-triggered state change; rewards land silently when admin calls
/admin/challenge/:id/award. - FM-Challenges-4
promo.service.ts:36callsexpiresAt.getMilliseconds()(0-999) where.getTime()was intended — that path always evaluates false. - FM-Blackjack-2 Abandoned hand locks player funds indefinitely — no TTL auto-resolution on
is_finished=falserounds. - FM-Blackjack-3
BetService.createBettriggers 6+ side-load SELECTs per/init. Same pattern flagged inflows/dropbet-bet-place.md. - FM-Blackjack-5
payloadis opaque JSONB — no covering index; analytics must parse per-row. - FM-Blackjack-6 No settlement push channel;
/handleActionresponse body is the sole settlement signal. - FM-Speed-roulette-3 State processor is
concurrency: 1, single-worker — any unhandled path that exhausts retries without a follow-up job deadlocks the queue. - FM-RT-4 Unknown client event is warn-log +
undefinedreturn — ack-callers hang to their own 5 s timeout with no error event back. - FM-RT-5
handleDisconnectdoes notzremthe user; "online" window extends to TTL.
Workspace-level gaps (WK-*)¶
- WK-1
ebit-api/node_modulesends up root-owned after docker builds (pending task #23). - WK-2 First sign-up produces no
UserSessionrow (see FM-Sign-up-4). Admin seeing a just-registered account finds only the one-shotRegistrationInforow. - WK-3 FastTrack RabbitMQ broker runs in compose but is stubbed; disposition decision pending (task #21).