--- title: Audit Index — 2026-05-24 tags: [audit, index, security, logic, performance] created: 2026-05-24 status: open --- # Audit Index — 2026-05-24 Full-system audit triggered by completion of Telegram first-class auth, Request Network integration, rate-limiting enablement, and funds ledger. Three parallel audit streams were run against actual source code. | Report | Findings | |--------|---------| | [[Security Audit - 2026-05-24]] | 6 critical · 5 high · 7 medium · 4 low | | [[Logic Audit - 2026-05-24]] | 4 critical · 5 high · 7 medium · 2 low | | [[Performance Audit - 2026-05-24]] | 6 high · 8 medium · 4 low | | [[Multi-Shop Branch Project Scan - 2026-06-10]] | Full nested-repo scan plus `feature/white-label-shops` documentation sync | | [[Comprehensive Workspace Audit - 2026-06-10]] | Full all-repo security, frontend/backend, deployment, scanner, assist, dependency, and quality audit | --- ## Cross-Cutting Criticals (Fix Immediately) These items appear in multiple audit streams or are exploitable right now. | ID | Severity | What | Where | Fix Effort | |----|----------|------|-------|------------| | SEC-C3 / LOG-CRIT3 | CRITICAL | Simulated transaction bypass (`SIM_*`) active in production | `paymentRoutes.ts:379` | 1 line — wrap in `NODE_ENV !== 'production'` | | SEC-C4 | CRITICAL | `forceVerifyUser` gate is wrong (`!== development` → unset env passes) | `authController.ts:1127` | 1 line — flip to `=== 'development'` | | SEC-C1 / SEC-C2 | CRITICAL | Hardcoded admin password + logged to stdout on every deploy | `init-admin.ts:7,50` | Remove fallback + delete log line + rotate credential | | SEC-C5 / LOG-CRIT4 | CRITICAL | Hardcoded SHKeeper admin credential in source | `shkeeperPayoutService.ts:224` | Move to env var + rotate | | SEC-C6 | CRITICAL | Access and refresh tokens share the same JWT signing secret | `authService.ts:18,54` | Add `REFRESH_TOKEN_SECRET` env var | | LOG-CRIT1 | CRITICAL | Concurrent webhooks can double-process the same payment (no DB-level lock) | `paymentCoordinator.ts` / `shkeeperWebhook.ts` | Atomic `findOneAndUpdate` with status guard | | LOG-CRIT2 | CRITICAL | Parallel Telegram auth creates orphan User documents (TOCTOU on link+user creation) | `authController.ts:377` | Upsert link first, create user only if upsert won | --- ## High-Priority Queue (Fix Before Soft Launch) | ID | Stream | What | Where | |----|--------|------|-------| | SEC-H2 | Security | SHKeeper webhook authentication bypass via `User-Agent` / `crypto` heuristic | `shkeeperWebhook.ts:95` | | SEC-H3 | Security | Request Network `allowTestMode: true` hardcoded — test header skips all sig verification | `requestNetworkRoutes.ts:104` | | SEC-H5 | Security | `global.io.emit(...)` broadcasts financial event data to all connected sockets | `shkeeperWebhook.ts:546` | | SEC-H4 | Security | Typing indicator IDOR — no chat membership check | `app.ts:267` | | SEC-H1 | Security | Telegram in-memory replay map reset on restart; replay possible within 24h window | `telegramService.ts:395` | | LOG-HIGH5 | Logic | `verifyEmailWithCode` non-atomic User.save + TempVerification.delete | `authController.ts:620` | | LOG-HIGH3 | Logic | `refreshTokens[]` array grows unboundedly | `authController.ts:62` | | LOG-HIGH4 | Logic | Blocked Telegram user bypasses block when TelegramLink is deleted | `authController.ts:355` | | LOG-MED5 | Logic | Unauthenticated `/payment/callback` endpoint can mutate any payment status | `paymentControllerRoutes.ts:20` | | PERF-H3 | Performance | Unbounded seller fan-out on new request: `User.find({role:'seller'})` + N socket emits | `PurchaseRequestService.ts:190` | | PERF-H4 | Performance | Full chat document (~250 MB for large chats) loaded into memory for every paginated request | `ChatService.ts:370` | --- ## Medium Priority (Fix Before Public Launch) | ID | Stream | What | Where | |----|--------|------|-------| | SEC-M1 | Security | OTP + reset codes logged in plaintext in all environments | `authController.ts:174,715` | | SEC-M2 | Security | `Math.random()` used for OTP (not CSPRNG) | `authService.ts:226` | | SEC-M3 | Security | No refresh token reuse/theft detection | `authController.ts:510` | | SEC-M4 | Security | Profile update mass-assignment + `validateBeforeSave: false` | `authController.ts:921` | | SEC-M5 | Security | Login Widget auth has no replay protection (Mini App has it) | `authController.ts:110` | | SEC-M6 | Security | No JWT secret length enforcement at startup | `config/index.ts:42` | | LOG-MED1 | Logic | Funds ledger availability check + append not atomic (concurrent double-release possible) | `releaseRefundService.ts:37` | | LOG-MED2 | Logic | `updatePurchaseRequestStatus` bypasses state machine validator | `PurchaseRequestService.ts:551` | | LOG-MED3 | Logic | `getUserPayments` queries `userId` (wrong field) — always returns empty | `paymentService.ts:342` | | LOG-MED4 | Logic | Payout created without verifying a completed inbound payment exists | `shkeeperPayoutService.ts:42` | | PERF-H1 | Performance | N+1: one `Payment.findOne` per request row in buyer dashboard | `PurchaseRequestService.ts:516` | | PERF-H2 | Performance | Missing index on `Payment.purchaseRequestId` | `models/Payment.ts:190` | | PERF-M1 | Performance | Missing compound index `(buyerId, createdAt)` on PurchaseRequest | `models/PurchaseRequest.ts:360` | | PERF-M2 | Performance | Unanchored regex on title/description — full collection scan | `PurchaseRequestService.ts:703` | | PERF-M7 | Performance | `user-online` event broadcast to all sockets globally | `app.ts:300` | --- ## Low Priority / Hardening | ID | Stream | What | |----|--------|------| | SEC-L1 | Security | Passkey challenge debug logs expose all active challenges + all users' passkey IDs | | SEC-L2 | Security | Login attempt counters in-memory (multi-replica bypass possible) | | SEC-L3 | Security | `FRONTEND_URL` unset allows CORS `*` | | SEC-M7 | Security | Legacy `verifyEmail` token route has no expiry check | | LOG-LOW1 | Logic | Duplicate `/payment/callback` route definition | | LOG-MED7 | Logic | `acceptOffer` notification uses undefined `offer.title` | | PERF-M3 | Performance | Double-fetch pattern in update methods (no `.lean()` on pre-check) | | PERF-M6 | Performance | `getSellers()` unbounded — no `.limit()` | | PERF-M8 | Performance | Post-filter after pagination causes wrong `totalItems` count | --- ## Recommended Index Additions Add these to eliminate the collection scans identified in the performance audit: ```ts // models/Payment.ts paymentSchema.index({ purchaseRequestId: 1, status: 1 }); // models/PurchaseRequest.ts PurchaseRequestSchema.index({ buyerId: 1, createdAt: -1 }); PurchaseRequestSchema.index({ status: 1, createdAt: -1 }); PurchaseRequestSchema.index({ categoryId: 1, status: 1, createdAt: -1 }); PurchaseRequestSchema.index({ title: 'text', description: 'text', tags: 'text' }); ``` --- ## Items Confirmed Correctly Handled (PASS) - HMAC timing-safe comparison on all webhooks ✓ - Telegram `initData` HMAC derivation and bot account rejection ✓ - Blocked Telegram user check on existing links ✓ - Refresh token rotation (old removed before new issued) ✓ - Password change / reset clears all refresh tokens ✓ - Socket.IO JWT enforcement on connect ✓ - `join-chat-room` membership check ✓ - bcrypt work factor = 12 ✓ - WebAuthn challenge consumed on first use ✓ - All TTL indexes (TempVerification, TelegramSession, Notification) ✓ - FundsLedgerEntry idempotency key (sparse unique index) ✓ - SHKeeper polling bounded and self-cleaning ✓ - Socket.IO room cleanup on disconnect ✓