-
-
Hooked: a phishing trainer for grades 6-12, built on the texts, DMs, and emails students actually get.
-
Teachers project a QR code and students join with just a nickname. No accounts. Class data deletes after a week unused.
-
The breach check sends only 5 characters of a hash to Have I Been Pwned. Your password never leaves your device.
-
Live teacher dashboard: first try vs. best try, Passed or Not yet, and the red flags the whole class misses most.
-
Emails, texts, and DMs look like the real apps. Links never open: hover or press and hold to see where they really go.
-
Results show your score, the red flags you missed most, and calm steps to take if you ever do get hooked.
-
After each answer, every red flag lights up inside the message with a one-line note on why it gives the scam away.
Inspiration
I work in IT for a school district. When I looked at the phishing reports we get, almost all of them target staff: fake "please sign this document" emails aimed at employees. Students barely show up. That doesn't mean they're safe. It means they get hit somewhere else: texts asking for a login code, "free gems" DMs, a friend's hacked account sending a link, a package fee that's never real.
Most phishing lessons describe scams in general terms. I wanted something students could actually practice on, built around the messages they really get, that a teacher could run in one class period.
What it does
Hooked is a short game. Each message looks like the app it would arrive in: an email client, a phone's texts, or a chat app's DMs. The student decides Phish or Legit. Then the message lights up: each red flag (or each sign it's safe) gets highlighted with a one-line note, and links show where they really go without ever opening. At the end, students see their score, the kinds of red flags they missed most, and calm steps to take if they ever do get hooked.
For classrooms: a teacher creates a class and projects a QR code. Students join with only a nickname. They can replay, and their best run counts. The teacher's dashboard updates live with each student's best score and Passed / Not yet against a passing score the teacher sets, plus the red flags the whole class misses most, ready to discuss. Results export as a CSV for the gradebook.
No accounts, on purpose. Closing the tab and reopening the link picks up where you left off. On another device, students use a 6-character rejoin code, and the teacher can reset it if they lose it. Classes delete themselves after a week of no use.
Password check: tells you if a password has shown up in a data breach without ever sending the password anywhere, and suggests a random passphrase instead. It also tells students not to type their real password into it, or into any site they don't trust.
How I built it
- Client: React, Vite, TypeScript, Tailwind. Every message lives in one shared file, and a check script makes sure every highlight matches its message exactly. The build fails if one doesn't.
- Server: Express on Railway as a single service. The server grades answers itself from the same shared data, so scores can't be faked from the browser.
- Database: Postgres through Prisma (classes, players, attempts, answers). Migrations run before each deploy, so a failed one leaves the old version running.
- Password check: the browser hashes the password with SHA-1 and sends only the first 5 characters of the hash to the Have I Been Pwned range API. Hundreds of passwords share that prefix, so the service can't tell which one you checked. The app shows students exactly what left their device.
- Testing: axe accessibility scans on every screen at phone and desktop sizes, keyboard-only playthroughs, and a smoke test that runs against the live site.
- AI: built with Claude and Claude Code (Anthropic) as AI assistants.
Challenges I ran into
- Rate limits behind Railway's proxy. My first setup trusted the wrong proxy hop, so everyone looked like they came from the same address and shared one limit. Logging what the server actually saw showed the real header layout. Then I realized a whole classroom shares one IP, so I moved limits from per-IP to per-player and per-class.
- 60 students joining at once. My first player cap locked the class in a transaction. With 70 simultaneous joins, only 20 got in and 50 got errors. I replaced it with a single SQL statement that claims a seat and creates the player together. The same test let exactly 60 in and showed the other 10 a friendly "class is full" message.
- Keeping dev away from production. A migration run from my laptop hit the live database and broke class answers for about two minutes. I restored it without deleting anything, rehearsed the fix on a copy, and moved local work to its own database.
Accomplishments I'm proud of
- Content grounded in where students actually get phished, with legit messages mixed in so it doesn't just teach paranoia.
- Zero axe violations on every screen, plus full keyboard play.
- A privacy model I can explain in one sentence: Hooked stores a nickname and answers, and deletes them after a week of no use.
- A class that survives 60 students joining in the same second.
What I learned
- Test infrastructure assumptions on the real deployment. My proxy assumption was wrong, and only real logs showed it.
- Classroom traffic is bursty and shares one network, and that changes how you design everything.
- You don't need accounts to give people continuity.
What's next for Hooked
- Easy, medium, and hard tracks, so beginners can build up and experienced students aren't bored.
- Google Classroom integration, so teachers can assign it where they already work.
- Testing with real students and real screen readers.
- Spanish and other languages.
Built With
- axe-core
- claude
- claude-code
- express.js
- have-i-been-pwned
- node.js
- playwright
- postgresql
- prisma
- qrcode
- railway
- react
- tailwindcss
- typescript
- vite
- web-crypto
- zod
Log in or sign up for Devpost to join the conversation.