Case study · Full-Stack Developer
SafeExam
proctored online exam platform
An online exam platform built on the assumption that people will try to cheat. The timer runs on the server, so refreshing can't buy extra time, and staff watch live webcam feeds. Built with Flask.

The problem
Online exams are easy to game: switch to another tab, refresh the page to reset the clock, or tamper with the browser so warnings never fire. SafeExam assumes candidates will try, so the rules that decide the outcome (time left, violation limits, lockouts) are enforced on the server, and proctors can watch each candidate's screen and webcam while the exam runs.
What the research found
- 22 pytest tests in tests/test_app.py, covering login and registration, role-based access, a full exam scored end to end, disqualification at the violation limit, auto-completion of an expired attempt, PDF evidence reports, CSV exports and teacher ownership of their own students and exams.
Decisions that shaped it
- 1
The server owns the clock. An attempt's deadline is its server-recorded start time plus the exam length, and reopening the exam resumes the same attempt instead of starting a new one, so a refresh cannot reset the timer. Every heartbeat from the exam page checks the deadline and grades the attempt as "Time expired" once it passes.
- 2
Cheating signals are weighed, not just counted. Each violation type has a weight: a stray key press costs 1, a tab switch 2, suspected dev tools 3. The server adds them up against the exam's limit and locks and disqualifies the attempt itself, so the browser cannot ignore the result. A burst of violations in a short window marks the candidate as High Risk on the proctor's monitor.
- 3
Flagged moments are kept as evidence. Live frames are overwritten by each new upload and deleted when the attempt is graded. When a proctor flags a moment, the app copies the current webcam frame (or the screen frame if the exam has no camera) into a permanent snapshot, so it still appears in the evidence report later.
- 4
Built to run on serverless hosting. On Vercel the disk is read-only and the request that receives a frame may not be the one that serves it to the proctor, so frames and snapshots are stored in the Turso database instead of on disk. The libSQL driver lacks parts of the interface SQLAlchemy expects, so the app includes a small adapter that fills them in. Where the host can keep a connection open, live view uses WebSockets; otherwise it falls back to polling.
- 5
Teachers only see their own work. Every teacher page loads exams, questions and classes through ownership checks that return "not found" for anything another teacher created, and teachers cannot open live screens, manage users or read the audit log.
Built with
- Python
- Flask
- SQLAlchemy
- Flask-Login
- Flask-WTF
- Turso / libSQL
- pytest
- Vercel