Repository navigation
PyStarter v1.0.7 — security hardening (please upgrade)
A security review of 1.0.6 found real problems in how student code was executed. Upgrade if you are running any earlier version, and read SECURITY.md.
What was wrong in 1.0.6 and earlier
- Student code ran inside the Django process. The Python restrictions can be escaped — submitted code reaches real builtins through ordinary object attributes — and in-process that meant an escape reached the application database connection, the Anthropic API key, the Django secret key, and Django internals.
- The execution deadline did not stop anything. A 1-second limit returned a
timeoutstatus only after the code ran to completion (measured: 9.2s). An infinite loop occupied a worker. - The container ran as root, so an escape inside Docker was root inside the container.
- The stack published on every network interface with TLS, secure cookies, and HSTS off.
- Tokens lived in browser-readable storage and logout never revoked them server-side.
- Password validators were configured but never invoked —
password1was an acceptable password. - AI critique was ungated and puts the exercise solution in the prompt, so it could be steered into revealing answers for exercises the caller had not solved.
Fixed in 1.0.7
| Area | Change |
|---|---|
| Execution | Separate process, scrubbed env (no keys, no DB password), memory limits, deadline that kills the job |
| Container | Non-root user, uid 10001 |
| Exposure | Binds 127.0.0.1 by default; set NGINX_BIND to widen deliberately |
| Auth | httpOnly cookies, refresh token blacklisted on logout, default-deny permissions |
| Passwords | Django validators enforced on registration and reset |
| AI | Critique requires a passing submission by the requesting user |
Verified in a clean install: an escaped payload now sees uid 10001 and four environment variables (HOME, LANG, LC_CTYPE, PATH) — no secret key, no database password.
The honest caveat
This is not a hard security boundary. Submitted Python can still escape the language-level restrictions; what changed is what an escape can reach. There is no seccomp profile, user namespace, or network isolation. PyStarter is for local use with people you trust. For untrusted users, put real isolation under the executor (gVisor, Firecracker, nsjail) — see SECURITY.md.
Upgrading
sed -E -i.bak "s/pystarter-(backend|frontend):[0-9.]+/pystarter-\1:1.0.7/" docker-compose.yml
docker compose pull
docker compose up -dYour progress in the pgdata volume is untouched. If you previously set NGINX_PORT to reach PyStarter from another machine, note the default is now loopback-only — set NGINX_BIND consciously, and read SECURITY.md first. Existing sessions are invalidated by the move to cookie auth; log in again.
Images
ghcr.io/e-conners-lab/pystarter-backend:1.0.7(linux/amd64, linux/arm64)ghcr.io/e-conners-lab/pystarter-frontend:1.0.7(linux/amd64, linux/arm64):latestnow points at 1.0.7