Final E2E Verification Report — Subagent I (Independent Security & QA Auditor)
Historical snapshot
This E2E report records a specific live staging verification and its observed defects at that time.
Treat its NOT_READY verdict as a dated finding, not as proof that the current code was retested or that production is safe.
Date: 2026-10-30 · Target: https://api.staging.devpay.chu.plus (Cloudflare Worker nougat-devpay-api-staging, DEV_AUTH=1, PAYMENT_MODE=staging) Method: Independent re-verification — all tests executed as real curl requests against live staging. No prior claims trusted. Two seller accounts (seller_a495f8f8ee6f, seller_f64fbd399929) + one buyer created via real /auth/signup → /seller/onboarding flow.
Test Matrix
| # | Test | Expected | Result | Evidence |
|---|---|---|---|---|
| 1 | signup→login→/me + real HS256 JWT | 200, eyJhbGciOiJIUzI1NiI, iss/aud/exp | PASS | header {"alg":"HS256","typ":"JWT"}; payload iss=nougat-devpay aud=nougat-api sub=usr_6a7adf2c6df1 tv=0 iat/exp set; sig=32B; /me→200 |
| 2 | Forged token (tampered sig) | 401 | PASS | /me + last sig char flipped → 401 {"code":"UNAUTHORIZED"}; forged admin-claim JWT → 403 FORBIDDEN |
| 3 | logout → token reuse | 401 (token_version) | PASS | /me 200 → POST /auth/logout 200 → same token /me→401, /orders→401 |
| 4 | deactivate → token reuse | 401/403 | PASS | POST /me/deactivate→200 → same token→401; re-login→403 ACCOUNT_INACTIVE |
| 5 | login rate limit (6 bad pw) | 429 | FAIL | 12 consecutive bad attempts → all 401 invalid credentials; correct password still logs in → no lockout |
| 6 | buyer token → /admin/users | 403 | PASS | 403 {"code":"FORBIDDEN","message":"admin only"}; seller token also 403 |
| 7 | sellerA GET /orders — sellerB leak? | scoped/empty | PASS | sellerA sees only ord_ad12be13a96c (seller_a495f8f8ee6f); sellerB sees only own |
| 8 | sellerA POST /orders/{B}/refund | 403 | PASS | 403 {"code":"FORBIDDEN","message":"다른 판매자의 주문은 환불할 수 없습니다"} |
| 9 | sellerA PATCH /products/{B} | 403/404 | PASS | 404 {"code":"NOT_FOUND"}; product unmodified |
| 10 | buyer POST /products | 403 (no fallback) | PASS | 403 {"code":"FORBIDDEN","message":"판매자 계정이 필요합니다"} — no seller_nougat fallback |
| 11 | POST /checkout simulate=success | works on staging | PASS | 200 {"ok":true,"order":{"id":"ord_d479746ed2e1","status":"paid"}} |
| 12 | paid feedback payment reuse | 409 PAYMENT_REUSED | PASS | 1st POST /feedbacks w/ pay_892a810edfee→200 fb_969ae6856f1d; 2nd→409 {"code":"PAYMENT_REUSED"} |
| 13 | GET /api/v1/health | 200 | PASS | 200 {"ok":true} (note: /health at root = 404; must use /api/v1/health) |
Supplementary checks (all PASS)
- sellerA
GET /orders/{sellerB_order_id}→404 NOT_FOUND(no leak); sellerB own order → 200 - buyer
GET /orders/{id}→ 404; unauth/me→ 401; unauth/orders→ 401 - duplicate
/seller/onboarding→409 ALREADY_SELLER - buyer →
/dashboard/stats403,/settlements403,POST /settlements403;/audit,/metrics→ 404 (not routed on Worker) - sellerA self-refund of own order → 200 (sanity: scoping direction correct)
- Code-verified: atomic settlement claim
UPDATE … AND settlement_id IS NULL(settle_impl.rs:61);safe_json_keywhitelist on alljson_setunit paths (refund/events/purchase);sub_eventseller-ownership check (events_impl.rs:34-44); PBKDF2-50k hashing;ct_eqconstant-time compares;verify_token_dbDB-side role/seller/status/token_version re-check.
NEW / STILL-BROKEN findings (verified live)
| Sev | Finding | Evidence |
|---|---|---|
| P0 | Buyer JWT can refund ANY order. refund() calls seller_scope_db → buyer has empty seller_id → None → map_or(true, …) treats "no scope" as admin → refund proceeds. | Live: buyer token POST /orders/ord_55565f0b1710/refund → 200 {"ok":true,"status":"refunded"}. Same hole exists in prod path (JWT-auth buyers also have no seller scope). Fix: only shared-secret/admin may pass with scope=None; role must be seller/admin for refunds. |
| P0 | DEV_AUTH=1 backdoor live on staging. X-Dev-User: <anything> (no token) → unauthenticated access; seller_scope_db maps it to seller_nougat. | Live: GET /orders -H "X-Dev-User: attacker" → 200, all 6 orders incl. PII; POST /products → created prod_06d991397b50 as seller_nougat. (Left in place — DELETE requires seller role which dev-user lacks.) Acceptable ONLY if staging is quarantined from real data; must be DEV_AUTH=0/absent in prod. |
| P1 | Login rate limiter dead on staging. 12 bad-password attempts → 401 each, never locks, correct password still succeeds. | Likely login_attempts table (migration 011) missing on staging D1 — record_login_fail swallows DB errors (let _ =) and returns fails=0 so fails>=5 never trips. Fix: apply migration + make fail counter fail-closed. |
| P1 | Cross-tenant read leak: any authenticated buyer reads ALL orders. GET /orders scopes only when role=="seller"; for buyer _sid=None → unscoped SELECT … FROM orders. | Live: buyer token GET /orders → 200 with all 6 orders across seller_a495f8f8ee6f, seller_f64fbd399929, seller_nougat (buyer emails, amounts, payment ids). Same for GET /feedbacks (all messages) and GET /licenses (all license metadata; key column not exposed). Per-order GET /orders/{id} IS correctly scoped (404) — list/record inconsistency. |
| P1 | POST /fulfillments/{id}/retry has no ownership check (code review, events_impl.rs:195-198) — only auth(); any valid token can force a failed fulfillment to grant currency. Not exploited live (no failed fulfillment to target). | Same class as refund hole. |
Verdict: NOT_READY for public beta
12/13 prescribed tests pass — JWT issuance/verification, revocation, deactivate, admin gating, seller-scoped order listing, cross-seller write blocks, checkout simulate, and payment-reuse are all genuinely fixed and verified end-to-end. However three live-verified holes block launch:
- Buyer→refund-any-order (P0) — a trivially exploitable destructive write present in the production code path, not just staging config.
- Unscoped list endpoints (P1) — any registered buyer can dump all sellers' orders/PII.
- Dead login rate limiter (P1) — unlimited password guessing.
- DEV_AUTH=1 staging backdoor (P0 for env hygiene) — fine for an isolated dev staging, fatal if staging ever holds real data or if the flag leaks to prod.
Recommended gate: fix refund scope (map_or(true) → require seller/admin role), scope all list endpoints by role (buyer→own buyer_ref only), repair login_attempts (migration + fail-closed), and assert DEV_AUTH=0 in prod deploy checks. Re-audit after those four land → likely WITH_LIMITATIONS (mock payments, mock IdP).