Security Audit and Fixes
Audit Summary
Two security audits conducted on the Ricoya codebase. Phase 1 (post-migration) covered payment security, auth, input validation, API security, and infrastructure. Phase 2 (March 4, 2026) covered API routes, auth/payments, and client-side vulnerabilities.
Phase 1 — Post-Migration Audit
| Severity | Found | Fixed |
|---|---|---|
| Critical | 5 | 5 |
| High | 8 | 6 |
| Medium | 9 | 8 |
| Low | 5 | 3 |
| Total | 27 | 22 |
Phase 2 — Full Codebase Audit (March 4)
| Severity | Found | Fixed |
|---|---|---|
| Critical | 4 | 1 |
| High | 9 | 0 |
| Medium | 7 | 0 |
| Total | 20 | 1 |
Phase 1 Critical Fixes (All Resolved)
1. Cart Not Cleared on Logout
Previous cart contents persisted in localStorage after logout. Another user on the same device could see or submit the previous user’s cart.
Fix: User-specific localStorage keys (cart_<userId>). Cart data isolated per user.
2. No Ownership Check on Order Retrieval
Any authenticated user could view any order by ID.
Fix: Added ownership validation. Customers can only view their own orders (returns 403 otherwise).
3. US Fallback Address
Default/fallback address data referenced US locations. App operates in Honduras.
Fix: Changed fallback to Honduras (Tegucigalpa, Francisco Morazan, HN). Removed test data from fallbacks.
4. Sandbox Text in UI
6 instances of sandbox/test text visible to users in production.
Fix: Replaced all sandbox references with production-appropriate text.
5. Dev Bypass Route
Development-only login bypass route was accessible in production.
Fix: Gutted route to return hard 403. No bypass logic remains.
6. No Impersonation Audit Logging
Admin impersonation of other users had no audit trail.
Fix: Added logging with admin/target details on every impersonation action.
Phase 1 High Priority Fixes
7. Payment Flow Console Logs
28 console.log statements in payment processing code could expose data.
Fix: All gated behind development environment check.
8. Logout Race Condition
Navigation could fire before signOut completed.
Fix: Reordered to close sidebar, await logout, then navigate to login.
9. Silent Repository Failures
Catch blocks in 16 repositories silently swallowed errors.
Fix: Added error logging to 25+ catch blocks across all repositories.
10. Security Headers
No security headers were set on responses.
Fix: Added X-Frame-Options, X-Content-Type-Options, Referrer-Policy, HSTS, Permissions-Policy in Next.js config.
11. Cart Negative Amounts
No validation preventing negative monetary values in cart calculations.
Fix: Added floor-to-zero guard on all 5 cent fields in cart repository.
Rate Limiting — Deferred
No rate limiting on any endpoint. Deferred pending architectural decision.
Payment Callback Signature — Deferred
Deferred. Mitigated by downstream verification step.
Phase 1 Medium Priority Fixes
- Rating user_id type — Changed to accept both number and string
- SWR cache on logout — Mitigated by localStorage cleanup + SWR revalidation on auth change
- Impersonation not cleared on logout — Added cleanup in AuthContext
- API timeout message — Protected paths return user-friendly session expired message
- PayPal restaurantId null — Cart page passes restaurantId as search param on PayPal return URL
- Middleware route protection — Expanded to protect admin, manager, driver, orders, cart, profile routes
- Menu error no retry — Added retry button
- Checkout step revert silent — Added notification explaining why user was reverted
Phase 1 Post-Migration Bug Fixes
- SQL injection in push notify route — string interpolation replaced with parameterized queries
- Push notify role check — Added admin role requirement (was open to any authenticated user)
- Driver claim race condition — Checks if update affected rows (0 = already claimed)
- Query method fix — Changed .single() to .maybeSingle() across 14 lookup functions in 9 repos
- SQL injection in driver routes — Replaced string interpolation with safe array queries
- Restaurant logs — Added missing RLS policies and indexes
Phase 2 Critical Findings (March 4)
C1. Push Notify — No Role Check (FIXED)
/api/push/notify — any authenticated user could send mass push notifications. No admin role verification.
Status: Fixed in Phase 1 post-migration fixes.
C2. Payment Status — Customer Can Mark As Paid
/api/orders/[orderId]/payment-status — customers can mark their own orders as “paid” without actual payment processing.
Status: Open. Needs server-side payment verification before status update.
C3. Discount Code Enumeration — No Auth
/api/cart/validate-discount — no authentication required. Anyone can enumerate valid discount codes.
Status: Open. Add auth requirement.
C4. Payment Amount Not Validated Server-Side
CyberSource 3DS verify and PayPal capture do not validate that the payment amount matches the order total. Client-submitted amount is trusted.
Status: Open. Server must recalculate order total before confirming payment.
Phase 2 High Findings
| Issue | Route/Component | Status |
|---|---|---|
| UUID vs numeric ID mismatch (systemic) | Driver access checks always fail | Open |
| Mass assignment on restaurant creation | owner_user_id accepted from request body |
Open |
| Driver/customer PII exposed | No field masking on sensitive data | Open |
| No rate limiting on auth endpoints | Login, signup, OTP | Open |
| Admin endpoints trust client role | No server-side role verification | Open |
| Order total manipulation | Client-submitted totals used in payment | Open |
| Missing CSRF protection | State-changing POST/PUT/DELETE routes | Open |
| Insecure direct object references | Multiple routes lack ownership checks | Open |
| API keys in client bundle | Supabase anon key exposed (mitigated by RLS) | Accepted risk |
Phase 2 Medium Findings
- RLS requires auth for all SELECT (menus, restaurants, categories) — unauthenticated users see blank pages
- No input sanitization on user-generated content (names, addresses, notes)
- Session tokens never explicitly invalidated on password change
- No account lockout after failed login attempts
- Push subscription endpoint accepts any user_id
- File upload accepts any content type (no validation)
- Error messages leak internal structure (stack traces in dev)