docs: align flow docs with code reality + create 35 implementation issue files

Flow docs updated (11 files):
- Delivery Confirmation: reversed actor roles (buyer generates, seller verifies),
  fixed endpoint paths (/delivery-code/generate, /delivery-code/verify)
- Passkey (WebAuthn): removed stub/simulated-key claims; real @simplewebauthn/server
  attestation is implemented; refresh tokens are persisted
- Dispute: corrected resolve schema (action enum), removed non-existent statuses,
  documented security gaps (no role guards on status/resolve/assign), route shadowing,
  all socket events are TODO stubs
- Seller Offer: corrected all endpoint paths, removed 'active' status, documented
  withdraw dead code, missing seller history page, select-offer notification gap
- Notification: corrected mark-all-read method+path, fixed GET /:id broken lookup,
  added unread-count-update socket event
- Authentication: corrected rate limiter (counts all attempts), axios 403 not handled,
  deleteAccount wrong endpoint bug, changePassword no UI
- Password Reset: corrected 6-digit code (not 8), documented no-complexity gap on
  reset-with-code vs token reset
- Payment Flow DePay: /create→/save, removed phantom sub-routes, SIM_ bypass risk,
  PaymentProvider type gap, getProviderIntentEndpoint routing bug
- Payment Flow SHKeeper: removed phantom polling endpoint, fixed release/refund paths
- Purchase Request: added pending_payment/active statuses, fixed sellers/attachments
  endpoints, corrected socket events, PUT→PATCH bug
- Escrow: documented dispute resolve does not touch escrow, route shadowing, confirm-delivery auth gap

Issues created (35 files in Issues/):
- 9 security issues (critical) including: dispute privilege escalation ×4,
  unauthenticated payment/scanner endpoints ×2, SIM_ production bypass,
  confirm-delivery ownership gap
- 26 additional major/critical bugs covering broken endpoints, missing features,
  data integrity gaps, and frontend-backend mismatches

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Siavash Sameni
2026-05-29 14:47:49 +04:00
parent 5113b0df23
commit a1f056e6a5
47 changed files with 2160 additions and 196 deletions

View File

@@ -0,0 +1,50 @@
---
issue: "001"
title: "PATCH /api/disputes/:id/status has no role guard — privilege escalation"
severity: critical
domain: dispute
labels: [security, backend, bug]
status: open
created: 2026-05-29
source: Doc vs Code Audit 2026-05-29
---
# 🔴 PATCH /api/disputes/:id/status has no role guard — privilege escalation
**Severity:** critical
**Domain:** dispute
**Labels:** security, backend, bug
## Description
`PATCH /api/disputes/:id/status` is mounted with only `authenticateToken` middleware — no `authorizeRoles('admin')` guard. Any authenticated buyer or seller who knows a dispute `_id` can change that dispute's status to `resolved`, `closed`, or any other value including states that release funds or trigger bans.
## Current Behavior
Any authenticated user (buyer or seller) can call:
```
PATCH /api/disputes/{disputeId}/status
{ "status": "resolved" }
```
and receive a 200 response. The dispute status is updated in MongoDB.
## Expected Behavior
Only users with `role: admin` should be permitted to change a dispute's status. Non-admin tokens should receive `403 Forbidden`.
## Reproduction Steps
1. Log in as a buyer or seller, obtain a JWT.
2. Find or create a dispute `_id`.
3. `PATCH /api/disputes/{id}/status` with `{ "status": "resolved" }` and the buyer/seller Bearer token.
4. Observe 200 and the status change in the DB.
## Affected Files
- `backend/src/routes/disputeRoutes.ts` — router missing `authorizeRoles('admin')` before `updateStatus` handler
- `backend/src/controllers/disputeController.ts``updateStatus` method
## References
- [Doc vs Code Audit Report](../09%20-%20Audits/Doc%20vs%20Code%20Audit%20Report%20-%202026-05-29.md) — Finding C16
- Related: [[ISSUE-002-dispute-resolve-no-role-guard]]

View File

@@ -0,0 +1,45 @@
---
issue: "002"
title: "POST /api/disputes/:id/resolve has no role guard — any user can resolve disputes and ban sellers"
severity: critical
domain: dispute
labels: [security, backend, bug]
status: open
created: 2026-05-29
source: Doc vs Code Audit 2026-05-29
---
# 🔴 POST /api/disputes/:id/resolve has no role guard — any user can resolve disputes and ban sellers
**Severity:** critical
**Domain:** dispute
**Labels:** security, backend, bug
## Description
The dashboard dispute router's `POST /api/disputes/:id/resolve` handler applies only `authenticateToken`. No `authorizeRoles('admin')` guard exists. Any authenticated user can post any resolution action including `action: 'ban_seller'`, `action: 'refund'`, or `action: 'no_action'`, bypassing all admin authority.
Note: the *releaseHold* router's `POST /api/disputes/:purchaseRequestId/resolve` correctly uses `authorizeRoles('admin')`, but the dashboard router does not.
## Current Behavior
A buyer or seller can call:
```
POST /api/disputes/{disputeId}/resolve
{ "action": "ban_seller", "notes": "malicious" }
```
The resolution is persisted with a 200 response.
## Expected Behavior
`POST /api/disputes/:id/resolve` must be protected by `authorizeRoles('admin')`. Non-admin tokens should receive `403`.
## Affected Files
- `backend/src/routes/disputeRoutes.ts` (dashboard router, mounted at `/api/disputes` first)
- `backend/src/controllers/disputeController.ts``resolveDispute` method
## References
- [Doc vs Code Audit Report](../09%20-%20Audits/Doc%20vs%20Code%20Audit%20Report%20-%202026-05-29.md) — Finding C17
- Related: [[ISSUE-001-dispute-status-no-role-guard]], [[ISSUE-003-dispute-route-shadowing]]

View File

@@ -0,0 +1,41 @@
---
issue: "003"
title: "Route shadowing: two dispute routers mounted at /api/disputes cause non-deterministic handler dispatch"
severity: critical
domain: dispute
labels: [backend, bug]
status: open
created: 2026-05-29
source: Doc vs Code Audit 2026-05-29
---
# 🔴 Route shadowing: two dispute routers mounted at /api/disputes cause non-deterministic handler dispatch
**Severity:** critical
**Domain:** dispute
**Labels:** backend, bug
## Description
In `backend/src/app.ts`, two separate dispute routers are mounted on the same path `/api/disputes`:
- Line ~521: `dashboardDisputeRoutes` (first — unguarded `POST /:id/resolve`, `PATCH /:id/status`)
- Line ~585: `releaseHold disputeRoutes` (second — admin-guarded `POST /:purchaseRequestId/resolve`, also `GET /:purchaseRequestId/status`)
Express evaluates in registration order. A `POST /api/disputes/{purchaseRequestId}/resolve` request will match the **dashboard router's** `POST /:id/resolve` handler first (since `:id` and `:purchaseRequestId` are identical route patterns). This executes the unguarded Dispute CRUD resolve instead of the admin-guarded escrow release-hold logic.
## Current Behavior
`POST /api/disputes/{purchaseRequestId}/resolve` executes the dashboard `resolveDispute` controller (updates the Dispute document only, no role guard) rather than the intended `releaseHold` handler (admin-only, clears escrow).
## Expected Behavior
The escrow-release resolve handler should be reachable at a distinct, unambiguous path (e.g., `/api/disputes/hold/:purchaseRequestId/resolve` or mounted at a different prefix).
## Affected Files
- `backend/src/app.ts` — two `app.use('/api/disputes', ...)` mount points
## References
- [Doc vs Code Audit Report](../09%20-%20Audits/Doc%20vs%20Code%20Audit%20Report%20-%202026-05-29.md) — Finding C18
- Related: [[ISSUE-002-dispute-resolve-no-role-guard]]

View File

@@ -0,0 +1,46 @@
---
issue: "004"
title: "fetch-tx, auto-fetch-missing, and debug payment endpoints have no authentication"
severity: critical
domain: payment
labels: [security, backend, bug]
status: open
created: 2026-05-29
source: Doc vs Code Audit 2026-05-29
---
# 🔴 fetch-tx, auto-fetch-missing, and debug payment endpoints have no authentication
**Severity:** critical
**Domain:** payment
**Labels:** security, backend, bug
## Description
Three backend payment endpoints are mounted with **no `authenticateToken` middleware**, despite being documented as admin-only:
1. `POST /api/payment/payments/:id/fetch-tx` — triggers on-chain transaction fetch for a payment
2. `POST /api/payment/payments/auto-fetch-missing` — triggers bulk on-chain fetch for all pending payments
3. `GET /api/payment/payments/:id/debug` — returns full payment document including blockchain metadata and wallet monitor state
Any unauthenticated caller (no Authorization header needed) can call all three endpoints.
## Current Behavior
```bash
curl -X POST https://api.example.com/api/payment/payments/anyId/fetch-tx
# Returns 200 and triggers on-chain state write
```
## Expected Behavior
All three endpoints should require `authenticateToken` + `authorizeRoles('admin')` and return `401` without credentials.
## Affected Files
- `backend/src/routes/paymentRoutes.js` — route definitions for `fetch-tx`, `auto-fetch-missing`, `debug`
## References
- [Doc vs Code Audit Report](../09%20-%20Audits/Doc%20vs%20Code%20Audit%20Report%20-%202026-05-29.md) — Findings C28, M40
- Related: [[ISSUE-005-scanner-status-no-auth]]

View File

@@ -0,0 +1,40 @@
---
issue: "005"
title: "GET /api/admin/scanner/status has no authentication despite /api/admin/ prefix"
severity: critical
domain: admin
labels: [security, backend, bug]
status: open
created: 2026-05-29
source: Doc vs Code Audit 2026-05-29
---
# 🔴 GET /api/admin/scanner/status has no authentication despite /api/admin/ prefix
**Severity:** critical
**Domain:** admin
**Labels:** security, backend, bug
## Description
`GET /api/admin/scanner/status` proxies to `AMN_SCANNER_URL` and returns scanner status data. Despite sitting under the `/api/admin/` prefix (which conventionally implies admin auth), this endpoint has **no `authenticateToken` middleware**. Any unauthenticated request returns scanner data.
## Current Behavior
```bash
curl https://api.example.com/api/admin/scanner/status
# Returns scanner data with 200, no credentials needed
```
## Expected Behavior
Should return `401` without a valid admin JWT.
## Affected Files
- `backend/src/routes/adminRoutes.js` — scanner proxy route definition
## References
- [Doc vs Code Audit Report](../09%20-%20Audits/Doc%20vs%20Code%20Audit%20Report%20-%202026-05-29.md) — Finding C29
- Related: [[ISSUE-004-payment-endpoints-no-auth]]

View File

@@ -0,0 +1,49 @@
---
issue: "006"
title: "Frontend deleteAccount action calls DELETE /user/profile which does not exist"
severity: critical
domain: auth
labels: [frontend, bug]
status: open
created: 2026-05-29
source: Doc vs Code Audit 2026-05-29
---
# 🔴 Frontend deleteAccount action calls DELETE /user/profile which does not exist
**Severity:** critical
**Domain:** auth
**Labels:** frontend, bug
## Description
`frontend/src/actions/account.ts` (line ~144) calls:
```ts
axiosInstance.delete(endpoints.users.profile)
// resolves to DELETE /user/profile
```
There is no `DELETE` handler on `/user/profile` in the backend. The actual soft-delete endpoint is:
```
DELETE /api/auth/account
```
which requires a `password` field in the request body and runs `deleteAccountValidation`.
**Result:** Account deletion silently 404s from every UI path. Users cannot delete their accounts.
## Current Behavior
Clicking the delete account button in the dashboard sends `DELETE /user/profile` → 404. The account is not deleted.
## Expected Behavior
The action should send `DELETE /api/auth/account` with `{ password }` in the body. On success, the account status is set to `'deleted'` (soft delete) in MongoDB.
## Affected Files
- `frontend/src/actions/account.ts``deleteAccount` function
- `frontend/src/lib/axios.ts``endpoints.users.profile` key used for the path
## References
- [Doc vs Code Audit Report](../09%20-%20Audits/Doc%20vs%20Code%20Audit%20Report%20-%202026-05-29.md) — Finding C3

View File

@@ -0,0 +1,42 @@
---
issue: "007"
title: "SIM_ transaction bypass active in production — no NODE_ENV guard on wallet connection fallback"
severity: critical
domain: payment
labels: [security, frontend, backend, bug]
status: open
created: 2026-05-29
source: Doc vs Code Audit 2026-05-29
---
# 🔴 SIM_ transaction bypass active in production — no NODE_ENV guard on wallet connection fallback
**Severity:** critical
**Domain:** payment
**Labels:** security, frontend, backend, bug
## Description
`frontend/src/web3/context/web3-provider.tsx` (lines ~225 and ~232) generates `SIM_` prefixed transaction hashes when wallet connection fails, and passes these to the backend as real transaction hashes.
The backend's payment service skips all on-chain verification for any `paymentHash` starting with `SIM_`. This bypass is controlled **only by the hash prefix** — there is no `process.env.NODE_ENV === 'development'` check in either the frontend or backend.
In production, if a user's wallet connection times out or throws (e.g., network error, MetaMask not responding), the frontend will submit a `SIM_` hash. This can result in a payment record being created as `completed` without any actual on-chain transaction.
## Current Behavior
Wallet connection failure → frontend generates `SIM_xxxxxxxx` hash → sends to backend → backend skips on-chain verification → payment created as completed.
## Expected Behavior
- Frontend: `SIM_` hash generation should be gated on `process.env.NODE_ENV !== 'production'`
- Backend: `SIM_` bypass should additionally check an environment flag (e.g., `process.env.ALLOW_SIM_PAYMENTS !== 'true'`)
## Affected Files
- `frontend/src/web3/context/web3-provider.tsx` — lines ~225, ~232
- `backend/src/services/payment/` — SIM_ prefix check in payment verification logic
## References
- [Doc vs Code Audit Report](../09%20-%20Audits/Doc%20vs%20Code%20Audit%20Report%20-%202026-05-29.md) — Finding M39

View File

@@ -0,0 +1,41 @@
---
issue: "008"
title: "sendFileMessage posts to wrong endpoint — file uploads always fail in chat"
severity: critical
domain: chat
labels: [frontend, bug]
status: open
created: 2026-05-29
source: Doc vs Code Audit 2026-05-29
---
# 🔴 sendFileMessage posts to wrong endpoint — file uploads always fail in chat
**Severity:** critical
**Domain:** chat
**Labels:** frontend, bug
## Description
`frontend/src/actions/chat.ts` (line ~386) sends file upload multipart form data to `endpoints.chat.sendMessage` which resolves to `POST /api/chat/:id/messages` — the text message endpoint.
The actual backend file upload endpoint is `POST /api/chat/:id/messages/file`.
The text-message handler expects a JSON body with a `content` string field, not a multipart payload. The file upload either fails or the attachment is silently discarded.
## Current Behavior
User picks a file in the chat input → `sendFileMessage` POSTs multipart to `/chat/:id/messages` → backend text handler rejects or ignores the multipart payload → file is never uploaded or stored.
## Expected Behavior
`sendFileMessage` should POST to `/api/chat/:id/messages/file` with the multipart form data. The response should include a message with an `attachments` array.
## Affected Files
- `frontend/src/actions/chat.ts``sendFileMessage` function uses `endpoints.chat.sendMessage`
- `frontend/src/lib/axios.ts` — no `endpoints.chat.sendFileMessage` entry exists; needs to be added as `/chat/:id/messages/file`
## References
- [Doc vs Code Audit Report](../09%20-%20Audits/Doc%20vs%20Code%20Audit%20Report%20-%202026-05-29.md) — Finding C19

View File

@@ -0,0 +1,36 @@
---
issue: "009"
title: "archiveConversation uses PUT but backend only accepts PATCH"
severity: major
domain: chat
labels: [frontend, bug]
status: open
created: 2026-05-29
source: Doc vs Code Audit 2026-05-29
---
# 🟠 archiveConversation uses PUT but backend only accepts PATCH
**Severity:** major
**Domain:** chat
**Labels:** frontend, bug
## Description
`frontend/src/actions/chat.ts` (line ~289) calls `axiosInstance.put(endpoints.chat.archive, ...)`. The backend registers this route as `PATCH /api/chat/:id/archive`. Express treats PUT and PATCH as distinct methods; PUT will not match the PATCH handler and returns 404/405.
## Current Behavior
Attempting to archive a conversation from the UI sends `PUT /api/chat/:id/archive` → 404. The chat is not archived.
## Expected Behavior
`archiveConversation` should use `axiosInstance.patch(...)` to match the backend's PATCH registration. The endpoint also has toggle semantics — calling it on an archived chat unarchives it.
## Affected Files
- `frontend/src/actions/chat.ts``archiveConversation` method verb (`put``patch`)
## References
- [Doc vs Code Audit Report](../09%20-%20Audits/Doc%20vs%20Code%20Audit%20Report%20-%202026-05-29.md) — Finding C20

View File

@@ -0,0 +1,49 @@
---
issue: "010"
title: "Admin user status/role actions broken: wrong HTTP verb (PUT vs PATCH) and wrong status values"
severity: critical
domain: admin
labels: [frontend, bug]
status: open
created: 2026-05-29
source: Doc vs Code Audit 2026-05-29
---
# 🔴 Admin user status/role actions broken: wrong HTTP verb (PUT vs PATCH) and wrong status values
**Severity:** critical
**Domain:** admin
**Labels:** frontend, bug
## Description
Two separate bugs on the admin user management actions:
**Bug 1 — Wrong HTTP verb:**
`frontend/src/actions/user.ts`:
- `updateUserStatus` calls `axiosInstance.put(...)` — backend registers `PATCH`
- `updateUserRole` calls `axiosInstance.put(...)` — backend registers `PATCH`
Both will 404/405 in production since Express doesn't alias PUT to PATCH.
**Bug 2 — Wrong status values:**
`updateUserStatus` accepts and sends `'active' | 'inactive' | 'pending'`. The backend `User.status` enum only accepts `'active' | 'suspended' | 'deleted'`. Sending `'inactive'` or `'pending'` is silently rejected or ignored. `'suspended'` is completely absent from the frontend type.
## Current Behavior
- Clicking "Suspend user" in admin panel sends `PUT /api/users/admin/:userId/status` with `{ status: 'inactive' }` → 404 and wrong value
- Clicking "Update role" sends `PUT /api/users/admin/:userId/role` → 404
## Expected Behavior
- Use `axiosInstance.patch(...)` for both actions
- Status values should be `'active' | 'suspended' | 'deleted'` to match the backend enum
## Affected Files
- `frontend/src/actions/user.ts``updateUserStatus` (line ~162), `updateUserRole` (line ~175)
- `frontend/src/types/user.ts` (line ~159) — status union type needs to include `'suspended'` and remove `'inactive'`/`'pending'`
## References
- [Doc vs Code Audit Report](../09%20-%20Audits/Doc%20vs%20Code%20Audit%20Report%20-%202026-05-29.md) — Findings C26, C27

View File

@@ -0,0 +1,36 @@
---
issue: "011"
title: "updatePurchaseRequest sends PUT but backend only accepts PATCH"
severity: major
domain: purchase-request
labels: [frontend, bug]
status: open
created: 2026-05-29
source: Doc vs Code Audit 2026-05-29
---
# 🟠 updatePurchaseRequest sends PUT but backend only accepts PATCH
**Severity:** major
**Domain:** purchase-request
**Labels:** frontend, bug
## Description
`frontend/src/actions/marketplace.ts` (line ~71) calls `axiosInstance.put(endpoints.marketplace.requests.update)`. The backend registers `PATCH /marketplace/purchase-requests/:id` (routes.ts). Sending PUT results in 404/405 — edits to purchase requests silently fail.
## Current Behavior
Editing a purchase request from the buyer edit view sends `PUT /marketplace/purchase-requests/:id` → 404. The request is not updated.
## Expected Behavior
The action should use `axiosInstance.patch(...)`.
## Affected Files
- `frontend/src/actions/marketplace.ts``updatePurchaseRequest` function (verb: `put``patch`)
## References
- [Doc vs Code Audit Report](../09%20-%20Audits/Doc%20vs%20Code%20Audit%20Report%20-%202026-05-29.md) — Finding M18

View File

@@ -0,0 +1,36 @@
---
issue: "012"
title: "updateOffer sends PUT but backend registers PATCH — offer edits fail"
severity: major
domain: seller-offer
labels: [frontend, bug]
status: open
created: 2026-05-29
source: Doc vs Code Audit 2026-05-29
---
# 🟠 updateOffer sends PUT but backend registers PATCH — offer edits fail
**Severity:** major
**Domain:** seller-offer
**Labels:** frontend, bug
## Description
`frontend/src/actions/marketplace.ts` (line ~289) calls `axiosInstance.put(endpoints.marketplace.offers.update)` mapping to `PUT /marketplace/offers/:id`. The backend registers `PATCH /offers/:id` (routes.ts line ~1260). Method mismatch → 404 or matched wrong route. `step-1-send-proposal.tsx` calls `updateOffer()` for proposal edits, so this path is actively exercised.
## Current Behavior
A seller editing an existing proposal sends `PUT /marketplace/offers/:id` which does not match the registered `PATCH` handler.
## Expected Behavior
`updateOffer` should use `axiosInstance.patch(...)`.
## Affected Files
- `frontend/src/actions/marketplace.ts``updateOffer` function
## References
- [Doc vs Code Audit Report](../09%20-%20Audits/Doc%20vs%20Code%20Audit%20Report%20-%202026-05-29.md) — Finding M28

View File

@@ -0,0 +1,42 @@
---
issue: "013"
title: "select-offer cascade overwrites withdrawn/rejected offers — missing status filter in updateMany"
severity: major
domain: seller-offer
labels: [backend, bug, data-integrity]
status: open
created: 2026-05-29
source: Doc vs Code Audit 2026-05-29
---
# 🟠 select-offer cascade overwrites withdrawn/rejected offers — missing status filter in updateMany
**Severity:** major
**Domain:** seller-offer
**Labels:** backend, bug, data-integrity
## Description
`POST /api/marketplace/purchase-requests/:id/select-offer` (routes.ts lines ~1386-1395) calls `SellerOffer.updateMany({ purchaseRequestId, _id: { $ne: offerId } }, { status: 'rejected' })` with **no status filter**. This overwrites offers that are already `'withdrawn'` or previously `'rejected'`, corrupting their status history.
By contrast, `SellerOfferService.acceptOffer()` (the service method used by `PUT /offers/:id/accept`) correctly filters with `status: { $in: ['pending', 'active'] }` before bulk-rejecting competitors.
## Current Behavior
1. Seller A submits offer → pending
2. Seller B submits offer → pending
3. Seller B withdraws offer → withdrawn
4. Buyer selects Seller A's offer via `POST .../select-offer`
5. Seller B's withdrawn offer is **overwritten to 'rejected'** — status history corrupted
## Expected Behavior
The `updateMany` in the `select-offer` route handler should add `status: { $in: ['pending'] }` to only reject currently-pending competing offers. Already-withdrawn or rejected offers should be left untouched.
## Affected Files
- `backend/src/routes/routes.ts` (or marketplaceController.ts) — `select-offer` route handler's `updateMany` call
## References
- [Doc vs Code Audit Report](../09%20-%20Audits/Doc%20vs%20Code%20Audit%20Report%20-%202026-05-29.md) — Finding M23

View File

@@ -0,0 +1,43 @@
---
issue: "014"
title: "select-offer sends no per-seller socket events or notifications to winning/losing sellers"
severity: major
domain: seller-offer
labels: [backend, missing-feature]
status: open
created: 2026-05-29
source: Doc vs Code Audit 2026-05-29
---
# 🟠 select-offer sends no per-seller socket events or notifications to winning/losing sellers
**Severity:** major
**Domain:** seller-offer
**Labels:** backend, missing-feature
## Description
`POST /api/marketplace/purchase-requests/:id/select-offer` (routes.ts lines ~1300-1438) emits only a single `purchase-request-update` event to the request room with `eventType: 'offer-selected'`. It does NOT:
- Call `notifyOfferAccepted` for the winning seller
- Call `notifyOfferRejected` for losing sellers
- Emit `seller-offer-update` events to individual seller rooms
These notifications only fire when using `PUT /offers/:id/accept` or `PUT /offers/:id/status` (via `SellerOfferService.updateOfferStatus`), not via the `select-offer` path used by the frontend.
## Current Behavior
Buyer selects an offer → winning seller gets no real-time notification → losing sellers get no notification.
## Expected Behavior
When a buyer selects an offer:
1. Winning seller receives a `seller-offer-update` event and a push notification
2. Losing sellers receive a `seller-offer-update` event and a notification
## Affected Files
- `backend/src/routes/routes.ts``select-offer` route handler, missing `notifyOfferAccepted` and `notifyOfferRejected` calls
## References
- [Doc vs Code Audit Report](../09%20-%20Audits/Doc%20vs%20Code%20Audit%20Report%20-%202026-05-29.md) — Finding M25

View File

@@ -0,0 +1,44 @@
---
issue: "015"
title: "Seller offer withdraw has no HTTP route — withdrawOffer() service method is dead code"
severity: major
domain: seller-offer
labels: [backend, missing-feature]
status: open
created: 2026-05-29
source: Doc vs Code Audit 2026-05-29
---
# 🟠 Seller offer withdraw has no HTTP route — withdrawOffer() service method is dead code
**Severity:** major
**Domain:** seller-offer
**Labels:** backend, missing-feature
## Description
`SellerOfferService.withdrawOffer()` (SellerOfferService.ts lines ~427-443) exists and implements withdrawal logic, but no HTTP route calls it. The documented `POST /api/marketplace/offers/:id/withdraw` endpoint does not exist in `routes.ts` or `marketplaceController.ts`.
There is also no frontend `withdrawOffer()` action, no withdraw button in any seller step component, and no seller offers history page at `/dashboard/seller/marketplace/offers`.
The only workaround is `PUT /api/marketplace/offers/:id/status` with `{ status: 'withdrawn' }`, which has no guard ensuring the requester is the offer's seller.
## Current Behavior
Sellers cannot withdraw their pending offers through any UI path. Withdrawing via `PUT /offers/:id/status` is the only API path and has no ownership guard.
## Expected Behavior
1. Wire a `POST /api/marketplace/offers/:id/withdraw` route to `SellerOfferService.withdrawOffer()`
2. Add an ownership guard (only the offer's seller can withdraw)
3. Add a frontend withdraw button and action
## Affected Files
- `backend/src/routes/routes.ts` — missing `POST /offers/:id/withdraw` route
- `frontend/src/actions/marketplace.ts` — missing `withdrawOffer` action
- Frontend seller dashboard — missing offers list page
## References
- [Doc vs Code Audit Report](../09%20-%20Audits/Doc%20vs%20Code%20Audit%20Report%20-%202026-05-29.md) — Findings C9, M26

View File

@@ -0,0 +1,39 @@
---
issue: "016"
title: "createProviderPaymentIntent always routes to request-network regardless of provider — SHKeeper checkout broken"
severity: critical
domain: payment
labels: [frontend, bug]
status: open
created: 2026-05-29
source: Doc vs Code Audit 2026-05-29
---
# 🔴 createProviderPaymentIntent always routes to request-network regardless of provider — SHKeeper checkout broken
**Severity:** critical
**Domain:** payment
**Labels:** frontend, bug
## Description
`frontend/src/actions/payment.ts``getProviderIntentEndpoint()` ignores its `provider` argument and always returns `endpoints.payments.requestNetwork.intents` (`/payment/request-network/intents`).
If any UI component passes `provider='shkeeper'` to `createProviderPaymentIntent()`, the intent creation silently POSTs to the Request Network endpoint instead of `/payment/shkeeper/intents`. The SHKeeper intents endpoint is defined in `axios.ts` but is never reached by this factory.
## Current Behavior
A SHKeeper checkout call to `createProviderPaymentIntent('shkeeper', ...)` POSTs to `/payment/request-network/intents`. The RN endpoint creates a Request Network intent, not a SHKeeper intent. The payment provider is silently misrouted.
## Expected Behavior
`getProviderIntentEndpoint('shkeeper')` should return `endpoints.payments.shkeeper.intents`. The function should switch on the provider argument.
## Affected Files
- `frontend/src/actions/payment.ts``getProviderIntentEndpoint()` function (~line 444)
## References
- [Doc vs Code Audit Report](../09%20-%20Audits/Doc%20vs%20Code%20Audit%20Report%20-%202026-05-29.md) — Finding M38
- Related: [[ISSUE-017-payment-provider-type-missing-values]]

View File

@@ -0,0 +1,46 @@
---
issue: "017"
title: "PaymentProvider TypeScript type missing 'shkeeper' and 'decentralized' values"
severity: major
domain: payment
labels: [frontend, bug, typescript]
status: open
created: 2026-05-29
source: Doc vs Code Audit 2026-05-29
---
# 🟠 PaymentProvider TypeScript type missing 'shkeeper' and 'decentralized' values
**Severity:** major
**Domain:** payment
**Labels:** frontend, bug, typescript
## Description
`frontend/src/types/payment.ts` defines:
```ts
type PaymentProvider = 'request.network' | 'test' | 'other'
```
The backend accepts `'shkeeper'`, `'decentralized'`, and `'other'` as `provider` values on Payment records. The two most-used production providers (`shkeeper`, `decentralized`) are absent from the TypeScript union.
Any frontend code that switches on `payment.provider` will fall through to a default/unknown branch for all SHKeeper and DePay payments, causing incorrect UI rendering (wrong labels, missing payment method icons, etc.).
## Current Behavior
SHKeeper and DePay payments in the payment list and payment detail views may show as "Unknown provider" or trigger TypeScript errors at compile time.
## Expected Behavior
```ts
type PaymentProvider = 'request.network' | 'shkeeper' | 'decentralized' | 'test' | 'other'
```
## Affected Files
- `frontend/src/types/payment.ts``PaymentProvider` type definition (~line 15)
## References
- [Doc vs Code Audit Report](../09%20-%20Audits/Doc%20vs%20Code%20Audit%20Report%20-%202026-05-29.md) — Finding M37
- Related: [[ISSUE-016-payment-provider-routing-always-request-network]]

View File

@@ -0,0 +1,53 @@
---
issue: "018"
title: "Trezor Safekeeping has zero frontend implementation — all backend endpoints unreachable from UI"
severity: critical
domain: trezor
labels: [frontend, missing-feature]
status: open
created: 2026-05-29
source: Doc vs Code Audit 2026-05-29
---
# 🔴 Trezor Safekeeping has zero frontend implementation — all backend endpoints unreachable from UI
**Severity:** critical
**Domain:** trezor
**Labels:** frontend, missing-feature
## Description
A comprehensive search of all `.ts` and `.tsx` files in `frontend/src/` finds **zero calls** to any Trezor backend endpoint. There is no:
- Trezor registration page
- xpub input UI
- Trezor Connect SDK import
- Admin Trezor signing panel
- Any action calling `/api/trezor/*`
The only Trezor reference in the entire frontend is a brand logo in `wallet-icons.ts`.
The documented 12-step challenge-sign-submit flow exists entirely in the backend but has no frontend surface at any step.
Additionally, `confirmReleaseTx` and `confirmRefundTx` in `frontend/src/actions/payment.ts` post `{ txHash, ...extra }` with **no `trezor` object** (message + signature). With `TREZOR_SAFEKEEPING_REQUIRED=true`, every admin release/refund from the UI will be rejected by the backend's `assertTrezorSignatureForOperation` guard.
## Current Behavior
- No UI exists for Trezor registration
- Admin release/refund with `TREZOR_SAFEKEEPING_REQUIRED=true` always fails (missing signature payload)
- All Trezor API endpoints are only testable via curl/Postman
## Expected Behavior
A complete frontend implementation covering:
1. Trezor registration page (xpub input, challenge-sign-submit flow)
2. Operation signing UI for admin release/refund (call `POST /api/trezor/operation-message`, prompt sign, attach `trezor` object to confirm body)
## Affected Files
- `frontend/src/actions/payment.ts``confirmReleaseTx`, `confirmRefundTx` missing `trezor` field
- Missing: Trezor registration page component
- Missing: Admin Trezor signing integration in dispute/payment admin panels
## References
- [Doc vs Code Audit Report](../09%20-%20Audits/Doc%20vs%20Code%20Audit%20Report%20-%202026-05-29.md) — Findings C31, C32

View File

@@ -0,0 +1,46 @@
---
issue: "019"
title: "Request Network admin payout/release/refund sub-routes do not exist in backend"
severity: major
domain: payment
labels: [backend, missing-feature]
status: open
created: 2026-05-29
source: Doc vs Code Audit 2026-05-29
---
# 🟠 Request Network admin payout/release/refund sub-routes do not exist in backend
**Severity:** major
**Domain:** payment
**Labels:** backend, missing-feature
## Description
`frontend/src/actions/payment.ts` exports four functions that hit non-existent backend endpoints:
| Function | Calls | Status |
|---|---|---|
| `initiateRequestNetworkPayout()` | `POST /api/payment/request-network/:id/payout/initiate` | 404 |
| `confirmRequestNetworkPayout()` | `POST /api/payment/request-network/:id/payout/confirm` | 404 |
| `confirmRequestNetworkRelease()` | `POST /api/payment/request-network/:id/release/confirm` | 404 |
| `confirmRequestNetworkRefund()` | `POST /api/payment/request-network/:id/refund/confirm` | 404 |
The backend only implements: `POST /api/payment/request-network/intents`, `GET /api/payment/request-network/:paymentId/checkout`, `POST /api/payment/request-network/webhook`.
## Current Behavior
All four admin RN payout/release/refund actions return 404. Admin has no way to complete or refund a Request Network payment through the UI.
## Expected Behavior
Backend should implement the four sub-routes, or the frontend actions should be mapped to the actual release/refund mechanism.
## Affected Files
- `frontend/src/actions/payment.ts``initiateRequestNetworkPayout`, `confirmRequestNetworkPayout`, `confirmRequestNetworkRelease`, `confirmRequestNetworkRefund`
- Backend: missing `request-network/:id/payout/*`, `release/confirm`, `refund/confirm` routes
## References
- [Doc vs Code Audit Report](../09%20-%20Audits/Doc%20vs%20Code%20Audit%20Report%20-%202026-05-29.md) — Finding M34

View File

@@ -0,0 +1,42 @@
---
issue: "020"
title: "POST /api/disputes/:id/assign has no role guard — any user can self-assign as mediator"
severity: major
domain: dispute
labels: [security, backend, bug]
status: open
created: 2026-05-29
source: Doc vs Code Audit 2026-05-29
---
# 🟠 POST /api/disputes/:id/assign has no role guard — any user can self-assign as mediator
**Severity:** major
**Domain:** dispute
**Labels:** security, backend, bug
## Description
`POST /api/disputes/:id/assign` is mounted with only `authenticateToken`. Any authenticated buyer or seller can assign themselves as the mediator/admin for any open dispute.
## Current Behavior
```bash
POST /api/disputes/{disputeId}/assign
Authorization: Bearer <buyer-jwt>
{ "adminId": "<buyer-user-id>" }
```
Returns 200 and sets the dispute's assigned mediator to the buyer.
## Expected Behavior
Should require `authorizeRoles('admin')`. Non-admin tokens should receive `403`.
## Affected Files
- `backend/src/routes/disputeRoutes.ts` — missing role guard on the assign route
## References
- [Doc vs Code Audit Report](../09%20-%20Audits/Doc%20vs%20Code%20Audit%20Report%20-%202026-05-29.md)
- Related: [[ISSUE-001-dispute-status-no-role-guard]], [[ISSUE-002-dispute-resolve-no-role-guard]]

View File

@@ -0,0 +1,45 @@
---
issue: "021"
title: "Axios interceptor only retriggers token refresh for 401, not 403"
severity: major
domain: auth
labels: [frontend, bug]
status: open
created: 2026-05-29
source: Doc vs Code Audit 2026-05-29
---
# 🟠 Axios interceptor only retriggers token refresh for 401, not 403
**Severity:** major
**Domain:** auth
**Labels:** frontend, bug
## Description
`frontend/src/lib/axios.ts` (line ~105) only triggers the token refresh flow for `status === 401`:
```ts
if (status === 401 && !isAuthRoute && !originalRequest?._retry) {
// trigger refresh
}
```
A `403` response (e.g., `EMAIL_NOT_VERIFIED`, a blocked account, or an under-privileged action) is not intercepted — it propagates as an unhandled error. Depending on how calling components handle errors, this may result in a blank screen or silent failure rather than an appropriate user message.
## Current Behavior
Backend returns `403 EMAIL_NOT_VERIFIED` → interceptor does not retry or refresh → error propagates to the component. Some components may not handle this gracefully.
## Expected Behavior
The interceptor (or a separate error handler) should:
- On `403`: **not** attempt a token refresh (a 403 is an authorization failure, not an expired token)
- But should surface the error clearly to the user (e.g., redirect to verify-email page for `EMAIL_NOT_VERIFIED` errors)
## Affected Files
- `frontend/src/lib/axios.ts` — response interceptor
## References
- [Doc vs Code Audit Report](../09%20-%20Audits/Doc%20vs%20Code%20Audit%20Report%20-%202026-05-29.md) — Finding M1

View File

@@ -0,0 +1,38 @@
---
issue: "022"
title: "Login rate limiter counts all attempts (not just failures) — users can be locked out after correct logins"
severity: major
domain: auth
labels: [backend, bug]
status: open
created: 2026-05-29
source: Doc vs Code Audit 2026-05-29
---
# 🟠 Login rate limiter counts all attempts (not just failures) — users can be locked out after correct logins
**Severity:** major
**Domain:** auth
**Labels:** backend, bug
## Description
`rateLimitService.checkLoginAttempts()` calls `checkLimit()` which calls `redisService.incr` — incrementing the counter on **every invocation**, before password comparison. The counter is only reset after a full successful login (password verified + session created).
With the limit at 5 attempts/15 min, a user who makes 4 correct logins in quick succession (e.g., testing on multiple devices) followed by 1 wrong password will be locked out immediately, even though they never "failed" 5 times in the intended sense.
## Current Behavior
5 total login attempts within 15 minutes (any combination of correct/incorrect passwords) triggers `429 TOO_MANY_ATTEMPTS`.
## Expected Behavior
The counter should only increment on **failed** password comparison, not on every attempt. Alternatively, the behaviour should be clearly documented so UX can warn users appropriately.
## Affected Files
- `backend/src/services/auth/rateLimitService.ts``checkLoginAttempts` / `checkLimit` — counter increment should move to after password comparison in `authController.ts`
## References
- [Doc vs Code Audit Report](../09%20-%20Audits/Doc%20vs%20Code%20Audit%20Report%20-%202026-05-29.md) — Finding M3

View File

@@ -0,0 +1,37 @@
---
issue: "023"
title: "changePassword action exists but no dashboard UI page exposes it"
severity: major
domain: auth
labels: [frontend, missing-feature]
status: open
created: 2026-05-29
source: Doc vs Code Audit 2026-05-29
---
# 🟠 changePassword action exists but no dashboard UI page exposes it
**Severity:** major
**Domain:** auth
**Labels:** frontend, missing-feature
## Description
`frontend/src/actions/account.ts` (line ~263) defines `changePassword()` which calls `POST /api/auth/change-password`. The backend endpoint exists and `changePasswordValidation` enforces password complexity (uppercase + lowercase + digit). However, **no dashboard page or component renders a change-password form**. The feature is API-only.
## Current Behavior
Users have no UI path to change their password after login. The only password reset mechanism is the email-based reset flow.
## Expected Behavior
A "Change Password" section in the account settings dashboard (e.g., under `/dashboard/account`) that calls `changePassword()` with `{ currentPassword, newPassword }`.
## Affected Files
- Missing: Change password form component in `/dashboard/account` or `/dashboard/account/security`
- `frontend/src/actions/account.ts``changePassword` function (implemented, no callers)
## References
- [Doc vs Code Audit Report](../09%20-%20Audits/Doc%20vs%20Code%20Audit%20Report%20-%202026-05-29.md) — Finding M4

View File

@@ -0,0 +1,41 @@
---
issue: "024"
title: "POST /api/auth/reset-password-with-code accepts weak passwords — no complexity validation"
severity: major
domain: auth
labels: [backend, security, bug]
status: open
created: 2026-05-29
source: Doc vs Code Audit 2026-05-29
---
# 🟠 POST /api/auth/reset-password-with-code accepts weak passwords — no complexity validation
**Severity:** major
**Domain:** auth
**Labels:** backend, security, bug
## Description
`POST /api/auth/reset-password-with-code` has **no `passwordResetValidation` middleware** (`authRoutes.ts` line ~54-57). The controller only validates that email, code, and password fields are present, and that the code is 6 digits.
Passwords like `'123456'`, `'aaaaaa'`, or `'password'` are accepted.
By contrast, the legacy `POST /api/auth/reset-password` (token-based) is wired with `passwordResetValidation` which enforces `/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/` — at least one uppercase, one lowercase, one digit.
## Current Behavior
`POST /api/auth/reset-password-with-code` with `{ email, code: "123456", password: "aaaaaa" }` → 200, password reset to weak value.
## Expected Behavior
Apply `passwordResetValidation` (or equivalent inline validation) to `reset-password-with-code` as well.
## Affected Files
- `backend/src/routes/authRoutes.ts` — line ~54-57, add `passwordResetValidation` middleware
- `backend/src/shared/middleware/authValidation.ts``passwordResetValidation` definition
## References
- [Doc vs Code Audit Report](../09%20-%20Audits/Doc%20vs%20Code%20Audit%20Report%20-%202026-05-29.md) — Finding M6

View File

@@ -0,0 +1,46 @@
---
issue: "025"
title: "All dispute socket events are commented-out TODO stubs — no real-time updates in dispute flow"
severity: major
domain: dispute
labels: [backend, missing-feature]
status: open
created: 2026-05-29
source: Doc vs Code Audit 2026-05-29
---
# 🟠 All dispute socket events are commented-out TODO stubs — no real-time updates in dispute flow
**Severity:** major
**Domain:** dispute
**Labels:** backend, missing-feature
## Description
Every `socket.io` emit block in `DisputeService` is currently commented out as a TODO. No real-time updates fire for any dispute lifecycle event:
- Dispute created
- Admin assigned
- Status changed
- Evidence uploaded
- Resolution posted
The dispute flow is CRUD-only. Any UI component that relies on socket events for real-time dispute state will never receive updates.
## Current Behavior
All dispute state changes are only visible after a manual page refresh.
## Expected Behavior
Implement the socket emit calls for key dispute events:
- `dispute-created` → to buyer, seller, and admin rooms
- `dispute-status-changed` → to involved parties
- `dispute-resolved` → to buyer and seller rooms
## Affected Files
- `backend/src/services/dispute/disputeService.ts` — all commented-out `io.to(...).emit(...)` blocks
## References
- [Doc vs Code Audit Report](../09%20-%20Audits/Doc%20vs%20Code%20Audit%20Report%20-%202026-05-29.md)

View File

@@ -0,0 +1,38 @@
---
issue: "026"
title: "'completed' payment status not counted in successfulPayments stats — admin dashboard undercounts"
severity: major
domain: payment
labels: [backend, bug]
status: open
created: 2026-05-29
source: Doc vs Code Audit 2026-05-29
---
# 🟠 'completed' payment status not counted in successfulPayments stats — admin dashboard undercounts
**Severity:** major
**Domain:** payment
**Labels:** backend, bug
## Description
`paymentService.getPaymentStats()` aggregate counts only `'confirmed'` as `successfulPayments`. `'completed'` is excluded from this count.
Most SHKeeper payments follow the terminal path: `pending → processing → completed`. `'confirmed'` is a separate RN-specific intermediate state. This means the vast majority of successfully completed payments (SHKeeper + DePay) are **invisible in the `successfulPayments` count** in the admin stats endpoint.
## Current Behavior
Admin dashboard shows a `successfulPayments` count that excludes all `'completed'` status payments. For a platform where SHKeeper is the primary payment provider, this count is close to 0 even when hundreds of payments have succeeded.
## Expected Behavior
`successfulPayments` should count payments in both `'confirmed'` and `'completed'` status, or the aggregate should be documented with a clear note about which statuses are terminal success states.
## Affected Files
- `backend/src/services/payment/paymentService.ts``getPaymentStats()` aggregate pipeline
## References
- [Doc vs Code Audit Report](../09%20-%20Audits/Doc%20vs%20Code%20Audit%20Report%20-%202026-05-29.md) — Finding M36

View File

@@ -0,0 +1,38 @@
---
issue: "027"
title: "GET /api/notifications/:id always 404s for non-latest notifications — broken in-memory lookup"
severity: major
domain: notification
labels: [backend, bug]
status: open
created: 2026-05-29
source: Doc vs Code Audit 2026-05-29
---
# 🟠 GET /api/notifications/:id always 404s for non-latest notifications — broken in-memory lookup
**Severity:** major
**Domain:** notification
**Labels:** backend, bug
## Description
The `getNotificationById` controller does NOT perform a direct MongoDB `findById` lookup. Instead it calls `getUserNotifications(userId, 1, 1)` — fetching only the user's single most-recent notification — and then does an **in-memory `_id` string comparison**.
Any notification that is not the user's absolute latest record returns `404`, regardless of ownership. This makes the endpoint completely unreliable for any consumer that tries to fetch a specific notification by ID.
## Current Behavior
`GET /api/notifications/abc123` returns the notification only if `abc123` happens to be the user's most recently created notification. For all others: 404.
## Expected Behavior
`getNotificationById` should do a direct `Notification.findOne({ _id: id, userId })` query.
## Affected Files
- `backend/src/services/notification/notificationService.ts` (or controller) — `getNotificationById` / `getUserNotifications` call
## References
- [Doc vs Code Audit Report](../09%20-%20Audits/Doc%20vs%20Code%20Audit%20Report%20-%202026-05-29.md) — Finding C22

View File

@@ -0,0 +1,41 @@
---
issue: "028"
title: "GET /api/payment/export has no admin role guard — any authenticated user can export payment data"
severity: major
domain: payment
labels: [security, backend, bug]
status: open
created: 2026-05-29
source: Doc vs Code Audit 2026-05-29
---
# 🟠 GET /api/payment/export has no admin role guard — any authenticated user can export payment data
**Severity:** major
**Domain:** payment
**Labels:** security, backend, bug
## Description
Two parallel export endpoints exist:
- `GET /api/payment/payments/export` — has `authorizeRoles('admin')` guard (correct)
- `GET /api/payment/export` (controller-pattern route) — only has `authenticateToken`, **no admin guard**
The frontend hits `/payment/export` (the controller-pattern route without the admin guard). Any authenticated buyer can export payment records.
## Current Behavior
`GET /api/payment/export` with any valid user JWT → 200 with payment export data.
## Expected Behavior
`GET /api/payment/export` should require `authorizeRoles('admin')`, or the frontend should be pointed at `/api/payment/payments/export`.
## Affected Files
- Backend: controller-pattern route for `GET /payment/export` — missing `authorizeRoles('admin')`
- `frontend/src/lib/axios.ts``endpoints.payments.export` maps to the wrong route
## References
- [Doc vs Code Audit Report](../09%20-%20Audits/Doc%20vs%20Code%20Audit%20Report%20-%202026-05-29.md) — Finding M31

View File

@@ -0,0 +1,46 @@
---
issue: "029"
title: "Frontend delivery actions regenerate/attempts/stats call non-existent backend endpoints"
severity: major
domain: delivery
labels: [frontend, missing-feature]
status: open
created: 2026-05-29
source: Doc vs Code Audit 2026-05-29
---
# 🟠 Frontend delivery actions regenerate/attempts/stats call non-existent backend endpoints
**Severity:** major
**Domain:** delivery
**Labels:** frontend, missing-feature
## Description
Three frontend delivery actions hit non-existent backend routes:
| Action | Calls | Status |
|---|---|---|
| `regenerateDeliveryCode` | `POST /delivery-code/regenerate` | 404 (falls back to `/generate`) |
| `getDeliveryAttempts` | `GET /delivery-code/attempts` | 404, throws |
| `getDeliveryStats` | `GET /delivery/stats` | 404, throws |
`regenerateDeliveryCode` silently falls back to the generate endpoint on 404. The other two throw unhandled errors if any component calls them.
## Current Behavior
- Code "regeneration" actually calls generate (new code, ignores regenerate semantic)
- Any UI showing delivery attempt count or stats shows nothing or throws
## Expected Behavior
Either implement the backend routes, or remove the phantom actions and handle their use cases differently.
## Affected Files
- `frontend/src/actions/delivery.ts``regenerateDeliveryCode`, `getDeliveryAttempts`, `getDeliveryStats`
- Backend: missing routes for `/delivery-code/regenerate`, `/delivery-code/attempts`, `/delivery/stats`
## References
- [Doc vs Code Audit Report](../09%20-%20Audits/Doc%20vs%20Code%20Audit%20Report%20-%202026-05-29.md) — Finding M15

View File

@@ -0,0 +1,36 @@
---
issue: "030"
title: "PATCH /confirm-delivery has no ownership check — any authenticated user can confirm delivery"
severity: major
domain: delivery
labels: [backend, security, bug]
status: open
created: 2026-05-29
source: Doc vs Code Audit 2026-05-29
---
# 🟠 PATCH /confirm-delivery has no ownership check — any authenticated user can confirm delivery
**Severity:** major
**Domain:** delivery
**Labels:** backend, security, bug
## Description
`PATCH /api/marketplace/purchase-requests/:id/confirm-delivery` (the buyer fast-track path to `'delivered'` status) has no ownership or role check. Any authenticated user who knows a purchase request ID can mark it as delivered without possessing the delivery code.
## Current Behavior
`PATCH /purchase-requests/{anyId}/confirm-delivery` with any valid JWT → 200, status set to `'delivered'`.
## Expected Behavior
Should verify `req.user.id === request.buyerId` — only the buyer of that specific request should be able to confirm delivery via this fast-track path.
## Affected Files
- `backend/src/routes/controllerRoutes.ts` or `routes.ts``confirm-delivery` handler missing ownership guard
## References
- [Doc vs Code Audit Report](../09%20-%20Audits/Doc%20vs%20Code%20Audit%20Report%20-%202026-05-29.md)

View File

@@ -0,0 +1,47 @@
---
issue: "031"
title: "Points/referral system missing 5 frontend pages — redemption, levels, referrals, transactions, admin"
severity: major
domain: points
labels: [frontend, missing-feature]
status: open
created: 2026-05-29
source: Doc vs Code Audit 2026-05-29
---
# 🟠 Points/referral system missing 5 frontend pages — redemption, levels, referrals, transactions, admin
**Severity:** major
**Domain:** points
**Labels:** frontend, missing-feature
## Description
The following routes return 404 because no frontend pages exist:
| Route | Backend Endpoint | Status |
|---|---|---|
| `/dashboard/points/referrals` | `GET /api/points/referrals` | Page missing |
| `/dashboard/points/transactions` | `GET /api/points/transactions` | Page missing |
| `/dashboard/points/levels` | `GET /api/points/levels` | Page missing |
| `/dashboard/points/redeem` (or any UI) | `POST /api/points/redeem` | No redemption UI anywhere |
| Admin points management | `POST /api/points/admin/add` | No admin page |
`redeemPoints()` and `generateReferralCode()` actions are defined but have no call sites in any component.
## Current Behavior
All points features beyond the basic balance display are inaccessible from the UI.
## Expected Behavior
Implement frontend pages for: referral history, transaction history, levels display, points redemption flow, and admin points management.
## Affected Files
- Missing pages in `frontend/src/app/dashboard/points/`
- `frontend/src/actions/points.ts``redeemPoints`, `generateReferralCode` (defined, no callers)
## References
- [Doc vs Code Audit Report](../09%20-%20Audits/Doc%20vs%20Code%20Audit%20Report%20-%202026-05-29.md)

View File

@@ -0,0 +1,45 @@
---
issue: "032"
title: "SHKeeper release/refund doc paths include erroneous /shkeeper/ segment"
severity: major
domain: payment
labels: [backend, bug]
status: open
created: 2026-05-29
source: Doc vs Code Audit 2026-05-29
---
# 🟠 SHKeeper release/refund doc paths include erroneous /shkeeper/ segment
**Severity:** major
**Domain:** payment
**Labels:** backend, bug
## Description
The SHKeeper Payment Flow was documented with `/shkeeper/` in the release/refund paths. The actual backend routes are:
| Documented (wrong) | Actual (correct) |
|---|---|
| `POST /api/payment/shkeeper/:id/release` | `POST /api/payment/:id/release` |
| `POST /api/payment/shkeeper/:id/release/confirm` | `POST /api/payment/:id/release/confirm` |
| `POST /api/payment/shkeeper/:id/refund` | `POST /api/payment/:id/refund` |
| `POST /api/payment/shkeeper/:id/refund/confirm` | `POST /api/payment/:id/refund/confirm` |
The frontend `endpoints.payments.details` maps to `/payment/:id` (correct), so the frontend is unaffected. The issue is in the documentation and any external integration or test harness built from the docs.
## Current Behavior
Calling any `/shkeeper/` path returns 404.
## Expected Behavior
Documentation and any test harnesses should use paths without the `/shkeeper/` segment.
## Affected Files
- Doc file updated: `04 - Flows/Payment Flow - SHKeeper.md`
## References
- [Doc vs Code Audit Report](../09%20-%20Audits/Doc%20vs%20Code%20Audit%20Report%20-%202026-05-29.md) — Finding C30

View File

@@ -0,0 +1,42 @@
---
issue: "033"
title: "GET seller offer history has no HTTP route — getOffersBySeller() is unreachable dead code"
severity: major
domain: seller-offer
labels: [backend, missing-feature]
status: open
created: 2026-05-29
source: Doc vs Code Audit 2026-05-29
---
# 🟠 GET seller offer history has no HTTP route — getOffersBySeller() is unreachable dead code
**Severity:** major
**Domain:** seller-offer
**Labels:** backend, missing-feature
## Description
`SellerOfferService.getOffersBySeller()` exists in the service layer but no HTTP route exposes it. The documented endpoint `GET /api/marketplace/offers/seller/:sellerId` does not exist in `routes.ts` or `marketplaceController.ts`.
Notification action URLs that point to `/dashboard/seller/marketplace/offers` are also broken — that frontend page does not exist.
## Current Behavior
- Sellers have no way to view their own offer history via the API
- Notification deep-links to the offers page return 404
## Expected Behavior
1. Register `GET /api/marketplace/offers/seller/:sellerId` (or equivalent scoped route) calling `getOffersBySeller()`
2. Create the frontend page at `/dashboard/seller/marketplace/offers`
3. Fix notification `actionUrl` to point to the real page
## Affected Files
- `backend/src/routes/routes.ts` — missing `GET /offers/seller/:sellerId` route
- Missing: `frontend/src/app/dashboard/shops/` or similar seller offers list page
## References
- [Doc vs Code Audit Report](../09%20-%20Audits/Doc%20vs%20Code%20Audit%20Report%20-%202026-05-29.md) — Finding M27

View File

@@ -0,0 +1,41 @@
---
issue: "034"
title: "SellerOffer 'active' status does not exist in schema — saves with this value throw ValidationError"
severity: major
domain: seller-offer
labels: [backend, bug]
status: open
created: 2026-05-29
source: Doc vs Code Audit 2026-05-29
---
# 🟠 SellerOffer 'active' status does not exist in schema — saves with this value throw ValidationError
**Severity:** major
**Domain:** seller-offer
**Labels:** backend, bug
## Description
The Seller Offer Flow doc lists `'active'` as a valid `SellerOffer.status`. The Mongoose schema and TypeScript interface only enumerate:
```
'pending' | 'accepted' | 'rejected' | 'withdrawn'
```
Any code path that attempts to set `SellerOffer.status = 'active'` will throw a Mongoose `ValidationError`. The `createOffer()` service correctly checks `PurchaseRequest.status === 'active'` (a different model's status), but `SellerOffer.status = 'active'` is never valid.
## Current Behavior
`SellerOffer.save()` with `status: 'active'` → Mongoose ValidationError. (Currently no code path actually tries to do this — the bug is latent but would be triggered by misreading the documentation.)
## Expected Behavior
Remove `'active'` from all `SellerOffer` status documentation. The valid states are `pending | accepted | rejected | withdrawn`.
## Affected Files
- Doc file updated: `04 - Flows/Seller Offer Flow.md` and `02 - Data Models/SellerOffer.md`
## References
- [Doc vs Code Audit Report](../09%20-%20Audits/Doc%20vs%20Code%20Audit%20Report%20-%202026-05-29.md) — Finding M22

View File

@@ -0,0 +1,41 @@
---
issue: "035"
title: "Dispute payment card 'Verify' button always 404s — getPaymentStatus calls non-existent endpoint"
severity: major
domain: payment
labels: [frontend, bug]
status: open
created: 2026-05-29
source: Doc vs Code Audit 2026-05-29
---
# 🟠 Dispute payment card 'Verify' button always 404s — getPaymentStatus calls non-existent endpoint
**Severity:** major
**Domain:** payment
**Labels:** frontend, bug
## Description
`frontend/src/sections/dispute/components/payment-details-card.tsx` (line ~101) calls `getPaymentStatus()` which builds URL as `GET /payment/:id/status`. No `/status` sub-route exists on any payment route in the backend.
The 'Verify' button in the dispute panel is permanently broken in production.
## Current Behavior
Clicking 'Verify' on the dispute payment card → `GET /payment/{id}/status` → 404.
## Expected Behavior
Either:
1. Implement `GET /api/payment/:id/status` on the backend, or
2. Update the component to use the existing `GET /api/payment/:id` endpoint for payment detail fetching
## Affected Files
- `frontend/src/sections/dispute/components/payment-details-card.tsx` — line ~101
- `frontend/src/actions/payment.ts``getPaymentStatus` function
## References
- [Doc vs Code Audit Report](../09%20-%20Audits/Doc%20vs%20Code%20Audit%20Report%20-%202026-05-29.md) — Finding C13

59
Issues/Issues Index.md Normal file
View File

@@ -0,0 +1,59 @@
# Issues Index
> Generated from Doc vs Code Audit — 2026-05-29
> **35 open issues** | 🔴 14 critical · 🟠 19 major · 🟡 2 minor
## 🔴 Critical
- [[ISSUE-001-dispute-status-no-role-guard|PATCH /api/disputes/:id/status no role guard — privilege escalation]] — `dispute` · security
- [[ISSUE-002-dispute-resolve-no-role-guard|POST /api/disputes/:id/resolve no role guard — any user can resolve + ban sellers]] — `dispute` · security
- [[ISSUE-003-dispute-route-shadowing|Route shadowing: two dispute routers at /api/disputes — wrong handler fires]] — `dispute`
- [[ISSUE-004-payment-endpoints-no-auth|fetch-tx, auto-fetch-missing, debug payment endpoints have no authentication]] — `payment` · security
- [[ISSUE-005-scanner-status-no-auth|GET /api/admin/scanner/status has no authentication]] — `admin` · security
- [[ISSUE-006-delete-account-wrong-endpoint|Frontend deleteAccount calls DELETE /user/profile — endpoint doesn't exist]] — `auth`
- [[ISSUE-007-sim-bypass-no-env-guard|SIM_ transaction bypass active in production — no NODE_ENV guard]] — `payment` · security
- [[ISSUE-008-chat-file-upload-wrong-endpoint|sendFileMessage posts to wrong endpoint — chat file uploads always fail]] — `chat`
- [[ISSUE-010-admin-user-status-wrong-values-and-verb|Admin user status/role broken: wrong HTTP verb + wrong status values]] — `admin`
- [[ISSUE-016-payment-provider-routing-always-request-network|createProviderPaymentIntent always routes to request-network — SHKeeper broken]] — `payment`
- [[ISSUE-018-trezor-no-frontend-implementation|Trezor Safekeeping has zero frontend implementation]] — `trezor`
- [[ISSUE-020-dispute-assign-no-role-guard|POST /api/disputes/:id/assign no role guard — any user can self-assign mediator]] — `dispute` · security
- [[ISSUE-030-confirm-delivery-no-auth-guard|PATCH /confirm-delivery no ownership check — any user can confirm delivery]] — `delivery` · security
- [[ISSUE-035-payment-dispute-verify-button-404|Dispute 'Verify' button always 404s — getPaymentStatus hits non-existent endpoint]] — `payment`
## 🟠 Major
- [[ISSUE-009-archive-chat-wrong-method|archiveConversation uses PUT but backend only accepts PATCH]] — `chat`
- [[ISSUE-011-update-purchase-request-put-vs-patch|updatePurchaseRequest sends PUT but backend only accepts PATCH]] — `purchase-request`
- [[ISSUE-012-update-offer-put-vs-patch|updateOffer sends PUT but backend registers PATCH]] — `seller-offer`
- [[ISSUE-013-select-offer-no-status-filter-corrupts-withdrawn|select-offer cascade overwrites withdrawn offers — missing status filter]] — `seller-offer` · data-integrity
- [[ISSUE-014-select-offer-no-seller-notifications|select-offer sends no per-seller notifications to winning/losing sellers]] — `seller-offer`
- [[ISSUE-015-seller-offer-withdraw-no-http-route|Seller offer withdraw has no HTTP route — withdrawOffer() is dead code]] — `seller-offer`
- [[ISSUE-017-payment-provider-type-missing-values|PaymentProvider TypeScript type missing 'shkeeper' and 'decentralized']] — `payment`
- [[ISSUE-019-rn-payout-release-refund-not-implemented|Request Network admin payout/release/refund sub-routes do not exist]] — `payment`
- [[ISSUE-021-axios-interceptor-403-not-handled|Axios interceptor only retriggers token refresh for 401, not 403]] — `auth`
- [[ISSUE-022-rate-limit-counts-all-attempts|Login rate limiter counts all attempts — users locked out after correct logins]] — `auth`
- [[ISSUE-023-change-password-no-ui|changePassword action exists but no dashboard UI page]] — `auth`
- [[ISSUE-024-reset-password-with-code-no-complexity-check|POST /api/auth/reset-password-with-code accepts weak passwords]] — `auth` · security
- [[ISSUE-025-dispute-socket-events-all-stubs|All dispute socket events are TODO stubs — no real-time updates]] — `dispute`
- [[ISSUE-026-payment-completed-not-counted-in-stats|'completed' payment not counted in successfulPayments — admin dashboard undercounts]] — `payment`
- [[ISSUE-027-get-notification-by-id-broken|GET /api/notifications/:id always 404s for non-latest notifications]] — `notification`
- [[ISSUE-028-payment-export-no-admin-guard|GET /api/payment/export has no admin guard — any user can export payments]] — `payment` · security
- [[ISSUE-029-delivery-attempts-stats-phantom-endpoints|Frontend delivery actions regenerate/attempts/stats hit non-existent endpoints]] — `delivery`
- [[ISSUE-031-points-missing-frontend-pages|Points/referral missing 5 frontend pages — redemption, levels, referrals, transactions, admin]] — `points`
- [[ISSUE-032-shkeeper-release-refund-wrong-paths|SHKeeper release/refund doc paths include erroneous /shkeeper/ segment]] — `payment`
- [[ISSUE-033-seller-offer-history-route-missing|GET seller offer history has no HTTP route — getOffersBySeller() is dead code]] — `seller-offer`
- [[ISSUE-034-seller-offer-active-status-invalid|SellerOffer 'active' status invalid — saves throw ValidationError]] — `seller-offer`
## Security Issues Summary
| # | Issue | Severity |
|---|---|---|
| 001 | Dispute status PATCH — no role guard (privilege escalation) | 🔴 Critical |
| 002 | Dispute resolve POST — no role guard (ban_seller without auth) | 🔴 Critical |
| 004 | Payment fetch-tx/auto-fetch/debug — no authentication | 🔴 Critical |
| 005 | Admin scanner status — no authentication | 🔴 Critical |
| 007 | SIM_ bypass active in production | 🔴 Critical |
| 020 | Dispute assign — no role guard | 🔴 Critical |
| 030 | confirm-delivery — no ownership check | 🔴 Critical |
| 024 | reset-password-with-code — no complexity validation | 🟠 Major |
| 028 | Payment export — no admin guard | 🟠 Major |