Files
nick-doc/09 - Audits/Audit Index - 2026-05-24.md
Siavash Sameni e52ffce48a docs: sync vault with codebase state (2026-06-12)
- Update backend, frontend, scanner, deployment, amanat-assist service docs
- Update System Overview, Scanner Architecture, Telegram Mini App flow
- Update 10 - Services/README.md
- Add Tenant data model, Tenant API reference, Tenant Storefront Flow
- Add Multi-Shop Branch Project Scan (2026-06-10)
- Add tenant.md service doc
- Append activity log entry
- Reflects archived/search/stats route fix and new E2E test suite

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 11:42:18 +04:00

126 lines
7.5 KiB
Markdown

---
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 ✓