Dex Bennett / Stylenecy

Case 01 · 2026

LEAP 2036

High-school students in a three-day workshop

A decision game that compresses ten years of adult life into forty-five minutes, built for students on their own phones with bad school wifi. Then I audited it and found I had left their data readable by anyone.

Status
Live — used in the field 3–5 Aug 2026
My role
Designed and built it · workshop facilitator
Built with
Vanilla JS · PWA / Service Worker · Supabase Postgres · Row Level Security · Vercel Functions
LEAP 2036 — screenshot of the live site, 6 Oct 2026
12decision rounds2026 → 2036
7endingsno winning one
0external dependenciesno CDN, no fonts, no npm
8 / 8engine parity tests passscoring cannot drift

(01) The problem

What was actually in the way

I was the overall chair of a 59-student community service programme run with Hong Kong Polytechnic University, and my group had to run one workshop session per class across two high schools. Every class was met exactly once. There was no second chance and no dress rehearsal.

The workshop had to make a teenager feel the weight of a ten-year decision — money, energy, mental health — in a single school period. A slideshow cannot do that. A game can.

Three constraints made this harder than a normal web app. School wifi could not be trusted, so it had to run offline. It ran on the students' own phones, so it had to install like an app and survive a dead connection mid-session. And it involved minors, so it could not collect names.

(02) Decisions

What I chose, and why

01The scoring engine never touches the DOM

All scoring lives in one pure module: resource deltas, a hard clamp between 0 and 200, the phase-one total captured at the seam, and a classifier that resolves one of seven 2036 profiles from invariants, votes and tie-breakers. Nothing about the display can change a score. That meant I could redesign the entire interface for the workshop without risking the numbers.

02A parity test suite guards the maths

Eight cases — invariants, votes, the phase-two bet payoff, running clamps, chaos-card seams, and full A/B sweeps — are checked against the original reference implementation. I ran it before and after every change to the visual layer. If a redesign ever silently altered an outcome, the suite fails instead of a student getting the wrong ending.

03Zero dependencies, so it survives the worst room

No CDN, no web fonts, no npm packages, system fonts only. The consequence is that the game opens straight from a file on a USB stick with no server and no internet. Installed as a PWA, the whole shell is cached and plays fully offline. The facilitator dashboard and the live data calls are deliberately excluded from that cache so the classroom board never shows stale numbers.

04Codes, never names

Each student gets a printed pseudonymous code. What is stored is the code, the three scores, the profile, and the values/DISC/goal answers — nothing that identifies a child. If the network drops mid-session the run is queued in local storage and retried on reconnect, so a bad signal costs nobody their result.

05The security audit — and what it found

I ran a self-audit on my own live system before the workshop. Anyone holding the public key could read every participant row straight from the REST endpoint. I confirmed it with a single request: HTTP 200, real rows, without ever opening the dashboard. The dashboard's password was a plain string sitting in the page source and was never protecting anything — the actual read policy underneath was open to anyone.

06How I closed it

I dropped the open read policy so the public key can now only insert, never read back. All reads moved behind a serverless function that holds the privileged key server-side and compares the password with a timing-safe check. I added database CHECK constraints so impossible values are rejected at the last layer regardless of which path they arrive through. Then I re-verified in production: reading anonymously returns an empty set, all three constraints reject bad data, a legitimate game submission still succeeds, and the old password is refused by the server.

(03) Result

What is true now

  • Used for the workshop on 3–5 August 2026 across two high schools, with 210 pre-printed pseudonymous codes for my group's five classes.
  • The hole was found by me, on my own project, before anyone else was affected — and closed, applied to production, and re-verified rather than written down as a to-do.
  • The offline path is not theoretical: the same folder plays from a USB stick with no network at all.

(04) Not done

What is unfinished or untrue yet

  • The engine is deterministic and tested; the classroom outcome is not something software can measure. Whether a student actually felt anything is not a number I have.
  • I only know the security hole existed for as long as the open policy did. I cannot prove nobody read the data before I closed it.

(05) Sources

Where these claims come from

Working documents on my own machine, not marketing copy. Listed so the numbers above can be challenged.

— leap-2036/AUDIT_HANDOFF_REPORT.md §8 — security fix, discovery, fix, and production re-verification— leap-2036/README.md — dependency and privacy claims— leap-2036/migration_2026-07-31_security_fix.sql — the applied migration— Semester 6/7. KKN/PROJECT_MASTER.md — chair role, 59 students, workshop schedule— Semester 6/7. KKN/docs/workshop/ — class schedule, code cards (v2, 210 codes)
Animations are off on this device