Epic: switching recitations to offline course-owned machines

#28 · open · 1 comments

View on GitHub ↗

adulbrich

A **contingency design**, not a plan of record. Recitations run on paper today and VISION section 12 records that as decided. This epic holds the full design for the case where that decision is ever reversed, broken into the work it would actually take, so the analysis is not redone from scratch under time pressure. Source: `ARCHITECTURE.md`, which specified this fleet in full. Nothing in it exists at Oregon State today. ## Sub-issues **Decide first** - [ ] #18 Decide the trigger conditions for switching recitations to machines **Build (about two person-weeks total)** - [ ] #20 Build the golden image - [ ] #21 Build the on-device filter - [ ] #22 Pin the allowlist empirically, and settle the two known side channels - [ ] #24 Rework session content delivery for the fleet - [ ] #25 Write the TA runbook and rehearse the failure ladder **Procure and deploy** - [ ] #19 Cost and procure the device fleet (about $5,600) - [ ] #23 Make the WiFi enrollment ask to Network Services - [ ] #26 Run a pilot term before buying the full fleet **Parked** - [ ] #27 Escalation variants if device-level filtering proves insufficient - [x] #16 Decide ARCHITECTURE.md's fate once its content lives here ## The design in one paragraph A **self-filtering device fleet**: Raspberry Pi 500 workstations (keyboard-integrated) plug into existing classroom monitors, join campus WiFi, and enforce a per-device allowlist locally. No gateway, no switch, no cabling, no network the course has to own. Students submit directly to Gradescope from their seats with the same workflow they use at home. The chokepoint moves from a room gateway onto each device, which is not a security downgrade at this threat level: students have no root, the rules are baked into a read-only image, and every device is identical. Design goals in order: **cheap** (low thousands, not tens), **self-supported** (one small one-time IT ask; operable by an instructor and two undergraduate TAs), **boring** (commodity hardware, stock OS, standard Linux filtering), and **fails safe** (every failure mode degrades to something a session survives, ending at the paper variant every recitation ships with). ## What already exists, so it is not rebuilt `check` and `pack` are written and in use: they live once in `starters/_lib/` and are copied into each starter by `scripts/build-starters.sh`. That was one row of the original build table and it is done. The Gradescope autograder image is separately tracked in #14 and is needed **regardless of this epic**, because assignments depend on it. ## What changed since the document was written The design assumed OSU GitLab hosted both the starter repositories and the boot-time allowlist file, and that students would clone the week's repo at session start. Neither holds: starters ship as Canvas downloads and git left the required path. #21 and #24 carry that delta. ## Why not BYOD, answered once Recorded here because it returns every budget cycle. A blocking script on student laptops is **trust inversion**: the referee runs on the player's hardware, where it can be not-run, edited, killed, run inside a VM while real browsing happens outside it, or satisfied on one laptop while a second sits in the bag. It is also the commercial-proctoring privacy model in homemade form, invasive for everyone and still bypassed by the people it targets, with breakage liability the course owns. And it cannot carry the no-AI claim at all, because the modern threat is ambient AI inside normal tools (editor copilots, OS assistants, autocomplete), which is invisible to visual proctoring and requires controlling the endpoint, the one thing BYOD surrenders by definition. The least-bad variant, for the record: Safe Exam Browser pointed at a course-hosted web IDE, with only the IDE, docs, and Gradescope reachable. Honest accounting: a real server returns to the architecture (25 concurrent Rust compiles need a serious box), SEB does not exist for Linux, and students with unsupported laptops need loaners anyway, so the course ends up owning devices regardless, just without controlling them. Acceptable as a make-up mechanism or a bridge term; not a primary design. At about $200 per seat, the course-owned fleet is the cheapest mechanism that actually closes the loop.

Comments

adulbrich

## Archive: the full text of ARCHITECTURE.md The file was deleted from the working tree on 2026-08-17 once this epic and its issues carried the work. It was never committed, so this comment is the only remaining copy. Preserved verbatim, including the parts the issues summarise rather than reproduce: the section 2 topology diagram, the full six-item threat model, and the sourcing note. <details><summary>ARCHITECTURE.md, 397 lines, verbatim</summary> # ENGR 103 Recitation Lab: Technical Architecture This document specifies the physical and software infrastructure for the proctored recitations described in `VISION.md` (section 5). Nothing described here exists at Oregon State today. The design goals, in order: 1. **Cheap.** Capital budget in the low thousands of dollars, not tens. 2. **Self-supported.** Runs without central IT involvement beyond one small, one-time ask; operable by an instructor and two undergraduate TAs. 3. **Boring.** Every component is a commodity: Raspberry Pi hardware, stock Raspberry Pi OS, standard Linux filtering, campus WiFi. Nothing exotic to maintain, nothing that only one person understands. 4. **Fails safe.** Every failure mode degrades to something a session can survive, ending at the paper variant that every recitation ships with. The architecture is a **self-filtering device fleet**: Raspberry Pi 500 workstations (keyboard-integrated) plug into existing classroom monitors, join campus WiFi, and enforce a per-device allowlist (official docs, the course starter repositories on OSU GitLab, Gradescope) locally. There is no gateway, no switch, no cabling, and no network the course has to own. Students submit directly to Gradescope from their seats, with the same workflow they use at home. Escalation variants (a filtering gateway, a full air gap) are kept in section 11 for the case where device-level filtering ever proves insufficient in practice, and the recurring "why not BYOD with a blocking script" question is answered once, in full, in section 12. --- ## 1. Requirements What one recitation session needs (25 students, 110 minutes, weekly): - A workstation per student with: Python 3, the Rust toolchain, VS Code and vim, a `check` command that runs the provided tests locally, and browser plus git access to exactly: the official Python and Rust documentation, the course starter repositories, and Gradescope. - No search engines, no AI tools, no mail or chat, no personal cloud storage, from any seat. - Direct, continuous submission to Gradescope (submit as often as you like, last one before the window closes counts). - A room reset between back-to-back sections in under 10 minutes, leaving no trace of the previous student at any seat. - Weekly content updates and allowlist changes without touching 25 devices by hand. Constraints: no dedicated sysadmin, no course-owned network, existing classroom monitors, and campus WiFi as the only connectivity. --- ## 2. System overview ``` Campus WiFi ──── Gradescope, OSU GitLab, docs.python.org, │ doc.rust-lang.org, SSO/Duo (and nothing else) │ ┌────┴──────────────────────────────────────────┐ │ 25 × RASPBERRY PI 500 (+2 spares) │ │ keyboard-integrated, existing monitors │ │ Raspberry Pi OS, overlay FS (reboot = clean) │ │ no sudo for students │ │ ON-DEVICE ALLOWLIST: │ │ dnsmasq resolves only allowlisted names │ │ nftables permits only resolved IPs │ │ allowlist file pulled from GitLab at boot │ │ browser policy: no DoH, no ECH, no extensions│ └───────────────────────────────────────────────┘ ``` The chokepoint moves from a room gateway onto each device. That is not a security downgrade at this threat level: students have no root, the filter rules are baked into a read-only image, and every device runs the identical configuration. What it removes is an entire class of infrastructure (gateway box, switch, cables, uplink negotiation). What it costs is central logging and path-level filtering, both accepted and documented in section 8. --- ## 3. Client hardware ### Raspberry Pi 500, not Pi 400 The Pi 400 (2020, Pi 4 class, 4 GB RAM) is below the comfortable floor for VS Code plus rust-analyzer plus a documentation browser running together; it will swap and students will feel it. The **Pi 500** (Pi 5 class, 8 GB RAM, integrated keyboard, $200 as the desktop bundle with power supply, mouse, SD card, and cables) handles that workload with room to spare, and compiles the course's one-file Rust scaffolds in single-digit seconds with the crate cache pre-warmed. If extra budget appears, the Pi 500+ (16 GB, built-in SSD) is a premium option, not a requirement. | Item | Qty | Unit | Total | |---|---|---|---| | Raspberry Pi 500 desktop bundle (8 GB Pi with integrated keyboard, PSU, mouse, microSD, HDMI cable) | 27 | $200 | $5,400 | | Spare 64 GB A2-class microSD cards (flashed, on the shelf) | 5 | $12 | $60 | | Storage tote / small cart | 1 | | $150 | | **Total** | | | **about $5,600** | Monitors are the room's existing ones (the Pi 500 drives any HDMI display); if the room has none, used 22 to 24 inch panels add about $35 per seat. The whole fleet fits in a lockable tote, so no dedicated room is required: any classroom with monitors (or a cart of monitors) becomes the lab for 110 minutes. Two spare Pis plus five spare flashed SD cards make the swap procedure trivial: a misbehaving seat gets a new card first (30 seconds) and a new Pi second (2 minutes). ### The golden image Raspberry Pi OS (Debian-based, ARM64), one image flashed identically to every card with Raspberry Pi Imager (parallelizable with a USB hub). On it: - **Overlay filesystem mode on** (a stock `raspi-config` feature built for kiosks): the root filesystem is read-only with a tmpfs overlay, so every reboot returns the device to factory state. The between-section room reset is "power cycle everything," under 5 minutes, zero custom engineering. - Accounts: `student` (no sudo) and `proctor` (sudo, password held by TAs). - Toolchains: Python 3 with pytest vendored; Rust via rustup (ARM64) with a pre-warmed cargo cache for every crate any scaffold uses; git. - Editors: VS Code for ARM64 with rust-analyzer and the Python extension pinned; vim. - Browser: Firefox or Chromium under a managed policy that disables DNS-over-HTTPS, Encrypted Client Hello, sync, and extensions, so hostname filtering cannot be tunneled around from inside the browser. - **No course-specific software.** The image carries toolchains, editors, the browser policy, and the filter, nothing else. Everything course-specific (the problem spec as the repo README, the twin scaffolds, the visible tests, and the `check` and `pack` scripts) arrives inside each assignment's starter repository, cloned at session start. Consequences: the fleet serves any section or any course that adopts the same pattern, tool fixes ship weekly with content instead of requiring re-imaging, and take-home parity is automatic because students clone the identical repo at home. `check` runs the provided tests locally, printing exactly what Gradescope's autograder will print; `pack` zips the problem directory into `submission.zip` for the Gradescope upload box; both are plain Python scripts so they run anywhere the course does. - **On-device filter:** dnsmasq resolves only allowlisted hostnames (all other queries return NXDOMAIN); nftables permits outbound traffic only to IPs that came from those resolutions and drops everything else, including raw-IP connections. The allowlist file itself is fetched at boot over HTTPS from a URL named in a small per-deployment config file, normally a file in the course's OSU GitLab group, so an allowlist change is one commit, effective at next boot, and never a 25-device hand-edit; a shared fleet points at a different course's list (or a merged one) by editing that one config, not by rebuilding the image. If the fetch fails, the device uses the copy baked into the image and shows a warning on the login screen. - Lockdown: USB mass storage disabled for `student`, enabled for `proctor` (the emergency collection path); no extra login TTYs. ### WiFi enrollment: the one IT ask Campus WiFi is WPA2-Enterprise, so 27 Linux devices need either MAC registration on OSU's device/IoT network or a course service account. This is the design's single dependency on central IT: one conversation, once, with Network Services. It is a registration request, not an infrastructure request (no VLANs, no firewall rules, no imaging), which is what keeps it small. The credentials live in the read-only image outside the student account's reach. --- ## 4. The allowlist | Purpose | Hosts | Notes | |---|---|---| | Python docs | `docs.python.org` | exactly this host | | Rust docs | `doc.rust-lang.org` | **not** `*.rust-lang.org`: `play.rust-lang.org` executes code and `users.rust-lang.org` is a forum | | Starter repos + allowlist file + course site | OSU GitLab host, course site domain | see the GitLab caveat below | | Submission | `www.gradescope.com` plus its pinned asset hosts | discover exact CDN hostnames in log-only mode; never wildcard a CDN domain | | Login | `login.oregonstate.edu`, the Duo API hosts | the SSO chain Gradescope redirects through | Built empirically: the image ships with a log-only mode (same dnsmasq and nftables rules, logging instead of dropping); two mock sessions in that mode produce the exact hostname set, which then gets pinned. Two caveats, accepted knowingly rather than discovered later: - **GitLab is a side channel.** Hostname filtering cannot restrict paths, so allowlisting OSU's GitLab reaches all of it, including snippets and repos any student can create and share mid-session. At first-year threat level, with proctors in the room and the oral check asking students to explain their own code, this is an accepted risk; the tighter alternative (serve starter packages from the course site and drop GitLab from the session list) stays available if a term demonstrates abuse. - **Canvas is recommended off, even for instructors who have no course website.** Canvas Inbox is a live messaging channel to anyone in any of a student's courses, it cannot be disabled per course, and hostname filtering cannot carve it out. The starter-repo pattern removes the last in-session need for it: the problem spec is the repo's README, so GitLab delivers everything in and Gradescope carries everything out, and no website is required either. An instructor who allowlists Canvas anyway should do it as a knowing decision, accepting the channel and telling the proctors what to watch for, not as a default. Operational wrinkle for the runbook: OSU logins require Duo, so phones come out for the first five minutes to authenticate to Gradescope under proctor supervision, then go away. Gradescope sessions persist, so this happens once per session. --- ## 5. Submission and grading flow Students submit **directly to Gradescope from the seat**, as often as they like; the last submission before the TA closes the assignment window counts. Identical workflow to take-home assignments, so recitation day introduces zero new mechanics. Gradescope's own submission list is the TA dashboard (including for oral-check sampling: visit students whose code exists), the hidden tests run in the same Docker autograder used for assignments, and TAs do the human 20% (style spot-checks, stretch Polya plans) in Gradescope's rubric view afterward. Failure ladder, in order: 1. **Gradescope hiccup, WiFi fine:** students keep working; `check` is fully local. Submit when it recovers. 2. **Classroom AP has a bad day:** a TA phone hotspot is a legitimate emergency uplink, because enforcement lives on the device, not the network; the filter rules do not care which network carries the packets. 3. **WiFi gone entirely:** students keep working locally to the end, then the proctor account USB-collects `submission.zip` files, 25 seats in about ten minutes, and a TA uploads them from any online machine. 4. **Everything dies:** the paper variant, which every recitation ships with anyway for DAS and make-up cases. --- ## 6. Weekly content pipeline Authoring happens in this repository. A `make lab-week-N` target assembles the week's starter repository and pushes it to the course's OSU GitLab group. Each starter repo is self-contained: the problem spec as its README, the twin Python and Rust scaffolds, the visible tests, any data files, and the `check` and `pack` scripts, all versioned together. The same GitLab group hosts the device allowlist file. At session start students clone the week's repo (one command, written on the board). Client images never change during a term; a tooling fix ships with next week's repo, and an allowlist change is a git commit plus a reboot. --- ## 7. Session lifecycle (the TA runbook, condensed) **Before (10 min):** hand out or power on the Pis (they boot clean into overlay mode), spot-check one seat (docs load, Gradescope loads, a search engine does not), open the Gradescope assignment window. **Start (5 min):** students log into Gradescope (phones out for Duo, then away), clone the week's starter repo, begin. **During:** students work, `check` locally, upload to Gradescope whenever they want. TAs proctor, run the sampled oral checks off the Gradescope submission list, and swap any misbehaving seat (SD card first, whole Pi second; prior submissions are already on Gradescope, nothing is lost). **End:** TA closes the Gradescope window; students log out. **After (10 min):** power cycle (overlay mode discards everything), pack the tote. Grading happens on Gradescope on its own schedule. --- ## 8. Security and integrity model Threats, in decreasing order of realism, with the honest residuals: 1. **AI or search access from a seat.** Blocked by the on-device resolver and firewall, reinforced by the browser policy and the read-only image. Residual: personal phones, a proctoring matter, as in any exam. 2. **Side channels inside allowed hosts.** GitLab snippets and shared repos (accepted, documented above), Gradescope regrade-request text boxes (low bandwidth, visible to instructors). Canvas is kept off precisely to avoid the biggest of these. 3. **Tampering with the device filter.** Students have no root, the root filesystem is read-only, and the overlay discards any runtime state at reboot. Residual: physical attacks (pulling the SD card) are conspicuous in a proctored room and unproductive (the card holds no secrets beyond WiFi enrollment, which is revocable). 4. **A student sees the previous section's work.** Overlay mode plus the between-section power cycle; cross-section problem leakage by conversation is handled with per-section value variants (cheap under the family model). 5. **No central logging.** Without a gateway there is no single audit point for network activity; Gradescope timestamps and the oral check carry the integrity evidence instead. Accepted: the pedagogy (fresh variants, explain-your-code) is the deep defense, not packet logs. 6. **Session network dependence.** A graded event relies on campus WiFi and Gradescope uptime; mitigated by the failure ladder (local `check`, phone hotspot, USB collection, paper variant). If a term demonstrates real, repeated circumvention, escalate to section 11. --- ## 9. What must be built, and by whom | Artifact | Size | Effort | |---|---|---| | Golden image build script (pi-gen or shell over Raspberry Pi OS) | small | 4 to 5 days including filter, browser policy, overlay mode, and lockdown testing | | On-device filter config + boot-time allowlist fetch + log-only mode | config + scripts | 2 to 3 days | | `check` / `pack` scripts (shipped inside every starter repo) | under 1k lines | 3 days | | Weekly packaging (`make lab-week-N` to GitLab) | small | 1 to 2 days | | TA runbook + failure drills | prose | 2 days, then refined each term | Total: roughly **two person-weeks**, the smallest of the three designs this document has considered, because there is no server, no gateway, and no export tooling: Gradescope and GitLab do the serving. Budget-compatible sourcing: a few weeks of one funded student developer, or an **EECS senior capstone team** ("build and harden a self-filtering exam fleet" is a real project with a real adversarial model). Everything lands in this repository or a sibling repo, MIT licensed, reusable by any course facing the same GenAI assessment problem. --- ## 10. Rollout plan - **Term minus one:** buy 5 Pis, build the image, run the filter in log-only mode through two mock sessions to pin the allowlist, make the WiFi registration ask, fix what breaks. - **Pilot term:** one section uses the fleet while others run the paper variant; compare outcomes, tune the runbook, then buy the remaining seats with the pilot as evidence for the budget request. - **Full deployment:** all sections, spares in the tote, documentation handed off. --- ## 11. Escalation variants Kept for the case where device-level filtering proves insufficient in practice. Both reuse the fleet; neither is the starting posture. - **Filtering gateway:** put the Pis on their own switch behind one course-owned mini PC running squid in SNI-peek mode (no client certificates, fails closed). Restores central logging and a single enforcement point at the cost of owning a switch, a gateway, cabling, and an uplink. About one extra person-week and $300. - **Air-gapped island:** unplug the uplink entirely; the gateway becomes a room server with mirrored docs, a small signed-bundle submission API with an SQLite audit trail, and a post-session batch upload to Gradescope (or a local run of the same autograder container with a CSV import to Canvas). Maximum integrity, maximum friction: mirrored docs instead of live, a submission path that differs from take-home, and about two more person-weeks of software. The paper variant remains beneath even this. --- ## 12. Appendix: the BYOD question, answered once Every budget cycle someone will ask: instead of buying devices, could students run a script on their own laptops that blocks or monitors sites during the session? Documented here so the analysis does not have to be redone. **Why student-run enforcement cannot work: trust inversion.** The script is a guest on hardware the adversary owns with administrator rights. It can be not-run, edited, killed, run inside a virtual machine while the real browsing happens outside it, or satisfied on one laptop while a second device sits in the bag. "The script phones home to prove it is running" is spoofable without TPM-backed remote attestation, which cannot be deployed across an uncontrolled fleet of personal Windows, macOS, Linux, and ARM machines. The referee must not run on the player's hardware. **Why it is also the privacy nightmare it tries to avoid.** A course-mandated tool with admin rights that inspects or filters network traffic on personal devices is the commercial-proctoring model (Respondus, Honorlock) in homemade form: invasive for everyone, legally delicate, resented by students, and still bypassed by the people it targets. It also creates breakage liability: firewall-manipulating scripts across hundreds of personal laptops will corrupt someone's VPN or WiFi configuration, and the course owns that damage. The cost lands on the honest majority; the benefit rounds to zero. **Why BYOD cannot carry the "no AI" claim at all.** The modern threat is not visiting a chatbot site, which a proctor might notice. It is ambient AI inside normal tools: editor copilots, OS-level assistants, AI autocomplete. On a personal machine those are invisible to visual proctoring and indistinguishable from typing. Blocking them requires controlling the endpoint, which is the one thing BYOD surrenders by definition. **The least-bad BYOD variant, for the record.** Safe Exam Browser (free, open source, non-invasive: it locks the browser rather than monitoring the machine) pointed at a course-hosted web IDE (code-server), with only the IDE, the docs, and Gradescope reachable from inside it. Honest accounting: a real server returns to the architecture (25 concurrent Rust compiles need a serious box), SEB does not exist for Linux, and students with unsupported or failing laptops need loaners anyway, so the course ends up owning devices regardless, just without controlling them. Acceptable as a make-up-session mechanism or a bridge term before capital arrives; not a primary design. **Bottom line.** At about $200 per seat, the course-owned fleet is the cheapest mechanism that actually closes the loop. BYOD's capital savings are spent back, with interest, in support burden, privacy exposure, and a certification claim the course can no longer honestly make. </details>