LUMO is a sustainable-fashion marketplace: React 19 SPA, Express/MongoDB API, Socket.IO chat, Stripe and Khalti payments, multi-vendor onboarding and 3D product previews. Once it worked end to end, I re-entered my own codebase as an adversary and documented what I found.
Interrogated my own code. It confessed 44 times.
LUMO is a full-stack sustainable-fashion marketplace built as my final-year project — a React 19 single-page app over an Express and MongoDB API, with real-time chat on Socket.IO, Stripe and Khalti payment integration, multi-vendor store onboarding, and 3D product previews. Once the core flows worked end to end, I went back through all of it deliberately hunting for what I had gotten wrong — and wrote up 44 findings, each with the fix I would ship.
Solo, features-first builds accumulate invisible risk. A marketplace moves money and inventory: an unguarded race condition can oversell stock, a duplicated auth path can quietly re-open the exact attack the session cookie was built to stop, and validation that exists but is never wired up might as well not exist. Nothing was broken on the happy path — which is precisely what makes these bugs dangerous.
Two buyers. One last unit. Both got it. That's the bug.
I built the entire stack and then audited it: 48 backend files and 69 frontend files, in systematic passes over authentication, checkout and inventory, input validation, file uploads, and the frontend data layer.
Build Features-First
The full marketplace first — REST API, SPA, sockets, payment verification — proving every flow end to end before hardening anything.
Adversarial Pass
A systematic sweep over auth, checkout and inventory, validation, uploads, and the frontend data layer — looking for load, attack, and race failures.
Triage by Severity
Every finding classed critical, high, medium, or low by exploit path and effort — 1 / 8 / 13 / 17 across the four tiers, plus nine practices verified clean.
Design the Fix
Each finding paired with ready-to-merge code — atomic updates, wired validators, batched queries — not just a description of the problem.
The backend set a hardened, httpOnly session cookie. The frontend then stored the same JWT in localStorage in seven places and re-attached it as a header on every request — re-opening the exact token-theft path the cookie was built to close. The fix deletes code: stop returning the token in the response body and drop the manual scheme entirely.
Two buyers racing for the last unit could both win — the stock check and the decrement were separate steps. One conditional update makes overselling structurally impossible; the second buyer gets an honest 409.
The seed script created a working admin login with its password printed in source — the one finding needing no chained bug to exploit. Now pulled from the environment or generated randomly with forced rotation.
Vendor-onboarding and product rules were fully written, imported — and never attached to their routes. Two middleware lines each switched on validation for the highest-traffic write paths in the app.
The inbox loaded every thread's entire message history on every visit. Lists now exclude messages and detail views page the most recent fifty — dodging MongoDB's 16MB document ceiling before it hit the busiest threads.
Checkout made one database round trip per line item — roughly fifteen sequential queries for a typical order. Batched reads and bulk writes cut that to two, and closed the same race window the stock fix guards.
Nine deliberate calls verified clean: bcrypt at cost 12, server-side price computation, scoped ownership checks on every resource, server-verified payments, and no secret ever committed to history.
Auditing your own work honestly. It is easy to review for style; it is harder to admit your auth system fights itself. The discipline was treating the codebase as a stranger's — file by file, no skipping the parts I was proud of.
Concurrency is invisible on a dev machine. Every race condition reads correctly in sequence; the oversell only exists when two requests land in the same instant. The audit had to reason about interleavings, not just code paths.
The most dangerous bugs were not missing features — they were finished work left unconnected: validators written but never attached, stock-restoration logic reused in only one of three cancellation paths. Finding those meant tracing every caller, not just reading functions.
The result is a marketplace I understand at an adversarial level, and a written audit that turns every mistake into a designed fix — from a one-line sanitizing middleware to atomic stock updates. It demonstrates that I can build a complete product and then hold it to production standards.
Need someone who can build a complete product and then audit it like an attacker? I write the code — and then I interrogate it.