# Security and deployment notes ## Student Python execution - Docker installs Bubblewrap and `prlimit`; the app runs as the unprivileged `telearn` account. - Each public Python run gets a copied, size-limited project workspace and CPU/address-space/file/process limits. Child environments use an allowlist and do not inherit application tokens or admin credentials. - `RUNNER_ISOLATION_MODE=sandbox` (the source default) additionally requires Linux user/PID/network namespaces and read-only system mounts, and fails closed if the host blocks namespace creation. - At the owner's request, the Docker image explicitly sets `RUNNER_ISOLATION_MODE=process` to restore Python demos on Hugging Face, whose current host rejects unprivileged namespace creation. This mode runs a separate, resource-limited process as `telearn`, but provides **no filesystem, network, or same-user process isolation**. Environment filtering and temporary project copies are not a security sandbox. Set the mode back to `sandbox` on a compatible execution host. - Uvicorn caps WebSocket frames at 1 MiB. Classroom sockets also have per-IP handshake and per-student message budgets; saved checkpoints/submissions have per-hour limits, cooldowns, a per-course quota, and a rolling retention cap. ## Classroom storage Defaults are configured in `Dockerfile` and may be changed with `CLASSROOM_*` environment variables. The database retains up to 100 checkpoints and 100 submissions per student per Lesson, with a 1 GiB active-data quota per course. Older versions are removed from the active SQLite history and Hugging Face repository tree. Deleting a file from a Hugging Face repository's current tree does not rewrite earlier Git commits. Therefore these limits bound the active version set and current tree, but cannot strictly cap the repository's historical Git object storage. A hard lifetime storage cap requires moving classroom versions to bounded object storage or periodically archiving/recreating the repository; neither is performed automatically. ## Browser security The app sends CSP, COOP/COEP, `X-Content-Type-Options`, Referrer Policy, Permissions Policy, and HSTS (enabled in the Docker image). CodeMirror, JSZip, and Pyodide are served same-origin from exact package versions included during image build. The pinned `pygame-ce` wheel and its lockfile dependency closure are also vendored same-origin, with each wheel SHA-256 checked against Pyodide's lockfile during the image build. Google Fonts, the QR service, Appetize, and MakeCode remain explicit external sources. Student HTML remains inside an opaque-origin sandbox iframe. After deployment, inspect the final public HTTPS response headers: Hugging Face's edge proxy must preserve or add HSTS and must not replace the application CSP with a weaker policy. Local source changes cannot prove what the edge returns. ## Dependency scanning and lock files Every Docker build resolves `requirements.txt` in a Python 3.11 build stage, generates a complete transitive lock with hashes, audits that resolved lock using `pip-audit`, then installs the runtime dependencies with `--require-hashes`. The generated lock is included in the image as `/app/requirements.lock`; it is build-specific rather than checked into source, so rebuilding later may select newer versions allowed by `requirements.txt`. GitHub Actions also runs `pip-audit` on pull requests and weekly when this repo is mirrored to GitHub; the canonical remote for this project is Hugging Face, which does not execute that GitHub workflow by itself.