audit: 2026-05-30 full-codebase audit — report, issues, docs, runbooks

Full-codebase-audit 2026-05-30 outputs:
- Audit report: 09 - Audits/Full Codebase Audit - 2026-05-30.md
- 81 issue files ISSUE-055..135 (decisions + 1 skipped no-brainer).
- Scanner docs from scratch (was zero): architecture, data model, API ref, payment
  flow, operations runbook + repo README.
- Doc-sync updates across API reference, data models, flows, design system.
- Secret Rotation Runbook (08 - Operations) for the exposed credentials.
- Reusable workflow guide (07 - Development) + .claude/workflows/full-codebase-audit.js.

Issues remain status:open intentionally — the code fixes are uncommitted-then-committed
working-tree changes per repo and aren't "resolved" until merged/deployed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Siavash Sameni
2026-05-30 18:41:44 +04:00
parent eab1d77582
commit dceaf82934
153 changed files with 6276 additions and 179 deletions

View File

@@ -3,6 +3,9 @@ issue: 007
title: "Frontend deleteAccount action calls DELETE /user/profile which has no backend route — account deletion is broken"
severity: critical
domain: Authentication
status: resolved
resolved: 2026-05-29
fix: "Updated deleteAccount in account.ts to call DELETE /auth/account (endpoints.auth.deleteAccount) — the route that exists in authRoutes.ts. Added deleteAccount entry to endpoints.auth in axios.ts."
labels: [bug, frontend, critical, broken-feature]
status: open
created: 2026-05-29

View File

@@ -3,6 +3,9 @@ issue: 008
title: "sendFileMessage posts to wrong endpoint — file uploads silently fail or corrupt text-message handler"
severity: critical
domain: Chat
status: resolved
resolved: 2026-05-29
fix: "Added sendFileMessage: '/chat/:id/messages/file' to endpoints.chat in axios.ts. Updated sendFileMessage in chat.ts to use endpoints.chat.sendFileMessage instead of sendMessage."
labels: [bug, frontend, critical, broken-feature]
status: open
created: 2026-05-29

View File

@@ -3,6 +3,9 @@ issue: 009
title: "archiveConversation sends PUT but backend only accepts PATCH — all archive attempts fail"
severity: critical
domain: Chat
status: resolved
resolved: 2026-05-29
fix: "Changed archiveConversation in chat.ts from axiosInstance.put to axiosInstance.patch — matches backend PATCH /:id/archive route."
labels: [bug, frontend, critical, broken-feature]
status: open
created: 2026-05-29

View File

@@ -3,6 +3,9 @@ issue: 010
title: "Frontend admin updateUserStatus and updateUserRole use PUT but backend only accepts PATCH"
severity: critical
domain: User Management
status: resolved
resolved: 2026-05-29
fix: "Changed updateUserStatus and updateUserRole in user.ts from axiosInstance.put to axiosInstance.patch — matches backend PATCH /admin/:userId/status and PATCH /admin/:userId/role routes."
labels: [bug, frontend, critical, admin, broken-feature]
status: open
created: 2026-05-29

View File

@@ -3,6 +3,9 @@ issue: 011
title: "Frontend updateUserStatus sends 'inactive'/'pending' status values that backend does not accept"
severity: critical
domain: User Management
status: resolved
resolved: 2026-05-29
fix: "Updated updateUserStatus type signature in user.ts from 'active' | 'inactive' | 'pending' to 'active' | 'suspended' | 'deleted' — matching backend's ['active', 'suspended', 'deleted'] validation."
labels: [bug, frontend, critical, admin, type-mismatch]
status: open
created: 2026-05-29

View File

@@ -3,6 +3,9 @@ issue: 013
title: "createProviderPaymentIntent always routes to request-network/intents regardless of provider argument"
severity: critical
domain: Payment
status: resolved
resolved: 2026-05-29
fix: "Replaced stub getProviderIntentEndpoint in payment.ts with a switch on provider: shkeeper → shkeeper.intents, decentralized → decentralized.save, default → requestNetwork.intents."
labels: [bug, frontend, critical, payment, routing]
status: open
created: 2026-05-29

View File

@@ -3,6 +3,9 @@ issue: 014
title: "PaymentProvider TypeScript type excludes 'shkeeper' and 'decentralized' causing UI fallthrough for main payment providers"
severity: critical
domain: Payment
status: resolved
resolved: 2026-05-29
fix: "Added 'shkeeper' and 'decentralized' to PaymentProvider union type in types/payment.ts."
labels: [bug, frontend, critical, payment, type-mismatch]
status: open
created: 2026-05-29

View File

@@ -3,6 +3,9 @@ issue: 015
title: "Simulated transaction SIM_ bypass has no environment guard — can fire in production on wallet connection failure"
severity: critical
domain: Payment
status: resolved
resolved: 2026-05-29
fix: "Backend: wrapped SIM_ bypass in both paymentRoutes.ts and marketplace/routes.ts with process.env.NODE_ENV !== 'production' guard. Frontend: web3-provider.tsx and web3-payment.tsx now throw in production instead of silently returning a fake SIM_ hash."
labels: [security, bug, critical, payment, frontend, bypass]
status: open
created: 2026-05-29

View File

@@ -4,6 +4,9 @@ title: "updatePurchaseRequest uses PUT but backend only registers PATCH — all
severity: major
domain: Purchase Request
labels: [bug, frontend, major, broken-feature]
status: resolved
resolved: 2026-05-29
fix: "Changed axiosInstance.put to axiosInstance.patch in updatePurchaseRequest in marketplace.ts — matches backend PATCH /purchase-requests/:id."
status: open
created: 2026-05-29
source: Doc vs Code Audit 2026-05-29

View File

@@ -3,6 +3,9 @@ issue: 017
title: "updateOffer uses PUT /marketplace/offers/:id but backend registers PATCH /offers/:id — offer edits fail"
severity: major
domain: Seller Offer
status: resolved
resolved: 2026-05-29
fix: "Changed axiosInstance.put to axiosInstance.patch in updateOffer in marketplace.ts — matches backend PATCH /offers/:id."
labels: [bug, frontend, major, broken-feature]
status: open
created: 2026-05-29

View File

@@ -4,6 +4,9 @@ title: "select-offer updateMany has no status filter — overwrites withdrawn/re
severity: major
domain: Seller Offer
labels: [bug, backend, major, data-integrity]
status: resolved
resolved: 2026-05-29
fix: "Added status: { $nin: ['withdrawn', 'rejected'] } to the updateMany filter in select-offer handler — preserves existing terminal statuses."
status: open
created: 2026-05-29
source: Doc vs Code Audit 2026-05-29

View File

@@ -4,6 +4,9 @@ title: "POST /api/marketplace/offers/:id/withdraw HTTP route does not exist —
severity: major
domain: Seller Offer
labels: [missing-feature, backend, frontend, major]
status: resolved
resolved: 2026-05-29
fix: "Added POST /offers/:id/withdraw route in marketplace/routes.ts. Calls sellerOfferService.withdrawOffer with ownership check (seller or admin only)."
status: open
created: 2026-05-29
source: Doc vs Code Audit 2026-05-29

View File

@@ -4,7 +4,9 @@ title: "GET /api/payment/payments/:id/debug has no authentication — full payme
severity: major
domain: Payment
labels: [security, bug, backend, major, missing-auth]
status: open
status: resolved
resolved: 2026-05-29
fix: "Already guarded by authenticateToken + authorizeRoles('admin') at paymentRoutes.ts line 285 (applied as part of ISSUE-005 fix). Confirmed in current code."
created: 2026-05-29
source: Doc vs Code Audit 2026-05-29
---

View File

@@ -4,6 +4,9 @@ title: "GET /api/payment/export has no admin role guard at route level — any a
severity: major
domain: Payment
labels: [security, bug, backend, major, privilege-escalation]
status: resolved
resolved: 2026-05-29
fix: "Already guarded by authenticateToken + authorizeRoles('admin') at paymentRoutes.ts line 79. Confirmed in current code."
status: open
created: 2026-05-29
source: Doc vs Code Audit 2026-05-29

View File

@@ -3,6 +3,9 @@ issue: 024
title: "GET /api/payment/stats has no admin role guard — any authenticated user can read aggregate payment stats"
severity: major
domain: Payment
status: resolved
resolved: 2026-05-29
fix: "Already guarded by authenticateToken + authorizeRoles('admin') at paymentRoutes.ts line 56. Confirmed in current code."
labels: [security, bug, backend, major, privilege-escalation]
status: open
created: 2026-05-29

View File

@@ -3,6 +3,9 @@ issue: 025
title: "GET /api/disputes/statistics has no admin role guard — any authenticated user can access aggregate dispute KPIs"
severity: major
domain: Dispute
status: resolved
resolved: 2026-05-29
fix: "Added authorizeRoles('admin', 'resolver') to GET /statistics in disputeRoutes.ts."
labels: [security, bug, backend, major, privilege-escalation]
status: open
created: 2026-05-29

View File

@@ -3,6 +3,9 @@ issue: 026
title: "GET /notifications/:id only returns user's most-recent notification — all others return 404 erroneously"
severity: major
domain: Notification
status: resolved
resolved: 2026-05-29
fix: "Fixed getNotificationById in notificationController.ts to use Notification.findOne({ _id: id, userId }) instead of fetching page 1 of user notifications."
labels: [bug, backend, major, broken-feature]
status: open
created: 2026-05-29

View File

@@ -4,6 +4,9 @@ title: "confirm-delivery endpoint has no ownership check — any authenticated u
severity: major
domain: Delivery
labels: [security, bug, backend, major, authorization]
status: resolved
resolved: 2026-05-29
fix: "Added buyer ownership check in marketplaceController.confirmDelivery — rejects with 403 if caller is not the request's buyerId and not admin."
status: open
created: 2026-05-29
source: Doc vs Code Audit 2026-05-29

View File

@@ -4,6 +4,9 @@ title: "delivery-code-generated socket event broadcasts raw 6-digit code to enti
severity: major
domain: Delivery
labels: [security, bug, backend, major, delivery]
status: resolved
resolved: 2026-05-29
fix: "Removed 'code' field from the delivery-code-generated socket payload in deliveryService.ts — only metadata (requestId, expiresAt, timestamp) is now broadcast to the room."
status: open
created: 2026-05-29
source: Doc vs Code Audit 2026-05-29

View File

@@ -4,6 +4,9 @@ title: "No brute-force protection on delivery code verification endpoint — 900
severity: major
domain: Delivery
labels: [security, bug, backend, major, brute-force]
status: resolved
resolved: 2026-05-29
fix: "Added deliveryCodeVerifyLimiter (express-rate-limit: 10 attempts per 15 min per requestId+userId) to POST /delivery-code/verify in marketplace/routes.ts."
status: open
created: 2026-05-29
source: Doc vs Code Audit 2026-05-29

View File

@@ -3,6 +3,9 @@ issue: 030
title: "POST /api/payment/payments/cleanup-pending admin check is inside handler only — no middleware-level enforcement"
severity: major
domain: Admin
status: resolved
resolved: 2026-05-29
fix: "Moved admin check to middleware: added authorizeRoles('admin') to cleanup-pending route in paymentRoutes.ts and removed inline role check."
labels: [security, bug, backend, major, missing-auth]
status: open
created: 2026-05-29

View File

@@ -3,6 +3,9 @@ issue: 031
title: "POST /api/points/admin/add admin check is inside handler only — no middleware-level enforcement"
severity: major
domain: Admin
status: resolved
resolved: 2026-05-29
fix: "Replaced inline admin check with authorizeRoles('admin') middleware on POST /admin/add in pointsRoutes.ts."
labels: [security, bug, backend, major, missing-auth]
status: open
created: 2026-05-29

View File

@@ -3,6 +3,9 @@ issue: 032
title: "Admin delete user via legacy endpoint performs hard delete (findByIdAndDelete) instead of soft delete"
severity: major
domain: User Management
status: resolved
resolved: 2026-05-29
fix: "Changed findByIdAndDelete to findByIdAndUpdate({ status: 'deleted' }) in legacy admin delete route in userRoutes.ts."
labels: [bug, frontend, backend, major, data-integrity]
status: open
created: 2026-05-29

View File

@@ -4,6 +4,9 @@ title: "Admin can delete other admin accounts via new controller — legacy admi
severity: major
domain: User Management
labels: [security, bug, backend, major, privilege-escalation]
status: resolved
resolved: 2026-05-29
fix: "Added pre-flight check in userController.deleteUser — looks up target user and returns 403 CANNOT_DELETE_ADMIN if role is 'admin'."
status: open
created: 2026-05-29
source: Doc vs Code Audit 2026-05-29

View File

@@ -3,6 +3,9 @@ issue: 035
title: "Frontend getPaymentStatus and confirmPayment call non-existent endpoints GET /payment/:id/status and POST /payment/:id/confirm"
severity: major
domain: Payment
status: resolved
resolved: 2026-05-29
fix: "Added GET /payments/:id/status and POST /payments/:id/confirm routes to paymentRoutes.ts. Updated getPaymentStatus and confirmPayment in payment.ts to use /payment/payments/:id/status and /payment/payments/:id/confirm."
labels: [bug, frontend, major, broken-feature, dispute]
status: open
created: 2026-05-29

View File

@@ -3,6 +3,9 @@ issue: 039
title: "reset-password-with-code endpoint has no password complexity validation — accepts weak passwords rejected by token-based reset"
severity: major
domain: Authentication
status: resolved
resolved: 2026-05-29
fix: "Added complexity validation in authController.resetPasswordWithCode: min 8 chars, requires uppercase, lowercase, and digit."
labels: [security, bug, backend, major, auth]
status: open
created: 2026-05-29

View File

@@ -4,6 +4,9 @@ title: "POST /api/marketplace/purchase-requests/:id/final-approval creates dummy
severity: major
domain: Purchase Request
labels: [security, bug, backend, major, escrow, bypass]
status: resolved
resolved: 2026-05-29
fix: "Wrapped dummy payment creation in process.env.NODE_ENV !== 'production' guard in marketplace/routes.ts — in production the route returns 404 when no real payment exists."
status: open
created: 2026-05-29
source: Doc vs Code Audit 2026-05-29

View File

@@ -3,6 +3,9 @@ issue: 045
title: "addParticipants frontend sends { participants: string[] } array but backend expects { userId: string } single user"
severity: major
domain: Chat
status: resolved
resolved: 2026-05-29
fix: "Fixed addParticipants in chat.ts to send { participantIds: participants } instead of { participants } — matches backend's expected body shape."
labels: [bug, frontend, major, chat]
status: open
created: 2026-05-29

View File

@@ -4,8 +4,10 @@ title: "Frontend reloadNetworkRegistry and probeChain call backend endpoints tha
severity: major
domain: Admin
labels: [missing-feature, backend, major, admin]
status: open
status: resolved
created: 2026-05-29
resolved: 2026-05-30
fix: "POST /api/admin/rn/networks/reload and POST /api/admin/rn/networks/probe/:chainId implemented in commit 5681abf (task #8)"
source: Doc vs Code Audit 2026-05-29
---

View File

@@ -4,8 +4,10 @@ title: "Frontend getConfirmationThresholdHistory calls GET /api/admin/settings/c
severity: major
domain: Admin
labels: [missing-feature, backend, major, admin]
status: open
status: resolved
created: 2026-05-29
resolved: 2026-05-30
fix: "GET /api/admin/settings/confirmation-thresholds/history implemented in commit 27fb15a (task #9) using ConfigSettingHistory model"
source: Doc vs Code Audit 2026-05-29
---

View File

@@ -3,6 +3,9 @@ issue: 052
title: "'completed' payment status not counted in successfulPayments stats — admin dashboard undercounts"
severity: major
domain: Payment
status: resolved
resolved: 2026-05-29
fix: "Added case 'completed' to the successfulPayments switch in paymentService.ts getPaymentStats."
labels: [backend, bug]
status: open
created: 2026-05-29

View File

@@ -3,6 +3,9 @@ issue: 053
title: "Axios interceptor only retriggers token refresh for 401, not 403"
severity: major
domain: Authentication
status: resolved
resolved: 2026-05-29
fix: "Extended axios response interceptor condition from status === 401 to (status === 401 || status === 403) in axios.ts."
labels: [frontend, bug]
status: open
created: 2026-05-29

View File

@@ -3,6 +3,9 @@ issue: 054
title: "Login rate limiter counts all attempts (not just failures) — users locked out after correct logins"
severity: major
domain: Authentication
status: resolved
resolved: 2026-05-29
fix: "Split checkLoginAttempts into read-only check and new incrementFailedLoginAttempt. authController now only calls increment on failed login paths, not on all attempts."
labels: [backend, bug]
status: open
created: 2026-05-29

View File

@@ -0,0 +1,38 @@
---
issue: 055
title: "DELETE /api/files/delete has no ownership check — requires new persistence layer (NB-27 skipped)"
severity: high
domain: File Management
labels: [security, backend, idor, skipped-nobrainer]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# DELETE /api/files/delete has no ownership check — requires new persistence layer (NB-27 skipped)
**Severity:** high
**Domain:** File Management
**Labels:** security, backend, idor, skipped-nobrainer
## Description
`fileService.deleteFile()` is a pure filesystem path operation — there is no `File` model and no `createdBy`/`owner` field stored anywhere in the database. Any authenticated user who knows (or guesses) another user's filename can delete that file via `DELETE /api/files/delete?filename=...`.
This was triaged as NB-27 but skipped because adding an ownership check requires first creating a new File persistence layer (model + write-on-upload path), which is a larger-than-mechanical change that risks introducing new bugs.
## What is Needed
1. Create a `File` model (or add an `uploads` sub-document to the User model) that records `{ filename, uploadedBy: ObjectId, createdAt }` when a file is stored.
2. Add a middleware or controller check in `fileController.deleteFile` that looks up the record and requires `req.user.id === file.uploadedBy` (or admin).
3. Back-fill the upload handler to write the record on every `POST /api/files/upload`.
## Affected Files
- `backend/src/services/file/fileController.ts` — add ownership check
- `backend/src/services/file/fileRoutes.ts` — (route already protected by `authenticateToken`)
- New: `backend/src/models/File.ts` (or equivalent) — persistence layer
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md)

View File

@@ -0,0 +1,40 @@
---
issue: 056
title: "Backend: verifyPayment and paymentCallback routes unauthenticated — payment completion exploitable"
severity: critical
domain: Payment
labels: [security, backend, authentication, webhook]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Backend: verifyPayment and paymentCallback routes unauthenticated — payment completion exploitable
**Severity:** critical
**Domain:** Payment
**Labels:** security, backend, authentication, webhook
## Description
`POST /payments/verify` and `POST /payments/callback` are registered without `authenticateToken` middleware. Additionally, a non-web3 bypass path (`isWeb3Payment === false`) allows marking a payment completed without any verifiable on-chain or provider proof.
An unauthenticated actor can call `/payments/verify` for any payment ID and trigger the completion side-effects (status change, offer acceptance, escrow release) without owning that payment. The callback endpoint is similarly unguarded, allowing fake webhook injection.
## Options
1. Require `authenticateToken` + ownership check on `/verify`; enforce HMAC signature verification on `/callback` as a provider webhook; remove the `isWeb3Payment=false` bypass so completion always requires verifiable proof.
2. Treat `/callback` as a provider webhook with HMAC only; add auth+ownership for `/verify`.
3. Remove the non-web3 bypass so payments without a verifiable tx cannot be marked completed.
## Recommendation
Add `authenticateToken` + ownership to `/verify`, enforce HMAC/on-chain verification on `/callback` as a webhook endpoint, and remove the `isWeb3Payment=false` bypass so completion always requires verifiable proof.
## Affected Files
- `backend/src/services/payment/paymentControllerRoutes.ts` — lines 2021
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-23

View File

@@ -0,0 +1,39 @@
---
issue: 057
title: "Frontend admin UI routes lack role-based authorization guard"
severity: high
domain: Admin
labels: [security, frontend, authorization]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Frontend admin UI routes lack role-based authorization guard
**Severity:** high
**Domain:** Admin
**Labels:** security, frontend, authorization
## Description
The `/dashboard/admin/*` route tree has no `RoleBasedGuard` at the layout level. Any authenticated user who knows the URL can access and interact with admin pages (trezor, payments-awaiting-confirmation, etc.) without any frontend role enforcement.
## Options
1. Wrap the admin route segment in a single `RoleBasedGuard(admin)` at the layout level — minimal surface, one place to maintain.
2. Add `useRole` checks inside each section view — more granular but repetitive and error-prone.
3. Server-side redirect in Next.js middleware for `/dashboard/admin/*` based on a decoded role claim — strongest but needs role in token/cookie.
## Recommendation
Add a `RoleBasedGuard(admin)` at the admin route-group layout (single chokepoint), and confirm the backend independently enforces admin on every admin API. Defense in depth, low blast radius.
## Affected Files
- `frontend/src/app/dashboard/admin/trezor/page.tsx` and all sibling admin pages
- Admin route-group layout file
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-1

View File

@@ -0,0 +1,38 @@
---
issue: 058
title: "Frontend test payment mode enablable in production via NEXT_PUBLIC env var"
severity: high
domain: Payment
labels: [security, frontend, test-bypass]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Frontend test payment mode enablable in production via NEXT_PUBLIC env var
**Severity:** high
**Domain:** Payment
**Labels:** security, frontend, test-bypass
## Description
`isTestPaymentEnabled()` in `src/web3/services/test-payment-service.ts` is gated only on `NEXT_PUBLIC_ENABLE_TEST_PAYMENT` env flag. Setting this flag in a production deployment (intentionally or by misconfiguration) activates test-payment mode, which bypasses real payment flows.
## Options
1. Gate `isTestPaymentEnabled()` on `(process.env.NODE_ENV !== 'production') AND` the env flag — code-level hard stop.
2. Strip the test-payment code path entirely from production via a build-time define/dead-code elimination.
3. Both: NODE_ENV guard plus CI assertion that the flag is unset in prod env.
## Recommendation
Require `NODE_ENV !== 'production'` in addition to the flag, and add a CI check that `NEXT_PUBLIC_ENABLE_TEST_PAYMENT` is absent in production secrets.
## Affected Files
- `frontend/src/web3/services/test-payment-service.ts:131`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-3

View File

@@ -0,0 +1,38 @@
---
issue: 059
title: "Frontend auth provider clears tokens on any non-403 error including network failures"
severity: high
domain: Authentication
labels: [bug, frontend, session]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Frontend auth provider clears tokens on any non-403 error including network failures
**Severity:** high
**Domain:** Authentication
**Labels:** bug, frontend, session
## Description
`src/auth/context/jwt/auth-provider.tsx:85` clears tokens and logs out the user on any error from the session-check call, including transient network failures and 5xx server errors. A momentary connectivity issue or backend restart silently logs out all active users.
## Options
1. Clear only on 401/403; treat 5xx and network errors as transient (keep tokens, retry).
2. Clear on 401/403 plus explicit invalid-token responses; keep tokens for everything else.
3. Add retry/backoff before deciding to clear.
## Recommendation
Clear tokens only on 401/403; on network/5xx errors keep the session and retry. Confirm acceptable retry behavior and UX with owner.
## Affected Files
- `frontend/src/auth/context/jwt/auth-provider.tsx:85`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-12

View File

@@ -0,0 +1,38 @@
---
issue: 060
title: "Frontend contacts-popover reads userId from non-existent localStorage 'user' key"
severity: high
domain: Chat
labels: [bug, frontend]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Frontend contacts-popover reads userId from non-existent localStorage 'user' key
**Severity:** high
**Domain:** Chat
**Labels:** bug, frontend
## Description
`src/layouts/components/contacts-popover.tsx:61` reads `currentUserId` from `localStorage.getItem('user')`, but no part of the auth flow writes a `'user'` key to localStorage. The result is always `null`, breaking any per-user contact filtering in the popover.
## Options
1. Use the auth context (`useAuthContext`) to get the real user id.
2. Decode the user id from the access token claims.
3. Add a real `'user'` object to storage on login and read it here.
## Recommendation
Pull `currentUserId` from the live auth context rather than a non-existent storage key. Requires confirming the canonical user-id field name in the auth context.
## Affected Files
- `frontend/src/layouts/components/contacts-popover.tsx:61`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-13

View File

@@ -0,0 +1,38 @@
---
issue: 061
title: "Frontend socket context helpers accumulate listeners without dedup — memory/event leaks"
severity: high
domain: Realtime
labels: [bug, frontend, memory-leak]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Frontend socket context helpers accumulate listeners without dedup — memory/event leaks
**Severity:** high
**Domain:** Realtime
**Labels:** bug, frontend, memory-leak
## Description
Socket subscription helpers in `src/socket/contexts/socket-context.tsx:244` do not return unsubscribe functions and do not call `socket.off` when consumers unmount. Each React effect re-registration adds a new listener without removing the old one, causing duplicate event callbacks and memory leaks.
## Options
1. Have each `on*` helper return an unsubscribe function and require consumers to call it in effect cleanup.
2. Make helpers stable (`useCallback`) and internally `off()` the previous handler before `on()`.
3. Centralize event handling in the provider and expose state, not raw subscriptions.
## Recommendation
Return an unsubscribe from each helper and call `socket.off` in cleanup; also memoize the helpers. This touches many consumers — coordinate as a single refactor pass.
## Affected Files
- `frontend/src/socket/contexts/socket-context.tsx:244`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-19

View File

@@ -0,0 +1,38 @@
---
issue: 062
title: "Backend: payment update routes lack ownership/role guards (cluster)"
severity: high
domain: Payment
labels: [security, backend, authorization, idor]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Backend: payment update routes lack ownership/role guards (cluster)
**Severity:** high
**Domain:** Payment
**Labels:** security, backend, authorization, idor
## Description
`PUT /:id updatePayment`, `PATCH /marketplace/payments/:id`, and status-change routes in `paymentControllerRoutes.ts` require only `authenticateToken` — no role check, no ownership check, no status-transition whitelist. Any authenticated user can change any payment's status to any value.
## Options
1. Add an admin-role middleware to all payment status-mutating routes and a status whitelist.
2. Add ownership checks (`req.user.id === buyerId/sellerId`) plus a strict allowed-status-transition validator shared across routes.
3. Both: admin-only for arbitrary status writes; constrained self-service transitions for owners.
## Recommendation
Introduce a shared `requireAdmin` middleware for arbitrary status writes and a centralized transition validator; owners may only trigger whitelisted transitions. This is a business-logic and authZ change across multiple routes.
## Affected Files
- `backend/src/services/payment/paymentControllerRoutes.ts:17`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-22

View File

@@ -0,0 +1,38 @@
---
issue: 063
title: "Backend: legacy marketplace PATCH /payments/:id lets buyer/seller set any status"
severity: high
domain: Payment
labels: [security, backend, authorization]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Backend: legacy marketplace PATCH /payments/:id lets buyer/seller set any status
**Severity:** high
**Domain:** Payment
**Labels:** security, backend, authorization
## Description
`backend/src/services/marketplace/routes.ts:237` registers a legacy PATCH endpoint for payment status that has no admin guard and no status whitelist. Buyers or sellers can set any status value directly.
## Options
1. Add admin role guard + status whitelist.
2. Deprecate/remove the legacy route if superseded by the new payment controller.
3. Restrict to system/internal callers only.
## Recommendation
If the route is legacy and superseded by the new payment controller, remove it. Otherwise gate with admin + whitelist. Needs confirmation that it is unused before removal.
## Affected Files
- `backend/src/services/marketplace/routes.ts:237`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-24

View File

@@ -0,0 +1,39 @@
---
issue: 064
title: "Backend: REQUEST_NETWORK_ALLOW_TEST_WEBHOOKS bypasses signature verification"
severity: high
domain: Payment
labels: [security, backend, webhook, test-bypass]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Backend: REQUEST_NETWORK_ALLOW_TEST_WEBHOOKS bypasses signature verification
**Severity:** high
**Domain:** Payment
**Labels:** security, backend, webhook, test-bypass
## Description
`REQUEST_NETWORK_ALLOW_TEST_WEBHOOKS` env flag disables HMAC signature verification on Request Network webhooks at `requestNetworkRoutes.ts:104` and `requestNetworkAdapter.ts:77`. If this flag is set in production (or if `NODE_ENV` is not production), any unauthenticated actor can forge a webhook and trigger payment completion.
## Options
1. Gate the bypass on `NODE_ENV === 'test'` only and ignore the env flag in production.
2. Require both `NODE_ENV !== 'production'` AND the flag.
3. Remove the env-flag bypass entirely; use a dedicated test harness.
## Recommendation
Allow the bypass only when `NODE_ENV === 'test'`; ignore `REQUEST_NETWORK_ALLOW_TEST_WEBHOOKS` in production. Apply the same fix in `requestNetworkAdapter.ts:77`.
## Affected Files
- `backend/src/services/payment/requestNetwork/requestNetworkRoutes.ts:104`
- `backend/src/services/payment/requestNetwork/requestNetworkAdapter.ts:77`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-25

View File

@@ -0,0 +1,38 @@
---
issue: 065
title: "Backend: RN webhook advances PurchaseRequest to non-existent 'funded' status"
severity: high
domain: Payment
labels: [bug, backend, state-machine]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Backend: RN webhook advances PurchaseRequest to non-existent 'funded' status
**Severity:** high
**Domain:** Payment
**Labels:** bug, backend, state-machine
## Description
`src/services/payment/request-network/requestNetworkWebhook.ts:123` sets `PurchaseRequest.status = 'funded'` when a payment is confirmed. `'funded'` does not exist in `STATUS_PROGRESSION_ORDER` or the status enum. The update is silently dropped by Mongoose, leaving the purchase request in its old status and breaking downstream state transitions.
## Options
1. Use the canonical `'processing'` status that exists in `STATUS_PROGRESSION_ORDER`.
2. Add `'funded'` as a real status across progression and transition maps.
3. Route through the same `propagatePaymentCompletion` path the new flow uses.
## Recommendation
Replace `'funded'` with the canonical status used by the current completion flow (likely `'processing'`), keeping the `escrow.funded` flag if needed. This is a state-machine decision — verify the intended state after on-chain funding confirmation.
## Affected Files
- `backend/src/services/payment/request-network/requestNetworkWebhook.ts:123`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-31

View File

@@ -0,0 +1,39 @@
---
issue: 066
title: "Backend: payout/confirm and release/confirm need canonical terminal status — DEC-32"
severity: high
domain: Payment
labels: [bug, backend, state-machine]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Backend: payout/confirm and release/confirm need canonical terminal status — DEC-32
**Severity:** high
**Domain:** Payment
**Labels:** bug, backend, state-machine
## Description
NB-29 was applied as an interim fix (setting `status:'completed'`), but the correct terminal status for a released/paid-out payment has not been decided. The enum currently has no `'released'` value. Until DEC-32 is resolved, the interim `'completed'` value may be incorrect for payments that need to be distinguished from non-released completions.
## Options
1. Add `'released'` to the Payment status enum and update all status-dependent logic (dashboards, filters, cleanup).
2. Map release/payout to the existing `'completed'` status plus a separate `releasedAt`/payout flag.
3. Introduce a dedicated escrow/payout sub-state field instead of overloading `status`.
## Recommendation
Decide the canonical representation (likely add `'released'` to the enum or a dedicated payout state) and update dashboards/filters consistently. The NB-29 interim fix uses `'completed'` — verify this does not cause dashboard overcounting or misclassification.
## Affected Files
- `backend/src/services/payment/requestNetwork/requestNetworkRoutes.ts:526`
- `backend/src/models/Payment.ts` — enum definition
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-32

View File

@@ -0,0 +1,38 @@
---
issue: 067
title: "Backend: amount-mismatch check runs after payment saved and offers accepted"
severity: medium
domain: Payment
labels: [bug, backend, logic]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Backend: amount-mismatch check runs after payment saved and offers accepted
**Severity:** medium
**Domain:** Payment
**Labels:** bug, backend, logic
## Description
In `paymentController.ts:886-889`, the check comparing `storedAmount` vs `amount` is executed after the payment has already been saved and offers accepted. If there is a mismatch, those side-effects cannot be rolled back, potentially leaving the system in an inconsistent state.
## Options
1. Move the `storedAmount` vs `amount` check before saving/advancing/accepting offers.
2. Wrap the verify flow in a transaction and roll back on mismatch.
3. Validate amount at intent-creation and re-check before completion.
## Recommendation
Reorder so the amount-mismatch check (and ideally a transaction) gates all side-effects. This is a control-flow/business-logic change.
## Affected Files
- `backend/src/services/payment/paymentController.ts:886-889`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-33

View File

@@ -0,0 +1,41 @@
---
issue: 068
title: "Backend: dataCleanupService deletes Payments without provider scoping — risk of destroying escrow records"
severity: high
domain: Admin
labels: [security, backend, data-loss, escrow]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Backend: dataCleanupService deletes Payments without provider scoping — risk of destroying escrow records
**Severity:** high
**Domain:** Admin
**Labels:** security, backend, data-loss, escrow
## Description
`dataCleanupService.ts:121` deletes Payment documents without filtering by `provider`. Request Network and SHKeeper escrow payments are webhook-driven and can take hours to confirm. Sweeping them deletes the ledger records that webhooks need to reconcile, silently destroying multi-seller cart records.
This matches the project memory note: "Any Payment-collection cleanup/orphan query MUST scope by `provider:`."
## Options
1. Scope all payment deletes by provider (exclude `request.network`/`shkeeper` escrow records).
2. Soft-delete instead of hard delete for payments.
3. Disallow payment-collection cleanup entirely from this tool.
## Recommendation
Require provider scoping on every payment delete and prefer soft-delete; never sweep escrow-driven records. This is a data-loss risk.
## Affected Files
- `backend/src/services/admin/dataCleanupService.ts:121`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-38
- Project memory: `feedback_payment_cleanup_provider_filter.md`

View File

@@ -0,0 +1,38 @@
---
issue: 069
title: "Backend: cleanupOldPendingPayments deletes pending RN payments mid-flow"
severity: high
domain: Payment
labels: [bug, backend, data-loss, escrow]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Backend: cleanupOldPendingPayments deletes pending RN payments mid-flow
**Severity:** high
**Domain:** Payment
**Labels:** bug, backend, data-loss, escrow
## Description
`cleanupPendingPayments.ts:42` deletes pending payments after a TTL without excluding webhook-driven providers. Request Network flows can take hours or days to receive on-chain confirmation. A pending RN payment deleted by this cleanup will never be reconciled when the late webhook arrives.
## Options
1. Exclude provider `request.network`/`shkeeper` from the cleanup, or greatly extend the TTL for them.
2. Mark as `expired` instead of deleting, so a late webhook can reconcile.
3. Only delete pending payments that have no associated active purchase request.
## Recommendation
Exclude webhook-driven providers (or use a long TTL) and prefer expire-over-delete so late webhooks can reconcile. This is a data-loss risk.
## Affected Files
- `backend/src/services/payment/cleanupPendingPayments.ts:42`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-39

View File

@@ -0,0 +1,38 @@
---
issue: 070
title: "Backend: notifyAllSellersAboutNewRequest unbounded fan-out — N DB writes + N socket emits per new request"
severity: high
domain: Marketplace
labels: [performance, backend, scalability]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Backend: notifyAllSellersAboutNewRequest unbounded fan-out — N DB writes + N socket emits per new request
**Severity:** high
**Domain:** Marketplace
**Labels:** performance, backend, scalability
## Description
`PurchaseRequestService.ts:196` loops over every seller and issues one `Notification.insertOne` and one `socket.emit` per seller, wrapped in `setTimeout`. With hundreds of sellers this creates hundreds of sequential DB writes and socket emits, blocking the event loop and risking OOM.
## Options
1. Batch with `insertMany` for notifications and a single room/broadcast emit instead of per-seller `setTimeout`.
2. Move to a queue/worker that processes seller notifications asynchronously with concurrency limits.
3. Fan out via a topic/room subscription rather than per-seller writes.
## Recommendation
Use `insertMany` + a single broadcast/room emit, or offload to a queue with bounded concurrency. This is an architectural change.
## Affected Files
- `backend/src/services/marketplace/PurchaseRequestService.ts:196`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-40

View File

@@ -0,0 +1,38 @@
---
issue: 071
title: "Backend: getReferrals N+1 — PurchaseRequest + PointTransaction per referral"
severity: high
domain: Points
labels: [performance, backend, n-plus-1]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Backend: getReferrals N+1 — PurchaseRequest + PointTransaction per referral
**Severity:** high
**Domain:** Points
**Labels:** performance, backend, n-plus-1
## Description
`PointsService.ts:312` issues two sequential DB queries per referred user: one `PurchaseRequest.findOne` and one `PointTransaction.find`. With N referrals, this results in 2×N queries. Under load this will cause slow responses and high DB load.
## Options
1. Batch with `$in` queries and aggregate grouped by referred user.
2. Precompute referral stats in a maintained summary doc.
3. Add pagination plus batched lookups.
## Recommendation
Replace per-referral queries with batched `$in` queries and an aggregation grouped by user; add pagination.
## Affected Files
- `backend/src/services/points/PointsService.ts:312`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-41

View File

@@ -0,0 +1,39 @@
---
issue: 072
title: "Backend: chat messages stored as embedded array — unbounded document growth, 16MB ceiling"
severity: high
domain: Chat
labels: [performance, backend, data-model]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Backend: chat messages stored as embedded array — unbounded document growth, 16MB ceiling
**Severity:** high
**Domain:** Chat
**Labels:** performance, backend, data-model
## Description
`backend/src/models/Chat.ts:173` stores all messages as an embedded array in the Chat document. MongoDB's 16MB document size limit will be hit for active long-running chats. Reads also load the full message history into memory even when only the latest page is needed.
## Options
1. Migrate messages to a `Messages` collection keyed by `chatId` with pagination.
2. Cap embedded messages and archive older ones.
3. Keep embedded but project only needed messages (slice) on reads.
## Recommendation
Plan migration to a dedicated `Messages` collection. This is a large data-model migration that needs careful coordination.
## Affected Files
- `backend/src/models/Chat.ts:173`
- `backend/src/services/chat/ChatService.ts` — all read paths
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-42

View File

@@ -0,0 +1,39 @@
---
issue: 073
title: "Backend: Payment provider enum missing 'shkeeper' — records silently dropped"
severity: high
domain: Payment
labels: [bug, backend, data-model]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Backend: Payment provider enum missing 'shkeeper' — records silently dropped
**Severity:** high
**Domain:** Payment
**Labels:** bug, backend, data-model
## Description
`backend/src/models/Payment.ts:46` defines the `provider` enum without including `'shkeeper'`. Any Payment document saved with `provider: 'shkeeper'` will fail Mongoose validation or be silently dropped. All downstream `Payment.find({provider: 'shkeeper'})` filters also return empty results.
## Options
1. Add `'shkeeper'` to the enum and audit all provider filters/migrations.
2. Migrate existing shkeeper records and standardize the provider taxonomy.
3. Add enum value plus a data migration to repair any silently-dropped values.
## Recommendation
Add `'shkeeper'` to the enum AND run a data audit/migration to repair records and verify every `Payment.find({provider})` filter.
## Affected Files
- `backend/src/models/Payment.ts:46`
- All `Payment.find({ provider: ... })` call sites
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-30

View File

@@ -0,0 +1,39 @@
---
issue: 074
title: "Backend: Telegram bot token + SMTP key (and others) committed in .env.development"
severity: high
domain: Security
labels: [security, backend, secrets, rotation-required]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Backend: Telegram bot token + SMTP key (and others) committed in .env.development
**Severity:** high
**Domain:** Security
**Labels:** security, backend, secrets, rotation-required
## Description
`backend/.env.development` contains live production secrets including the Telegram bot token and Resend SMTP API key (and potentially others). NB-33 replaced the `.env.example` placeholders, but `.env.development` itself contains the live values and is tracked in git.
The `.dockerignore` whitelist (see ISSUE-075) also copies this file into production images.
## What Must Happen
1. Rotate the Telegram bot token immediately.
2. Rotate the Resend SMTP API key immediately.
3. Untrack `.env.development` from git and scrub it from history.
4. Inject secrets at runtime via CI/vault rather than committed env files.
## Affected Files
- `backend/.env.development:31` (and potentially other lines)
- `backend/.dockerignore:14` (whitelist — see ISSUE-075)
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-56
- [[ISSUE-075-backend-dockerignore-whitelists-env-development-into-prod-image|ISSUE-075]]

View File

@@ -0,0 +1,40 @@
---
issue: 075
title: "Backend: .dockerignore whitelists .env.development into production image"
severity: high
domain: Security
labels: [security, backend, secrets, ci-cd]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Backend: .dockerignore whitelists .env.development into production image
**Severity:** high
**Domain:** Security
**Labels:** security, backend, secrets, ci-cd
## Description
`backend/.dockerignore:14` contains `!.env.development`, which negates the `.env*` ignore rule and causes `.env.development` (with live secrets) to be copied into every production Docker image. Any container pull or image inspection exposes the credentials.
## Options
1. Remove the `!.env.development` whitelist so no env file is copied into images.
2. Use a dedicated `.env.production` injected at runtime only.
3. Both: strip env files from image and inject secrets via runtime env.
## Recommendation
Remove the whitelist and never copy env files into images; inject secrets at runtime. Pair with rotating the leaked secrets (see ISSUE-074) and fixing backend config to not load `.env.development` unconditionally (see ISSUE-101).
## Affected Files
- `backend/.dockerignore:14`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-50
- [[ISSUE-074-backend-env-development-committed-with-live-telegram-and-smtp-s|ISSUE-074]]
- [[ISSUE-101-backend-config-loads-env-development-unconditionally|ISSUE-101]]

View File

@@ -0,0 +1,39 @@
---
issue: 076
title: "Scanner: SSRF via unvalidated callbackUrl on intent creation"
severity: high
domain: Scanner
labels: [security, scanner, ssrf]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Scanner: SSRF via unvalidated callbackUrl on intent creation
**Severity:** high
**Domain:** Scanner
**Labels:** security, scanner, ssrf
## Description
`scanner/api.go:143` accepts a caller-supplied `callbackUrl` without validating the scheme, host, or whether it points to an internal/RFC-1918 address. A caller can set `callbackUrl` to any internal service URL and receive webhook deliveries from the scanner, enabling server-side request forgery.
## Options
1. Allowlist schemes (`https` only) and reject RFC-1918/link-local/loopback hosts at create time and at dial time via a custom `DialContext`.
2. Restrict callbacks to a configured set of backend hostnames.
3. Route webhooks through an egress proxy that blocks internal ranges.
## Recommendation
Enforce `https`-only + block private/loopback/link-local at both validation and dial time (custom `DialContext`), ideally plus a host allowlist.
## Affected Files
- `scanner/api.go:143`
- `scanner/webhook.go` — dial path
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-57

View File

@@ -0,0 +1,39 @@
---
issue: 077
title: "Scanner: caller can override confirmation threshold down to 1 — reorg safety bypass"
severity: high
domain: Scanner
labels: [security, scanner, reorg]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Scanner: caller can override confirmation threshold down to 1 — reorg safety bypass
**Severity:** high
**Domain:** Scanner
**Labels:** security, scanner, reorg
## Description
`scanner/api.go:170` accepts a caller-supplied `confirmations` value and uses it as-is without enforcing the chain-config threshold as a floor. A caller can set `confirmations: 1` on a chain that requires 12 confirmations, bypassing reorg safety and causing premature payment confirmation.
## Options
1. Clamp confirmations to `max(callerValue, chainConfigThreshold)` — config is a floor.
2. Ignore caller value entirely; always use chain config.
3. Allow override only above the chain threshold.
## Recommendation
Treat the chain config threshold as a hard floor (`max` of caller and config). Changes reorg-safety semantics.
## Affected Files
- `scanner/api.go:170`
- `scanner/config.go` — chain threshold definition
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-58

View File

@@ -0,0 +1,38 @@
---
issue: 078
title: "Scanner: idempotency path ignores mismatched parameters — silent collision"
severity: high
domain: Scanner
labels: [bug, scanner, idempotency]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Scanner: idempotency path ignores mismatched parameters — silent collision
**Severity:** high
**Domain:** Scanner
**Labels:** bug, scanner, idempotency
## Description
`scanner/api.go:191` returns the existing intent when an `intentId` collision is detected, but does not compare the stored parameters to the incoming request. If a caller reuses an `intentId` with different `amount`, `tokenAddress`, or `callbackUrl`, the scanner silently returns the old intent and monitors the wrong payment parameters.
## Options
1. Return `409 Conflict` if stored params differ from request.
2. Return existing intent only if params match; else error.
3. Treat any reuse as conflict regardless of params.
## Recommendation
Compare stored vs incoming params and return `409 Conflict` on mismatch (return existing only on exact match). Changes API contract.
## Affected Files
- `scanner/api.go:191`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-62

View File

@@ -0,0 +1,39 @@
---
issue: 079
title: "Frontend: Telegram bot token committed in .gitleaks.toml allowlist — must rotate"
severity: high
domain: Security
labels: [security, frontend, secrets, rotation-required]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Frontend: Telegram bot token committed in .gitleaks.toml allowlist — must rotate
**Severity:** high
**Domain:** Security
**Labels:** security, frontend, secrets, rotation-required
## Description
`frontend/.gitleaks.toml:15` contains a value-based allowlist entry with the plaintext Telegram bot token. Value-based allowlist entries in gitleaks effectively publish the secret in the allowlist itself. The same token appears in the backend `.env.development` (see ISSUE-074).
## Options
1. Replace the value-based allowlist with a path/commit-hash allowlist and rotate the token.
2. Remove the allowlist entry entirely after scrubbing the secret from source.
3. Use the handle-gitleaks workflow to triage and remediate.
## Recommendation
Rotate the token, switch to a non-value-based allowlist (path/fingerprint), and scrub history. Coordinate with backend ISSUE-074 since the same token appears there.
## Affected Files
- `frontend/.gitleaks.toml:15`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-78
- [[ISSUE-074-backend-env-development-committed-with-live-telegram-and-smtp-s|ISSUE-074]]

View File

@@ -0,0 +1,39 @@
---
issue: 080
title: "Frontend: open redirect via unvalidated returnTo in GuestGuard"
severity: medium
domain: Authentication
labels: [security, frontend, open-redirect]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Frontend: open redirect via unvalidated returnTo in GuestGuard
**Severity:** medium
**Domain:** Authentication
**Labels:** security, frontend, open-redirect
## Description
`src/auth/guard/guest-guard.tsx:27` passes `returnTo` directly to `router.replace()` without validating that it is a same-origin relative path. An attacker can craft a link with `returnTo=//evil.com` or `returnTo=https://evil.com` to redirect users after login.
## Options
1. Allow only same-origin relative paths starting with a single `/` and not `//` — strict, safe default.
2. Allowlist of known internal path prefixes — safest but must be maintained.
3. Parse with `URL()` against `window.origin` and reject cross-origin — robust but slightly more code.
## Recommendation
Reject any `returnTo` that does not match `^/(?!/)` (single leading slash, not protocol-relative), else fall back to default landing route. One small helper, applied everywhere `returnTo` is consumed.
## Affected Files
- `frontend/src/auth/guard/guest-guard.tsx:27`
- Any other component that reads and acts on `returnTo`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-2

View File

@@ -0,0 +1,40 @@
---
issue: 081
title: "Frontend: auth tokens stored in localStorage — XSS-accessible"
severity: medium
domain: Authentication
labels: [security, frontend, session]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Frontend: auth tokens stored in localStorage — XSS-accessible
**Severity:** medium
**Domain:** Authentication
**Labels:** security, frontend, session
## Description
`src/auth/context/jwt/action.ts:100` stores access and refresh tokens in `localStorage`. Any XSS vulnerability can steal these tokens and impersonate the user. The risk is compounded by the lack of a Content-Security-Policy (see ISSUE-083).
## Options
1. Move refresh token to HttpOnly cookie, keep short-lived access token in memory — strong, but requires backend cookie + CSRF work.
2. Keep localStorage but add strict CSP + sanitization to reduce XSS surface — cheaper, weaker.
3. Full cookie-based session with `SameSite=strict` — strongest, largest change to axios/socket auth.
## Recommendation
Plan a migration to HttpOnly refresh cookie + in-memory access token, coordinated with backend. This is a large, cross-cutting change that breaks many call sites — treat as a deliberate project.
## Affected Files
- `frontend/src/auth/context/jwt/action.ts:100`
- `frontend/src/lib/axios.ts` — auth header injection
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-4
- [[ISSUE-083-frontend-no-content-security-policy-header-in-next-config|ISSUE-083]]

View File

@@ -0,0 +1,38 @@
---
issue: 082
title: "Frontend: wallet ownership signature verification is a no-op"
severity: medium
domain: Web3
labels: [security, frontend, wallet]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Frontend: wallet ownership signature verification is a no-op
**Severity:** medium
**Domain:** Web3
**Labels:** security, frontend, wallet
## Description
`src/sections/account/account-wallet-connection.tsx:425` has a `verifySignature` stub that always passes. The frontend does not actually verify that the signature matches the claimed wallet address, meaning any wallet address can be submitted without proof of ownership.
## Options
1. Implement real client-side verification with `ethers.verifyMessage(message, signature) === wallet.address` as a UX pre-check, keep backend authoritative.
2. Remove the misleading `verifySignature` stub and rely solely on backend (document this).
3. Both: client pre-check and confirm backend enforcement exists.
## Recommendation
Implement `ethers.verifyMessage` as a UX gate AND verify the backend enforces ownership. The stub is actively misleading.
## Affected Files
- `frontend/src/sections/account/account-wallet-connection.tsx:425`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-6

View File

@@ -0,0 +1,39 @@
---
issue: 083
title: "Frontend: no Content-Security-Policy header in Next.js config"
severity: medium
domain: Security
labels: [security, frontend, csp]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Frontend: no Content-Security-Policy header in Next.js config
**Severity:** medium
**Domain:** Security
**Labels:** security, frontend, csp
## Description
`next.config.ts:29` does not set a `Content-Security-Policy` header. Without CSP, XSS attacks have unrestricted script execution, making token theft (localStorage) and DOM-based attacks much easier.
## Options
1. Ship `Content-Security-Policy-Report-Only` first to collect violations, then enforce — safe rollout.
2. Enforce a moderate CSP allowing required hosts (Telegram, WalletConnect, Mapbox, Sentry) with nonces for inline scripts.
3. Strict CSP with nonces and removal of all inline scripts — strongest but requires refactoring `layout.tsx` inline scripts.
## Recommendation
Ship `Content-Security-Policy-Report-Only` first, gather violations for a week, then enforce. Inline scripts in `layout.tsx` must move to nonces. Non-trivial rollout.
## Affected Files
- `frontend/next.config.ts:29`
- `frontend/src/app/layout.tsx` — inline scripts that need nonces
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-8

View File

@@ -0,0 +1,39 @@
---
issue: 084
title: "Frontend: console.error/warn suppression masks production errors"
severity: medium
domain: Observability
labels: [bug, frontend, logging]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Frontend: console.error/warn suppression masks production errors
**Severity:** medium
**Domain:** Observability
**Labels:** bug, frontend, logging
## Description
`src/app/layout.tsx:95` overrides `console.error` and `console.warn` globally in all environments. In production this suppresses real errors from reaching Sentry or developer tools, making production issues invisible. See also ISSUE-120 (the polling suppression interval that triggers this).
## Options
1. Remove the global override in production entirely (allow real errors through to Sentry).
2. Scope suppression to the specific known Emotion/MUI warning string only.
3. Keep dev-only suppression, none in production.
## Recommendation
Remove global suppression in production and, at most, filter the one known benign warning by message substring in development. Coordinate with ISSUE-120 since it is the same script.
## Affected Files
- `frontend/src/app/layout.tsx:95`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-10
- [[ISSUE-120-frontend-50ms-setinterval-console-suppression-script-in-root-l|ISSUE-120]]

View File

@@ -0,0 +1,38 @@
---
issue: 085
title: "Frontend: token refresh queue dispatches with undefined Authorization header"
severity: medium
domain: Authentication
labels: [bug, frontend, session]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Frontend: token refresh queue dispatches with undefined Authorization header
**Severity:** medium
**Domain:** Authentication
**Labels:** bug, frontend, session
## Description
`src/lib/axios.ts:136` flushes queued requests after a refresh attempt unconditionally. When the refresh yields no token (expired session, network error), queued requests are dispatched with `Authorization: Bearer undefined`, which backend middleware treats as an invalid token, causing all queued requests to fail with 401 — but no logout or error surfacing occurs.
## Options
1. On no token: reject queued requests (fail fast) and trigger logout/redirect.
2. Skip the `forEach` when `newAccessToken` is falsy and let requests retry later.
3. Move the `forEach` inside the `if(newAccessToken)` guard and reject the queue in the `else` branch.
## Recommendation
Move flush inside the token guard and explicitly reject queued callbacks so they error rather than retry with `'Bearer undefined'`.
## Affected Files
- `frontend/src/lib/axios.ts:136`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-11

View File

@@ -0,0 +1,39 @@
---
issue: 086
title: "Frontend: PaymentDetailsView status dropdown exposed to all users"
severity: medium
domain: Payment
labels: [security, frontend, authorization]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Frontend: PaymentDetailsView status dropdown exposed to all users
**Severity:** medium
**Domain:** Payment
**Labels:** security, frontend, authorization
## Description
`src/sections/payment/view/payment-details-view.tsx:312` renders a status-change dropdown without an `isAdmin` check. `PaymentDetailsCard` already gates this correctly with `isAdmin`, but the view-level dropdown bypasses that check, allowing any authenticated user to attempt a status change from the UI.
## Options
1. Wrap the status `TextField` in an `isAdmin` check mirroring `PaymentDetailsCard`.
2. Hide the control for non-admins and rely on backend role enforcement too.
3. Move status changes to an admin-only view.
## Recommendation
Gate the control behind `isAdmin` (as `PaymentDetailsCard` already does) AND ensure backend enforces admin for the underlying route (see ISSUE-062). UI gating alone is insufficient.
## Affected Files
- `frontend/src/sections/payment/view/payment-details-view.tsx:312`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-15
- [[ISSUE-062-backend-payment-update-routes-lack-ownership-role-guards|ISSUE-062]]

View File

@@ -0,0 +1,38 @@
---
issue: 087
title: "Frontend: getPaymentStatus and checkPaymentStatus hit different endpoints"
severity: medium
domain: Payment
labels: [bug, frontend]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Frontend: getPaymentStatus and checkPaymentStatus hit different endpoints
**Severity:** medium
**Domain:** Payment
**Labels:** bug, frontend
## Description
`src/actions/payment.ts:62` has two functions — `getPaymentStatus` and `checkPaymentStatus` — that appear to serve the same purpose but call different endpoints (`/payment/:id/status` vs `/payment/payments/:id/status`). Only one of these can be the correct backend path.
## Options
1. Point `getPaymentStatus` at the registry-defined `/payment/:id/status` and deduplicate with `checkPaymentStatus`.
2. Add `/payment/payments/:id/status` to the endpoints registry if backend truly serves it.
3. Remove the redundant `getPaymentStatus` and migrate callers to `checkPaymentStatus`.
## Recommendation
Verify the real backend route, then collapse to a single function using the registry path. Could break callers, so verify before removing.
## Affected Files
- `frontend/src/actions/payment.ts:62`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-16

View File

@@ -0,0 +1,38 @@
---
issue: 088
title: "Frontend: adminWalletPayout falls back to literal 'admin' adminUserId"
severity: medium
domain: Payment
labels: [bug, frontend, authorization]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Frontend: adminWalletPayout falls back to literal 'admin' adminUserId
**Severity:** medium
**Domain:** Payment
**Labels:** bug, frontend, authorization
## Description
`src/actions/payment.ts:663` sends `adminUserId: adminUserId || 'admin'`. When the admin user ID is not available (e.g. context not loaded), the string literal `'admin'` is sent to the backend. This may match a user record named 'admin' unintentionally or corrupt audit trails.
## Options
1. Require `adminUserId` and throw/abort if absent (no fallback).
2. Source `adminUserId` from the authenticated admin context automatically.
3. Keep a fallback but use the real admin id from token rather than the string `'admin'`.
## Recommendation
Remove the `'admin'` literal and require a real admin id from the auth context; abort if unavailable. This affects audit/authorization semantics.
## Affected Files
- `frontend/src/actions/payment.ts:663`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-17

View File

@@ -0,0 +1,38 @@
---
issue: 089
title: "Frontend: admin payments-awaiting-confirmation polls every 12s unconditionally"
severity: medium
domain: Admin
labels: [performance, frontend, polling]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Frontend: admin payments-awaiting-confirmation polls every 12s unconditionally
**Severity:** medium
**Domain:** Admin
**Labels:** performance, frontend, polling
## Description
`payments-awaiting-confirmation-list-view.tsx:95` polls the backend every 12 seconds regardless of tab visibility or socket connectivity. NB-49 added visibility-gating as a no-brainer, but the longer-term question of whether to replace polling with socket subscriptions remains.
## Options
1. Pause polling when `document.visibilityState === 'hidden'` and increase interval (applied via NB-49).
2. Replace polling with a socket subscription for awaiting-confirmation events — best but needs backend events.
3. Both: visibility-gated polling now, socket later.
## Recommendation
NB-49 applied the visibility gate. Plan a socket subscription for awaiting-confirmation events to eliminate polling entirely. Confirm acceptable notification latency with owner.
## Affected Files
- `frontend/src/sections/admin/payments-awaiting-confirmation/payments-awaiting-confirmation-list-view.tsx:95`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-18

View File

@@ -0,0 +1,40 @@
---
issue: 090
title: "Frontend: chat views re-fetch full conversation on every new-message socket event"
severity: medium
domain: Chat
labels: [performance, frontend]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Frontend: chat views re-fetch full conversation on every new-message socket event
**Severity:** medium
**Domain:** Chat
**Labels:** performance, frontend
## Description
`src/sections/chat/view/buyer-chat-view.tsx:157` calls the full conversation fetch whenever a `new-message` socket event fires. With the chat messages stored as an embedded array (see ISSUE-072), this re-fetches the entire conversation history on every incoming message, causing high network and backend load in active chats.
## Options
1. Append the message from the socket payload to local state; only re-fetch on gaps/errors.
2. Keep re-fetch but debounce it.
3. Hybrid: optimistic append plus periodic reconciliation.
## Recommendation
Append the payload message directly and reconcile only on inconsistency. This changes data-flow correctness assumptions.
## Affected Files
- `frontend/src/sections/chat/view/buyer-chat-view.tsx:157`
- `frontend/src/sections/chat/view/seller-chat-view.tsx` (similar pattern)
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-20
- [[ISSUE-072-backend-chat-messages-stored-as-embedded-array-unbounded-growth|ISSUE-072]]

View File

@@ -0,0 +1,39 @@
---
issue: 091
title: "Frontend: dual socket connections (SocketProvider + socketService singleton)"
severity: medium
domain: Realtime
labels: [bug, frontend, performance]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Frontend: dual socket connections (SocketProvider + socketService singleton)
**Severity:** medium
**Domain:** Realtime
**Labels:** bug, frontend, performance
## Description
`src/socket/lib/socket-service.ts:217` creates a standalone socket.io connection separate from the `SocketProvider` context. Both may connect simultaneously, resulting in duplicate connections to the backend, doubled event delivery, and doubled auth overhead.
## Options
1. Make `socketService` delegate to the `SocketProvider` connection (single source of truth).
2. Migrate all `actions/chat.ts` usages to the context provider and delete `socketService`.
3. Keep both but ensure only one actually connects.
## Recommendation
Consolidate onto `SocketProvider` and refactor `socketService` callers; remove the duplicate connection. This is a large refactor.
## Affected Files
- `frontend/src/socket/lib/socket-service.ts:217`
- `frontend/src/actions/chat.ts` — socketService callers
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-21

View File

@@ -0,0 +1,39 @@
---
issue: 092
title: "Backend: JWT refresh and access tokens share the same secret; middleware skips token type check"
severity: medium
domain: Authentication
labels: [security, backend, jwt]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Backend: JWT refresh and access tokens share the same secret; middleware skips token type check
**Severity:** medium
**Domain:** Authentication
**Labels:** security, backend, jwt
## Description
`src/services/auth/authService.ts:44` signs both access and refresh tokens with the same secret. `authenticateToken` middleware does not check `token.type`, so a refresh token can be presented as an access token and accepted by protected routes.
## Options
1. Add a `type:'access'` claim check in `authenticateToken` middleware (reject `type:'refresh'`).
2. Use separate secrets for access vs refresh tokens.
3. Add audience/issuer claims and verify them in middleware.
## Recommendation
Enforce a token-type check in the middleware (reject refresh tokens) and ideally split secrets. Both changes touch core auth verification.
## Affected Files
- `backend/src/services/auth/authService.ts:44`
- `backend/src/middleware/authenticateToken.ts` (or equivalent)
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-26

View File

@@ -0,0 +1,39 @@
---
issue: 093
title: "Backend: addEvidence has no participant ownership check on disputes"
severity: medium
domain: Dispute
labels: [security, backend, authorization]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Backend: addEvidence has no participant ownership check on disputes
**Severity:** medium
**Domain:** Dispute
**Labels:** security, backend, authorization
## Description
`src/routes/disputeRoutes.ts:32` registers the `addEvidence` route with only `authenticateToken`. Any authenticated user can submit evidence to any dispute, not just the buyer/seller/admin who are participants.
## Options
1. Verify `req.user.id` is buyer or seller of the dispute before accepting evidence.
2. Allow admins plus participants only.
3. Add participant check in controller and reject otherwise.
## Recommendation
Add a participant (buyer/seller/admin) check in `addEvidence` before persisting. This is an authorization-logic change.
## Affected Files
- `backend/src/routes/disputeRoutes.ts:32`
- `backend/src/controllers/disputeController.ts``addEvidence` handler
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-27

View File

@@ -0,0 +1,38 @@
---
issue: 094
title: "Backend: selectOffer does not verify buyer owns the purchase request"
severity: medium
domain: Marketplace
labels: [security, backend, idor]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Backend: selectOffer does not verify buyer owns the purchase request
**Severity:** medium
**Domain:** Marketplace
**Labels:** security, backend, idor
## Description
`src/services/marketplace/marketplaceController.ts:1029` handles `selectOffer` without checking that `req.user.id` matches the `purchaseRequest.buyerId`. Any authenticated user who knows the purchase request ID can select an offer on someone else's request.
## Options
1. Reject when `req.user.id !== purchaseRequest.buyerId`.
2. Allow buyer-owner or admin only.
3. Atomic `findOneAndUpdate` scoped by `buyerId`.
## Recommendation
Enforce `req.user.id === purchaseRequest.buyerId` (admin override allowed). This changes who can accept offers.
## Affected Files
- `backend/src/services/marketplace/marketplaceController.ts:1029`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-28

View File

@@ -0,0 +1,38 @@
---
issue: 095
title: "Backend: getUserStats has no ownership/admin check (IDOR)"
severity: medium
domain: Payment
labels: [security, backend, idor]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Backend: getUserStats has no ownership/admin check (IDOR)
**Severity:** medium
**Domain:** Payment
**Labels:** security, backend, idor
## Description
`paymentControllerRoutes.ts:13` serves `GET /api/payment/stats/:userId` without checking that `req.user.id === req.params.userId` or that the caller is an admin. Any authenticated user can retrieve payment statistics for any other user ID.
## Options
1. Require `req.user.id === req.params.userId`, or admin.
2. Admin-only endpoint.
3. Scope query to the authenticated user, ignore param.
## Recommendation
Require self-or-admin (`req.user.id === userId || isAdmin`).
## Affected Files
- `backend/src/services/payment/paymentControllerRoutes.ts:13`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-29

View File

@@ -0,0 +1,38 @@
---
issue: 096
title: "Backend: validateStatusTransition requires escrowState 'funded' never set on completed payments"
severity: medium
domain: Payment
labels: [bug, backend, state-machine]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Backend: validateStatusTransition requires escrowState 'funded' never set on completed payments
**Severity:** medium
**Domain:** Payment
**Labels:** bug, backend, state-machine
## Description
`marketplaceController.ts:570-583` guards a status transition by querying for `{ status:'completed', escrowState:'funded' }`. The completion flow never sets `escrowState:'funded'`; it is set earlier (at funding time). A genuinely completed payment may not match this query, causing the guard to reject valid transitions.
## Options
1. Query by `status:'completed'` only (drop `escrowState:'funded'`).
2. Ensure the completion flow sets `escrowState:'funded'` consistently and keep the guard.
3. Match on a documented completed-payment predicate aligned with the actual write path.
## Recommendation
Align the guard with what the completion flow actually writes — most safely query `status:'completed'` without the `escrowState` constraint, after confirming no false positives.
## Affected Files
- `backend/src/services/marketplace/marketplaceController.ts:570-583`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-34

View File

@@ -0,0 +1,38 @@
---
issue: 097
title: "Backend: validTransitions map missing 'in_negotiation' key"
severity: medium
domain: Marketplace
labels: [bug, backend, state-machine]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Backend: validTransitions map missing 'in_negotiation' key
**Severity:** medium
**Domain:** Marketplace
**Labels:** bug, backend, state-machine
## Description
`marketplaceController.ts:544-555` defines a `validTransitions` map for PurchaseRequest status transitions but has no entry for `'in_negotiation'`. A PurchaseRequest in the `in_negotiation` state cannot transition to any other state via this validator.
## Options
1. Add `'in_negotiation'` with its allowed next statuses (e.g. `payment`, `cancelled`).
2. Treat missing key as 'allow same-tier transitions' default.
3. Derive transitions from `STATUS_PROGRESSION_ORDER` instead of a hand-maintained map.
## Recommendation
Add an explicit `'in_negotiation'` entry with the correct next statuses. Requires product/state-machine confirmation of valid transitions.
## Affected Files
- `backend/src/services/marketplace/marketplaceController.ts:544-555`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-35

View File

@@ -0,0 +1,38 @@
---
issue: 098
title: "Backend: in-memory seenDeliveryIds resets on restart — webhook dedup lost"
severity: medium
domain: Payment
labels: [bug, backend, idempotency]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Backend: in-memory seenDeliveryIds resets on restart — webhook dedup lost
**Severity:** medium
**Domain:** Payment
**Labels:** bug, backend, idempotency
## Description
`requestNetworkRoutes.ts:16` maintains webhook deduplication via an in-memory `Set` of delivery IDs. This Set is lost on every server restart or pod restart. A redelivered webhook that arrived before the restart will be processed twice, potentially triggering double payment completion.
## Options
1. Persist processed delivery IDs in MongoDB (unique index) with TTL.
2. Use Redis SET with TTL for delivery-id dedup.
3. Make webhook handlers idempotent by keying state transitions on payment status guards.
## Recommendation
Persist delivery IDs (Mongo unique index or Redis) AND make handlers idempotent via status guards. This is an infra/state decision.
## Affected Files
- `backend/src/services/payment/requestNetwork/requestNetworkRoutes.ts:16`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-36

View File

@@ -0,0 +1,38 @@
---
issue: 099
title: "Backend: on-demand RN reconciliation in getPaymentById can race — double-processing risk"
severity: medium
domain: Payment
labels: [bug, backend, concurrency]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Backend: on-demand RN reconciliation in getPaymentById can race — double-processing risk
**Severity:** medium
**Domain:** Payment
**Labels:** bug, backend, concurrency
## Description
`paymentController.ts:407-466` triggers RN reconciliation on every `GET /payment/:id` call. If two browser tabs or requests call this concurrently on a pending payment, both can read `status:'pending'` and both trigger the completion side-effects before either write commits.
## Options
1. Use an atomic `findOneAndUpdate` guarded on `status:'pending'` so only one writer wins.
2. Add a distributed lock (Redis) around reconciliation per payment.
3. Move reconciliation off the read path into a single-writer background job.
## Recommendation
Make the status transition atomic (`findOneAndUpdate` filtering on current status) so only the first concurrent caller advances it; ideally move reconciliation off the GET path.
## Affected Files
- `backend/src/services/payment/paymentController.ts:407-466`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-37

View File

@@ -0,0 +1,38 @@
---
issue: 100
title: "Backend: updatePurchaseRequest does findById then findByIdAndUpdate — non-atomic race"
severity: medium
domain: Marketplace
labels: [bug, backend, concurrency]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Backend: updatePurchaseRequest does findById then findByIdAndUpdate — non-atomic race
**Severity:** medium
**Domain:** Marketplace
**Labels:** bug, backend, concurrency
## Description
`PurchaseRequestService.ts:413` reads the document first (`findById`) to check allowed status transitions, then writes it (`findByIdAndUpdate`). Between the read and the write, another request can change the status, defeating the transition guard.
## Options
1. Use `findOneAndUpdate` with `status:{$in:allowedCurrentStatuses}` condition — atomic.
2. Keep two queries but wrap in a transaction.
3. Leave as-is.
## Recommendation
Use a single conditional `findOneAndUpdate` to make the transition atomic and halve round-trips.
## Affected Files
- `backend/src/services/marketplace/PurchaseRequestService.ts:413`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-46

View File

@@ -0,0 +1,39 @@
---
issue: 101
title: "Backend: config loads .env.development unconditionally regardless of NODE_ENV"
severity: medium
domain: Security
labels: [security, backend, configuration]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Backend: config loads .env.development unconditionally regardless of NODE_ENV
**Severity:** medium
**Domain:** Security
**Labels:** security, backend, configuration
## Description
`backend/src/shared/config/index.ts:4` loads `.env.development` unconditionally. In a production environment where `NODE_ENV=production`, this still reads and applies `.env.development` values, overriding injected production secrets with development values. Paired with `.dockerignore` whitelisting this file (ISSUE-075), it means dev secrets are active in prod images.
## Options
1. Load `.env.<NODE_ENV>` conditionally, never fall back to dev file in production.
2. Only load dotenv when not in production (rely on injected env in prod).
3. Load env-specific file and fail fast if required vars are missing.
## Recommendation
Load the env-file matching `NODE_ENV` (or none in production) and never default to `.env.development`. Pair with ISSUE-075.
## Affected Files
- `backend/src/shared/config/index.ts:4`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-49
- [[ISSUE-075-backend-dockerignore-whitelists-env-development-into-prod-image|ISSUE-075]]

View File

@@ -0,0 +1,39 @@
---
issue: 102
title: "Backend: 14 high-severity npm vulnerabilities, no audit step in CI"
severity: medium
domain: Dependencies
labels: [security, backend, dependencies, ci-cd]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Backend: 14 high-severity npm vulnerabilities, no audit step in CI
**Severity:** medium
**Domain:** Dependencies
**Labels:** security, backend, dependencies, ci-cd
## Description
`npm audit` reports 14 high-severity vulnerabilities in backend production dependencies (packages include mongoose, multer, axios, and others). No CI pipeline step runs `npm audit`, so new vulnerabilities silently accumulate.
## Options
1. Add `npm audit` (or `audit-ci`) as a non-blocking report step first, then make blocking.
2. Upgrade the flagged packages and add a blocking audit gate.
3. Adopt Renovate/Dependabot plus a CI audit step.
## Recommendation
Add an audit step (start as report), prioritize upgrading the 14 highs, then make the gate blocking. Package upgrades risk breakage — test before making the gate mandatory.
## Affected Files
- `backend/package.json`
- `backend/.woodpecker/development.yml` — add audit step
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-51

View File

@@ -0,0 +1,38 @@
---
issue: 103
title: "Backend: react/react-dom in backend production dependencies"
severity: medium
domain: Dependencies
labels: [backend, dependencies, cleanup]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Backend: react/react-dom in backend production dependencies
**Severity:** medium
**Domain:** Dependencies
**Labels:** backend, dependencies, cleanup
## Description
`backend/package.json:83` lists `react` and `react-dom` as production dependencies. These are large packages with no apparent usage in the backend (no SSR email templates confirmed). They inflate the production bundle and increase the attack surface.
## Options
1. Remove both after confirming zero imports.
2. Move to `devDependencies` if only used in tooling.
3. Keep if some build step requires them.
## Recommendation
Confirm no runtime/SSR usage, then remove. Because removal could break an unseen template render, verify all imports before removing.
## Affected Files
- `backend/package.json:83`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-52

View File

@@ -0,0 +1,38 @@
---
issue: 104
title: "Backend: native bcrypt addon present alongside bcryptjs — unnecessary build toolchain dependency"
severity: medium
domain: Dependencies
labels: [backend, dependencies, cleanup]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Backend: native bcrypt addon present alongside bcryptjs — unnecessary build toolchain dependency
**Severity:** medium
**Domain:** Dependencies
**Labels:** backend, dependencies, cleanup
## Description
`backend/package.json:67` includes `bcrypt` (native C++ addon, requires build toolchain) alongside `bcryptjs` (pure JS). Code uses `bcryptjs`. The native addon adds unnecessary native build complexity and is an unused dependency.
## Options
1. Remove `bcrypt` (keep `bcryptjs`) after confirming no imports and no migration need.
2. Standardize on native `bcrypt` instead (faster) and migrate hashes-compatible.
3. Leave both.
## Recommendation
Confirm `bcryptjs` is the sole hasher and remove native `bcrypt` to drop the build toolchain requirement. Hashing libs are sensitive — verify before removing.
## Affected Files
- `backend/package.json:67`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-53

View File

@@ -0,0 +1,38 @@
---
issue: 105
title: "Backend: no startup validation of required env vars — silent misconfiguration"
severity: medium
domain: Configuration
labels: [backend, reliability, configuration]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Backend: no startup validation of required env vars — silent misconfiguration
**Severity:** medium
**Domain:** Configuration
**Labels:** backend, reliability, configuration
## Description
`backend/src/shared/config/index.ts:32` reads env vars without validating they are present or have correct types. A misconfigured deployment (missing `JWT_SECRET`, `MONGODB_URI`, or `SMTP_PORT`) starts silently and fails only at runtime when those vars are first used, making misconfiguration hard to diagnose.
## Options
1. Validate required vars with a schema (zod/envalid) and exit on missing/NaN.
2. Manual assertions for the critical few (`PORT`, `JWT_SECRET`, `MONGODB_URI`, `SMTP_PORT`).
3. Log-and-continue warnings only.
## Recommendation
Add schema-based validation that fails fast on missing/invalid required vars. Changes startup behavior.
## Affected Files
- `backend/src/shared/config/index.ts:32`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-54

View File

@@ -0,0 +1,41 @@
---
issue: 106
title: "Backend: dual lockfiles (yarn.lock + package-lock.json) diverge"
severity: medium
domain: Dependencies
labels: [backend, ci-cd, dependencies]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Backend: dual lockfiles (yarn.lock + package-lock.json) diverge
**Severity:** medium
**Domain:** Dependencies
**Labels:** backend, ci-cd, dependencies
## Description
`backend/package.json:117` has both `yarn.lock` and `package-lock.json` in the repo, and they are not kept in sync. CI and production use npm; the `packageManager` field references yarn. The two lockfiles represent different resolved dependency trees, so local yarn installs and CI npm installs can diverge.
## Options
1. Standardize on npm + `package-lock.json` (matches CI/prod), delete `yarn.lock`, fix `Dockerfile.dev`.
2. Standardize on yarn (matches `packageManager` field), make CI use yarn.
3. Keep both but regenerate and pin.
## Recommendation
Pick one (npm matches prod/CI), delete the other lockfile, align Dockerfiles, and regenerate.
## Affected Files
- `backend/package.json`
- `backend/yarn.lock`
- `backend/package-lock.json`
- `backend/Dockerfile.dev`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-55

View File

@@ -0,0 +1,38 @@
---
issue: 107
title: "Scanner: TronGrid pagination next-URL used unvalidated — SSRF via API response"
severity: medium
domain: Scanner
labels: [security, scanner, ssrf]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Scanner: TronGrid pagination next-URL used unvalidated — SSRF via API response
**Severity:** medium
**Domain:** Scanner
**Labels:** security, scanner, ssrf
## Description
`scanner/tron_chain.go:180` follows the `Links.Next` URL from a TronGrid API response without validating that it has the same scheme and host as the configured RPC URL. A compromised or malicious TronGrid response can redirect the scanner to arbitrary internal endpoints.
## Options
1. Require next URL to share scheme+host with `chain.RpcURL`.
2. Reconstruct pagination params ourselves instead of trusting `Links.Next`.
3. Allowlist the TronGrid host.
## Recommendation
Validate scheme+host equals the configured RPC URL before following `Links.Next`.
## Affected Files
- `scanner/tron_chain.go:180`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-61

View File

@@ -0,0 +1,38 @@
---
issue: 108
title: "Scanner: unauthenticated startup when SCANNER_API_KEY unset"
severity: medium
domain: Scanner
labels: [security, scanner, configuration]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Scanner: unauthenticated startup when SCANNER_API_KEY unset
**Severity:** medium
**Domain:** Scanner
**Labels:** security, scanner, configuration
## Description
`scanner/config.go:111` logs a warning when `SCANNER_API_KEY` is empty but allows the server to start and accept unauthenticated requests. An operator mistake or CI misconfiguration can deploy a scanner that accepts any intent without an API key.
## Options
1. Fail fast in non-dev when `SCANNER_API_KEY` is empty.
2. Allow empty key only when bound to localhost; refuse otherwise.
3. Keep warning but add a required-in-prod env flag.
## Recommendation
Refuse to start (or restrict to loopback) when no API key is set outside local dev.
## Affected Files
- `scanner/config.go:111`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-60

View File

@@ -0,0 +1,39 @@
---
issue: 109
title: "Scanner: Tron lag metric reported in ms, not blocks — inconsistent with EVM chains"
severity: medium
domain: Scanner
labels: [scanner, observability]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Scanner: Tron lag metric reported in ms, not blocks — inconsistent with EVM chains
**Severity:** medium
**Domain:** Scanner
**Labels:** scanner, observability
## Description
`scanner/api.go:55` reports Tron lag in milliseconds while EVM chains report lag in blocks. Monitoring dashboards and alerts that compare lag across chains will produce incorrect comparisons.
## Options
1. Convert Tron lag to blocks (divide by ~3s block time) to match EVM semantics.
2. Keep ms but relabel the field/units and fix the comment and alerts.
3. Report a normalized seconds value across all chains.
## Recommendation
Pick a consistent unit (blocks for EVM/Tron, or seconds everywhere), update the struct comment and any alerts. Affects monitoring contracts.
## Affected Files
- `scanner/api.go:55`
- Status struct and any Prometheus/monitoring config
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-64

View File

@@ -0,0 +1,38 @@
---
issue: 110
title: "Scanner: TON worker O(N) HTTP fan-out per scan cycle — one TonCenter call per intent"
severity: medium
domain: Scanner
labels: [performance, scanner, scalability]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Scanner: TON worker O(N) HTTP fan-out per scan cycle — one TonCenter call per intent
**Severity:** medium
**Domain:** Scanner
**Labels:** performance, scanner, scalability
## Description
`scanner/ton_chain.go:131` issues one TonCenter API call per pending intent per scan cycle. With N pending intents, this creates N outbound HTTP calls per cycle. Under load or with many intents, this exhausts outbound connection capacity and hits TonCenter rate limits.
## Options
1. Batch intents by destination/jetton and query once per group.
2. Bounded-concurrency worker pool for per-intent calls.
3. Subscribe to TonCenter streaming/index instead of polling.
## Recommendation
Batch queries by jetton/destination where the API allows; otherwise bound concurrency. A TODO is already noted in the code.
## Affected Files
- `scanner/ton_chain.go:131`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-66

View File

@@ -0,0 +1,39 @@
---
issue: 111
title: "Scanner: deliverWebhook goroutines use blocking time.Sleep — goroutine leak under sustained failure"
severity: medium
domain: Scanner
labels: [bug, scanner, goroutine-leak]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Scanner: deliverWebhook goroutines use blocking time.Sleep — goroutine leak under sustained failure
**Severity:** medium
**Domain:** Scanner
**Labels:** bug, scanner, goroutine-leak
## Description
`scanner/webhook.go:90` spawns a goroutine per webhook delivery that uses `time.Sleep` for retry backoff. Under sustained backend failure, many goroutines accumulate blocking on sleep with no upper bound on their count or total memory usage.
## Options
1. Replace per-delivery sleeping goroutines with a persisted retry queue + ticker (already partially present).
2. Use a bounded worker pool + context cancellation instead of `time.Sleep`.
3. Cap concurrent in-flight deliveries with a semaphore.
## Recommendation
Move retries to the persisted queue/ticker model with a bounded worker pool and context-aware delays. Coordinate with ISSUE-112.
## Affected Files
- `scanner/webhook.go:90`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-67
- [[ISSUE-112-scanner-unbounded-goroutine-fan-out-for-webhook-retries|ISSUE-112]]

View File

@@ -0,0 +1,40 @@
---
issue: 112
title: "Scanner: unbounded goroutine fan-out for webhook retries and reconciliation"
severity: medium
domain: Scanner
labels: [bug, scanner, goroutine-leak]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Scanner: unbounded goroutine fan-out for webhook retries and reconciliation
**Severity:** medium
**Domain:** Scanner
**Labels:** bug, scanner, goroutine-leak
## Description
`scanner/main.go:130` spawns goroutines for retry and reconciliation fan-out without any concurrency bound. Under high load or many failed deliveries, the number of live goroutines is unbounded, risking OOM.
## Options
1. Bound with a semaphore/worker pool (e.g. `errgroup` with limit).
2. Process retries in batches sequentially.
3. Rate-limit outbound webhook calls globally.
## Recommendation
Introduce a bounded worker pool (`errgroup.SetLimit` or semaphore) for all retry fan-out paths. Coordinate with ISSUE-111.
## Affected Files
- `scanner/main.go:130`
- `scanner/webhook.go` — retry fan-out
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-68
- [[ISSUE-111-scanner-deliverwebhook-goroutines-use-blocking-time-sleep|ISSUE-111]]

View File

@@ -0,0 +1,40 @@
---
issue: 113
title: "Scanner/backend: RPC response bodies read without size limit — OOM risk"
severity: medium
domain: Scanner
labels: [security, scanner, oom]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Scanner/backend: RPC response bodies read without size limit — OOM risk
**Severity:** medium
**Domain:** Scanner
**Labels:** security, scanner, oom
## Description
NB-42 applied a `LimitReader` as a mechanical guard with a default cap, but the exact byte limit per endpoint was not decided. Choosing the wrong cap (too small) breaks legitimate large responses; too large offers little protection. A malicious RPC node can still exhaust memory if the cap is too generous.
## Options
1. Wrap `resp.Body` in `io.LimitReader(resp.Body, maxBytes)` with a generous per-endpoint cap (applied as NB-42).
2. Use `http.MaxBytesReader`-style enforcement and error on exceed.
3. Stream-parse JSON with a bounded decoder.
## Recommendation
Review the default cap applied by NB-42 against actual maximum RPC response sizes for each chain (EVM batch, Tron page, TON jetton response). Adjust per-endpoint caps and error explicitly when the limit is exceeded rather than silently truncating.
## Affected Files
- `scanner/chain.go:96`
- `scanner/tron_chain.go:116`
- `scanner/ton_chain.go:106`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-72

View File

@@ -0,0 +1,38 @@
---
issue: 114
title: "Frontend: WalletConnect/Google client IDs hardcoded as Dockerfile ARG defaults"
severity: low
domain: Security
labels: [frontend, configuration, ci-cd]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Frontend: WalletConnect/Google client IDs hardcoded as Dockerfile ARG defaults
**Severity:** low
**Domain:** Security
**Labels:** frontend, configuration, ci-cd
## Description
`frontend/Dockerfile:14` has hardcoded default values for `NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID` and `NEXT_PUBLIC_GOOGLE_CLIENT_ID` in `ARG` defaults. Forks or copies of this repo will silently use production IDs without being aware.
## Options
1. Remove defaults; require build-args/CI to supply them.
2. Keep defaults since values are public-by-design but document them.
3. Move to runtime env only.
## Recommendation
Remove the baked defaults and supply via CI build-args to avoid forks reusing prod IDs. These values are public but should be explicit.
## Affected Files
- `frontend/Dockerfile:14`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-74

View File

@@ -0,0 +1,38 @@
---
issue: 115
title: "Frontend: real plaintext credentials in committed scripts/show-credentials.sh"
severity: low
domain: Security
labels: [security, frontend, secrets, rotation-required]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Frontend: real plaintext credentials in committed scripts/show-credentials.sh
**Severity:** low
**Domain:** Security
**Labels:** security, frontend, secrets, rotation-required
## Description
`frontend/scripts/show-credentials.sh:8` contains hardcoded credentials including the password `Moji6364`. If this account exists in any real environment, the password must be rotated.
## Options
1. Delete the scripts and rotate the password if the account is real.
2. Replace hardcoded creds with env-var prompts.
3. Keep scripts but move creds out and rotate.
## Recommendation
Remove the hardcoded credentials (use env-var prompts instead) and rotate the account password if it exists in any real environment.
## Affected Files
- `frontend/scripts/show-credentials.sh:8`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-75

View File

@@ -0,0 +1,44 @@
---
issue: 116
title: "Frontend/scanner/backend: CI pipeline images not pinned to digests — tag-hijack risk"
severity: medium
domain: CI/CD
labels: [security, ci-cd, supply-chain]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Frontend/scanner/backend: CI pipeline images not pinned to digests — tag-hijack risk
**Severity:** medium
**Domain:** CI/CD
**Labels:** security, ci-cd, supply-chain
## Description
CI step images across all three repos use floating version tags or `latest` (node, buildx plugin, curl, alpine). A tag can be replaced with a malicious image that exfiltrates secrets or produces a compromised build artifact.
NB-40 and NB-41 pinned the scanner `alpine:latest` and buildx plugin. The broader policy of pinning all CI images across all repos remains a decision.
## Options
1. Pin all CI images (node, buildx plugin, curl, alpine) to immutable digests — track updates via Renovate.
2. Pin to specific version tags only.
3. Use a vetted internal mirror with digests.
## Recommendation
Pin every CI step image to a digest across all pipelines; track updates via Renovate. Affects all CI files in frontend, backend, and scanner.
## Affected Files
- `frontend/.woodpecker/development.yml:8`
- `frontend/.woodpecker/production.yml`
- `backend/.woodpecker/development.yml`
- `scanner/.woodpecker/development.yml`
- (and all other pipeline files)
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-76

View File

@@ -0,0 +1,41 @@
---
issue: 117
title: "Frontend/scanner/backend: production/manual CI pipelines lack lint/type/test/audit gates"
severity: medium
domain: CI/CD
labels: [ci-cd, quality, supply-chain]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Frontend/scanner/backend: production/manual CI pipelines lack lint/type/test/audit gates
**Severity:** medium
**Domain:** CI/CD
**Labels:** ci-cd, quality, supply-chain
## Description
Production and manual CI pipelines across all three repos push images without the same lint/type/test gates that development pipelines apply. A broken build can be pushed to production via a manual trigger. NB-37 added a typecheck to the backend manual pipeline; the broader question of enforcing gates on all production/manual pipelines remains.
## Options
1. Add tsc/lint/test (and `go vet`/`go test` for scanner) to production and manual pipelines.
2. Reuse the development pipeline's gate as a shared step.
3. Block manual pipeline pushes unless a gate flag is passed.
## Recommendation
Require the same lint/type/test gate on production and manual pipelines across all repos. This is a known project memory item ("verify before push").
## Affected Files
- `frontend/.woodpecker/production.yml`
- `backend/.woodpecker/manual.yml`
- `scanner/.woodpecker/manual.yml`
- `scanner/.woodpecker/production.yml`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-77

View File

@@ -0,0 +1,38 @@
---
issue: 118
title: "Frontend: notification title rendered via dangerouslySetInnerHTML in .backup drawer"
severity: low
domain: Security
labels: [security, frontend, xss, dead-code]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Frontend: notification title rendered via dangerouslySetInnerHTML in .backup drawer
**Severity:** low
**Domain:** Security
**Labels:** security, frontend, xss, dead-code
## Description
`src/layouts/components/notifications-drawer.backup/notification-item.tsx:32` renders a notification title via `dangerouslySetInnerHTML`, creating an XSS sink. The `.backup` directory is likely dead code but may be imported somewhere or re-enabled in the future.
## Options
1. Delete the entire `.backup` directory if unused — removes dead code and the XSS sink.
2. Replace `dangerouslySetInnerHTML` with plain text rendering.
3. Keep HTML but sanitize via DOMPurify.
## Recommendation
Confirm nothing imports the `.backup` directory and delete it. If any live notification rendering uses `dangerouslySetInnerHTML` elsewhere, switch to text or DOMPurify.
## Affected Files
- `frontend/src/layouts/components/notifications-drawer.backup/notification-item.tsx:32`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-5

View File

@@ -0,0 +1,38 @@
---
issue: 119
title: "Frontend: TelegramDebugPanel exposed in production via URL/localStorage flag"
severity: low
domain: Security
labels: [security, frontend, debug-panel]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Frontend: TelegramDebugPanel exposed in production via URL/localStorage flag
**Severity:** low
**Domain:** Security
**Labels:** security, frontend, debug-panel
## Description
`src/components/debug/telegram-debug-panel.tsx:50` is enabled by a URL param or localStorage flag. In production, any user who discovers this flag can activate the debug panel, which exposes internal state including email, wallet, userId, and Telegram session data.
## Options
1. Render the panel only when `NODE_ENV !== 'production'` (compile-time) — removes the enumeration surface.
2. Keep runtime flag but redact PII fields (email, wallet, userId).
3. Remove the component from account pages entirely.
## Recommendation
Guard rendering on `NODE_ENV !== 'production'` so the flag cannot reveal it in prod builds.
## Affected Files
- `frontend/src/components/debug/telegram-debug-panel.tsx:50`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-7

View File

@@ -0,0 +1,39 @@
---
issue: 120
title: "Frontend: 50ms setInterval console-suppression script in root layout"
severity: high
domain: Observability
labels: [bug, frontend, logging]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Frontend: 50ms setInterval console-suppression script in root layout
**Severity:** high
**Domain:** Observability
**Labels:** bug, frontend, logging
## Description
`src/app/layout.tsx:139` contains a `setInterval` that repeatedly overrides `console.error`/`console.warn` every 50ms. This creates a recurring CPU microtask throughout the page lifecycle. The goal appears to be silencing an Emotion/MUI SSR warning, but the approach overrides the console globally on every tick.
## Options
1. Remove the suppression script entirely and address the underlying Emotion/MUI SSR warning properly.
2. Keep one-time suppression (no interval) gated to development only.
3. Replace with a single non-polling console override applied once at module load.
## Recommendation
Remove the polling entirely. If the SSR warning must be silenced, apply a single non-polling override and only in development. Coordinate with ISSUE-084 (console suppression masks prod errors).
## Affected Files
- `frontend/src/app/layout.tsx:139`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-9
- [[ISSUE-084-frontend-console-error-warn-suppression-masks-prod-errors|ISSUE-084]]

View File

@@ -0,0 +1,38 @@
---
issue: 121
title: "Frontend: transferFunds and createPayment POST to the same endpoint"
severity: low
domain: Payment
labels: [bug, frontend]
status: open
created: 2026-05-30
source: Full Codebase Audit 2026-05-30
---
# Frontend: transferFunds and createPayment POST to the same endpoint
**Severity:** low
**Domain:** Payment
**Labels:** bug, frontend
## Description
`src/actions/payment.ts:186``transferFunds` and `createPayment` both POST to the same backend endpoint. It is unclear whether this is intentional (payload-shape disambiguation on the backend) or an error where `transferFunds` should use a dedicated route.
## Options
1. Give `transferFunds` a dedicated backend route + frontend endpoint constant.
2. Keep shared endpoint but document the backend disambiguation contract.
3. Merge the two functions if they are truly the same operation.
## Recommendation
Confirm backend routing intent; if distinct operations, introduce a dedicated endpoint.
## Affected Files
- `frontend/src/actions/payment.ts:186`
## References
- [Full Codebase Audit 2026-05-30](../09%20-%20Audits/Full%20Codebase%20Audit%20-%202026-05-30.md) — DEC-14

Some files were not shown because too many files have changed in this diff Show More