Repository navigation
Classroom Token Hub v2.2.2
·
514 commits
to main
since this release
Immutable
release. Only release title and notes can be modified.
Deployed to production 2026-10-11 00:23 UTC as 61b509b7f (release workflow run 38098238705). No migration. Release record: docs/ops/audits/DEPLOY_2026-10-11_61b509b7f.md.
Released as 61b509b7f on 2026-10-11 at 00:23 UTC (record). It ships #1535 (GHSA-7fxj-gpw3-23g3) on top of the untagged c2f16fb11 hotfix (#1532). No migration.
Security
- Stored script injection via hall-pass destination and announcements (S1, Critical; S4, Medium; 2026-10-10 audit; GHSA-7fxj-gpw3-23g3). A hall pass's
destinationis student-supplied free text with no enum check against the class's configured pass types, and was rendered unescaped in two places: the teacher's hall-pass history table (templates/admin_hall_pass.html, built viatbody.innerHTML), letting a script-bearing destination run in the teacher's session once the request was approved and the history tab opened; and the student's own dashboard server-state blob (templates/student_dashboard.html,attendance_state_json|safe), letting the same payload run in the student's own session immediately on submission. Separately,nl2br_filter(app/__init__.py) marked a teacher's raw announcement message safe with no escaping, so a hijacked or malicious teacher account could run script in every student's dashboard in that class. All three sinks now escape before introducing trusted markup; no stored or rendered data changes shape. Reach assessment: the exploit chain was reproduced end-to-end pre-fix in a real browser and confirmed to reach full teacher authority on the compromised account (the app's CSP permitsunsafe-inline/unsafe-evaland the CSRF token is DOM-readable); no cross-tenant or sysadmin path was found or exercised. A read-only check of production data found no evidence either sink was ever exploited there. Accessibility: no issues found or fixes needed — the diff adds no new DOM elements or visible markup, only escapes existing attacker-reachable text and switches one hidden data node back to default autoescaping; CI's Accessibility Gate passed on the changed templates (a local axe-core run was blocked by a sandbox-only Chromium mismatch, not attempted as evidence). (#1535)
Fixed
- Reverse no longer appears on the $0 "item removed" entry a purchase reversal writes (#1532; owner report 2026-10-10). Reversing a purchase writes a refund (
REVERSAL) and a $0void_item_removedentry in one operation (SPEC-OPS-001§3.5); both are the compensation, which is terminal (§3.1, §3.6). The student page offered Reverse on the second, and pressing it failed with an error. One list,REVERSAL_RECORD_TYPES(app/services/ledger_correction_service.py), now names a reversal's rows: the student page offers no Reverse on them, the reverse action refuses them with "This entry is part of a reversal and can't be reversed." before anything is written, andreverse_transactionrefuses them too. Tests intests/test_collective_goal_purchase_reversal.py, each confirmed red with its guard removed.