- Three-stream audit (security / logic / performance) with 35+ findings derived from actual source code, each with file:line and remediation - Audit Index cross-references criticals across streams into prioritized fix tiers: immediately / before soft launch / before public launch - Telegram Mini App debug handoff documenting what was implemented and all remaining work items with exact file lists and test commands - Updated architecture, data model, auth API, and registration flow docs to reflect Telegram auth, TON wallet, and email verification additions Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
7.2 KiB
7.2 KiB
title, tags, created, status
| title | tags | created | status | |||||
|---|---|---|---|---|---|---|---|---|
| Audit Index — 2026-05-24 |
|
2026-05-24 | 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 |
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:
// 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
initDataHMAC 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-roommembership 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 ✓