Final Security Audit — Subagent I
Historical snapshot
This is a historical independent security audit, including then-confirmed findings and some later rechecks, not an evergreen checklist.
Keep each finding's original evidence and date; consult newer security tests before stating it is still open or resolved.
P0
- Staging fully unauthenticated: DEV_AUTH=1 + X-Dev-User gives any visitor seller access (lib.rs:99, auth_impl.rs:198)
- Cross-seller IDOR: /projects,/products,/feedbacks,/licenses unscoped; refund/sub_event/ff_retry no ownership check; buyer JWT→seller_nougat fallback escalation
- JWT non-standard: audit_hash signature ≠ HS256; Axum can't verify; claim mismatch (seller_id vs sid)
P1
- No session revocation (24h token survives deactivate/logout)
- Prod auth collapse (JWTs dead on scoped routes; shared secret=omnipotent)
- Paid-feedback replay (payment_id never written → UNIQUE dead)
- SQL injection via format!() unit into json path (refund/events/purchase)
- Double-settlement race (no AND settlement_id IS NULL)
- No rate limiting (login/license-verify/feedbacks)
- Demo creds live on staging (demo1234 admin)
- Migration drift (Axum stops at 008; two 001 files)
P2
- Non-constant-time compares, no logout endpoint, no activation endpoint, no account enumeration guard
VERIFIED — Independent re-audit, 2026-10-30 (live curl vs api.staging.devpay.chu.plus)
| Finding | VERIFIED |
|---|---|
| Staging fully unauthenticated (DEV_AUTH=1, X-Dev-User) | CONFIRMED — still live. GET /orders -H "X-Dev-User: attacker" (no token) → 200, all orders incl. PII; POST /products → created prod_06d991397b50 as seller_nougat. Intended staging flag (wrangler.toml [env.staging.vars] DEV_AUTH="1") — must never reach prod; staging must hold no real data. |
| Cross-seller IDOR | PARTIALLY FIXED. FIXED+verified: seller order list scoped (A sees only own); cross-seller GET /orders/{id}→404, refund→403, PATCH /products/{id}→404; buyer POST /products→403 (no seller_nougat fallback); sub_event owner-checked (events_impl.rs:34-44). STILL BROKEN: (a) buyer JWT POST /orders/{any}/refund→200 — seller_scope_db=None→map_or(true) treats non-sellers as admin (live-verified: ord_55565f0b1710 refunded by buyer); (b) GET /orders,/feedbacks,/licenses,/products unscoped for non-seller roles — buyer token dumps all tenants' orders/PII (live-verified); (c) ff_retry still auth-only, no ownership check. |
| JWT non-standard | FIXED. Real HS256 issued (eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 header, 32-byte sig), claims iss=nougat-devpay aud=nougat-api sub role seller_id tv iat exp; tampered sig→401; forged admin-claim→403. |
| No session revocation | FIXED. token_version checked via D1 (verify_token_db); logout→token reuse→401; deactivate→401 token + 403 login (ACCOUNT_INACTIVE). All live-verified. |
| Prod auth collapse | MITIGATED (code review). verify_token_db re-reads role/seller_id/status/token_version from D1 — claims no longer trusted. Buyer tokens still reach scoped routes where scope=None is treated as allow-all (see refund hole) — residual, not fully collapsed. |
| Paid-feedback replay | FIXED. POST /feedbacks w/ pay_892a810edfee→200; reuse→409 PAYMENT_REUSED (used_at + feedbacks.payment_id + UNIQUE backstop). Live-verified. |
| SQL injection via json path unit | FIXED (code review). safe_json_key [a-zA-Z0-9_]{1,64} whitelist guards every json_set('$.{unit}') format!() site — refund_impl.rs:57, events_impl.rs:205, purchase_impl.rs:53. |
| Double-settlement race | FIXED (code review). Atomic claim UPDATE orders SET settlement_id=?1 WHERE id=?2 AND settlement_id IS NULL (settle_impl.rs:61). |
| No rate limiting | STILL BROKEN (live). Login: 12 bad attempts→401 each, no 429, account never locks — record_login_fail swallows DB errors (let _ =) so fails=0; likely login_attempts (migration 011) missing on staging D1 or stale worker deploy. license-verify/feedbacks limiting not retested. |
| Demo creds on staging | Not retested (no demo1234 login attempted; unrelated to current fixes). |
| Migration drift | Consistent w/ above — suspected missing migration is the probable cause of dead rate limiter. |
| Non-constant-time compares / no logout / no deactivate | FIXED. ct_eq used for sig+password compares; POST /auth/logout and POST /me/deactivate routed and live-verified. Account-enumeration guard present (unknown emails counted as fails — though moot while limiter is dead). |
Assessment: Core auth P0s (JWT, revocation, onboarding, admin gating, cross-seller write IDOR, paid-feedback replay) verified fixed end-to-end. NOT_READY — remaining live-verified blockers: buyer-refund-any-order (P0), unscoped order/feedback/license lists for buyers (P1), dead login limiter (P1), DEV_AUTH backdoor (P0 env-hygiene).