Purpose and current boundary
Use this before account beta, after access-sensitive changes and at the scheduled review cadence. The command below is a static inventory helper; it is not penetration testing, a formal security assessment or proof that every runtime path is safe.
pnpm access:reviewThe script examines app/actions.ts, lib/access-control.ts, proxy.ts and app/api. It reports recognised guard references, groups exported server actions, classifies API routes and writes tmp/access-review-latest.json. That status file can be overwritten and must be supplemented by a human test record.
After the static and manual reviews, an Administrator records the nine decisions in Settings → Global Settings → Role Access Review. Saving the review creates an account_audit_logs entry. Do not tick a decision merely because the scan says CHECK.
Reviewer roles
| Role | Responsibility |
|---|---|
| Review Owner | Defines scope, assigns findings and makes the go/no-go recommendation |
| Code Reviewer | Inspects server actions, API routes, queries, mutations and audit behaviour |
| Test Operator | Runs positive and negative tests with separate accounts and direct URLs/requests |
| School Administrator | Confirms each person's real school/role need and removes excess access |
| Independent Approver | Checks evidence and accepts or rejects residual risk |
| Host Operator | Confirms file/database/backup permissions outside the app |
The reviewer should not approve their own privileged access alone. For account beta or any public boundary, obtain an independent security review.
Access model to verify
The database roles are:
- owner — displayed as Administrator;
- teacher — displayed as Teacher;
- read-only — displayed as Read-only.
The enforcement levels are admin, teacher and read. A stronger role satisfies a weaker level. Role assignments are active school-specific rows in user_school_roles.
Important exceptions:
- the transitional pilot email fallback has Administrator-equivalent access to all schools when there is no secure account session;
- the Host Operator can bypass app controls by reading SQLite/backups;
- a QR profile session is not a Student account or app role;
- the public-profile-only process must expose only narrow student profile routes;
- Student and Parent/Carer account roles are not enabled;
- native iOS/Android sessions use the same account and school roles; a native client is not a new privilege class.
The review fails if any exception is treated as ordinary least-privilege access without an explicit containment plan.
Review cadence
Perform a full review:
- before account beta or a new school pilot;
- before public production;
- before enabling Student or Parent/Carer access;
- after adding/changing a server action, API route, app role, public route, import/export, native client or transcription workflow;
- after a staff departure, wrong-school event or suspected compromise;
- at least quarterly during a multi-user active pilot;
- annually as part of governance even if code has not changed.
Run a focused review immediately after changing a person's role or school assignment.
Evidence header
Record:
- review ID, date/time and app commit/build;
- environment and deployment mode;
- reviewer, test operator and approver;
- schools and fictional test accounts used;
- whether pilot fallback was enabled;
- scan result and tmp/access-review-latest.json copy/hash;
- routes/actions changed since the last review;
- findings by severity, owner and due date;
- final decision and next review date.
Never include session tokens, invitation/reset links, passwords or backup keys.
Static inventory review
The command prints a server action snapshot and, when classification or audit work remains, an Action review queue. Preserve both in the review evidence.
Server actions
For every exported action:
- identify whether it reads, creates, updates, deletes, archives, exports, signs off or changes access;
- identify the exact target object and its schoolId source;
- confirm the object is looked up and scoped before mutation;
- confirm the required level: setup, accounts, readiness, school approvals, support log and backup controls require Administrator; day-to-day teaching changes require Teacher or Administrator; view-only operations require Read-only or stronger; and only narrowly defined sign-in/self-service actions may be unauthenticated or account-self;
- inspect every branch, early return and error path;
- verify sensitive changes create suitable audit_logs or account_audit_logs evidence;
- verify redirects/revalidation do not replace server-side authorisation.
The scanner's AUDIT classification means a guard was found but audit need still requires judgment. REVIEW means no recognised boundary was found. Both require human resolution.
API routes
The current API inventory includes:
- Administrator: method-book background upload and backup status;
- Teacher/Administrator: rehearsal and lesson check-in/review, lunch practice, practice bingo and other teaching mutations;
- Read-level school routes: profile scan, feedback sounds and some practice/lesson-note endpoints;
- account-self: the current native/session contract;
- public-auth: the PKCE native sign-in exchange;
- public non-sensitive: client configuration and health;
- public-profile: rate-limited QR token exchange.
For every route, inspect the HTTP method and data effect. A static read classification is not enough if POST/PUT/DELETE or a helper writes data. In particular, manually inspect all lesson-note, practice-timer/tracking, mission-result, sight-reading-result, friend-choice, security-code, assessment upload/download, reward-redemption and linked-session routes for write semantics, feature gates, school/object scope, rate limits, request-size limits, cache headers and audit needs.
For assessments, test another student and another school against both teacher and student file downloads, confirm no raw path is returned, and confirm grading fields are absent before release. For linked sessions, test code expiry, another-school join, nonparticipant configuration reads and completed-session friend invitations. For reward requests, test duplicate active requests and both the approval and requested-date redemption gates.
Confirm:
- response fields are the minimum required;
- errors do not leak record existence across schools;
- identifiers from the client are not trusted without a school-scoped lookup;
- POST/PUT/PATCH/DELETE requests require the appropriate write role;
- public responses contain no student or operational secrets;
- public/profile endpoints are rate-limited where required;
- private responses use appropriate no-store behaviour;
- public-profile-only mode fails closed for teacher/admin pages.
Role test matrix
Create separate fictional accounts for each enabled role. Use a private browser context for each and test both UI and direct navigation.
| Test | Administrator | Teacher | Read-only |
|---|---|---|---|
| See assigned school | Allow | Allow | Allow |
| See unassigned school by URL/identifier | Deny | Deny | Deny |
| Switch among assigned schools | Allow | Allow | Allow |
| Edit global settings/readiness | Allow | Deny | Deny |
| Edit school setup and release approvals | Allow | Deny | Deny |
| Create/cancel/replace invitations and manage accounts | Allow | Deny | Deny |
| Create/update Support and Correction Log | Allow | Deny | Deny |
| Create attendance, schedules, progress, practice and pass-offs | Allow | Allow | Deny |
| Read approved school teaching data | Allow | Allow | Allow |
| Submit a direct data-changing request | Allow as scoped | Allow only teacher-scoped | Deny |
| View/download exports | Allow if approved | Allow if approved | Only if deliberately approved |
| Create backup from app | Allow | Deny | Deny |
| Request own account deletion review | Allow | Allow | Allow |
| Resolve deletion requests | Allow | Deny | Deny |
Also test disabled, locked, expired-session and revoked-session states.
Administrator
Test every setup, account, approval, readiness, support and backup action with an Administrator account assigned only to the intended school. Confirm Administrator access at one school does not grant access at another.
Teacher
Test daily teaching workflows with a Teacher account. Confirm setup, account, global-readiness and school-approval mutations are denied at the server.
Read-only
Test direct data-changing requests as well as the UI. Read-only must not create, alter, archive, delete, approve, sign off or trigger operational commands.
Student and Parent/Carer
These account roles are not enabled. The review evidence must show that they remain closed and that a QR profile session has not become a general Student account.
School-object boundary tests
Use two fictional schools and non-overlapping records.
- Change the selected-school cookie/URL and confirm access is still derived from active roles.
- Submit School A student/rehearsal/schedule identifiers while School B is selected.
- Attempt to undo a School A check-in from School B.
- Attempt Student Method Book progress against a book that is not enabled for that student.
- Attempt to add an existing Method Book to a student in a school where the account has only Read-only or no access.
- Attempt to set another school's Default Method Book by changing the submitted school identifier.
- Attempt to create, edit or import a global Method Book with a Teacher or Read-only account.
- Attempt to change another account's Player Method Book progress by supplying its Player Profile or checkpoint identifiers.
- Check notifications contain only accessible-school items.
- Test profile scan with a token/identifier from an inaccessible school.
- Verify a school-scoped support request cannot be read or changed under another school.
- Verify school release approval and account changes require admin for that same school.
- Confirm exports and reports cannot include another school's students.
Record the exact deny result. A hidden button is not evidence of server denial.
Public and QR boundary tests
Health/configuration
- /api/health exposes only non-sensitive host readiness.
- the native config contract contains no secret or student data.
- cache and error behaviour do not reveal database paths, keys or account lists.
QR profile
- exchange uses a random profile token and a narrow, expiring session;
- the session resolves only the intended student;
- only first name plus surname initial and approved profile information appear;
- the Method Book selector shows only the current student's enabled books and remains read-only;
- teacher/admin routes remain unavailable;
- logout/revocation and expiry deny further access;
- rate limiting is exercised;
- public-profile-only mode does not expose the teacher app.
Run explicit allow/deny tests for the full QR write allow-list:
- the pending token-bound session can establish or verify only its own four-digit security code; wrong/repeated guesses are rate-limited and do not unlock;
- an unlocked session can start, pause and complete only its own practice timer, saved minutes remain labelled unverified, and supplied student IDs are ignored or rejected;
- practice tracking can toggle only the current student's preference and does not enable microphone detection, audio capture or a verified-practice claim;
- Key Practice generates notation, pulse and play-along audio locally; its selected-note challenge and profile/device tracker stay in localStorage, do not call a result API and do not claim a live tuning score (the separate Tuner is an explicit hand-off);
- a mission result accepts only a current-catalogue mission, is server-bound to the current student and school, cannot bypass mission order, and cannot award teacher-controlled Licence, Method Book or pass-off progress;
- a sight-reading result is server-bound to the current student and school, and cannot be submitted as a teacher result or used to award progress;
- an avatar change is server-bound to the current student and can only unlock or equip fixed local catalogue items after an atomic Golden Crotchets balance check;
- a theory challenge is issued by the server, single-use and expiring; its answer transcript is rescored by the server before the once-per-day bonus and pity rules can grant a fixed avatar item or Golden Crotchets consolation;
- a Tuning Darts challenge is short-lived, single-use and restricted to the current student's instrument range; the saved target order must match, the score is recomputed from bounded device-reported pitch metadata, and one per-student submission ID makes an ambiguous network retry idempotent;
- a Contact request is session- and school-bound, bounded and rate-limited; its student-authored free text appears only in the intended support/correction queue, is not represented as private/emergency messaging, and is disabled for real data until moderation, safeguarding, support, retention and deletion are approved;
- friend add requires an active scanned profile token; friend removal deletes only the current student's friendship; another student's choice/profile is unchanged; and
- attendance, teacher-controlled progress, Licence awards, method-book progress, schedules, notes, cards, roles, student details and every mutation outside this allow-list return a denial and leave before/after database snapshots unchanged.
Repeat the write tests with no cookie, a pending-but-not-unlocked session, an expired session, a revoked session and another student's identifiers. Verify private/no-store/noindex headers, request-size limits, durable rate limits and the expected minimal audit record without copying tokens or sensitive content.
Live camera decoding must remain local and must not store or upload frames.
Native sign-in
- verified links must fail closed until association evidence is ready;
- handoff is single-use, PKCE S256-bound and expires in two minutes;
- client kind is checked;
- disabled/unverified accounts fail;
- resulting session has the existing account's school roles only;
- replay, wrong verifier, wrong client and expired code all fail;
- responses are no-store and rate limiting is tested.
Future-role boundary
Student and Parent/Carer roles remain Not enabled. Confirm there is:
- no open self-registration;
- no parent/carer account creation;
- no student-to-student messaging;
- no route that treats QR access as a broad Student role;
- no automatic lesson-note delivery;
- no access to another student's private information;
- no UI wording implying those accounts are available.
Do not mark the Student or Parent/Carer review item complete as “feature ready”. Mark it only when the reviewer has confirmed the feature remains closed and documented the future boundary.
Least-privilege review
For every active account and role row, record:
- person/account;
- school;
- current role;
- duties requiring access;
- last confirmed need;
- Administrator approval;
- keep, reduce, disable or investigate;
- action completed and verified;
- next review date.
Rules:
- one active role per user/school is enforced by a unique database relationship;
- choose Read-only unless write duties are demonstrated;
- choose Teacher unless setup/account duties require Administrator;
- remove Administrator access used only for convenience;
- disable access promptly when duties end;
- do not share accounts or links;
- review Host Operator access separately;
- do not count the pilot fallback as reviewed least privilege.
The NIST least-privilege requirement is a useful control reference: authorise only access needed for assigned tasks, review privileges at a defined frequency and remove or reassign excess access.
Audit review
Sample at least one successful and one denied/failed event for:
- sign-in and lockout;
- invitation create/cancel/accept;
- account enable/disable/unlock/role change;
- support/correction create and resolve;
- deletion request create and review;
- readiness/approval changes;
- attendance correction;
- Licence award/correction;
- pass-off override or removal;
- import and backup-sensitive workflow.
Confirm timestamps, actor/account, action, success, school/object references and before/after context are sufficient without recording secrets or excessive student data. Record gaps; do not invent an audit trail in a support note after the fact.
Findings and stop/go criteria
| Severity | Example | Release decision |
|---|---|---|
| Critical | Cross-school read/write; public teacher data; role bypass; secret/token disclosure | Stop pilot/account-beta expansion immediately |
| High | Read-only mutation; unaudited privileged access change; fallback still enabled for account beta; public endpoint leaks sensitive metadata | Block release until fixed and retested |
| Medium | Over-broad view/export; missing audit detail; unclear role ownership | Fix before wider access or obtain time-limited approved exception |
| Low | Documentation/test-evidence gap with no demonstrated access expansion | Track with owner/date |
No account-beta go-live while Critical/High findings remain, while any scan item is unexplained, or while the nine saved review items lack evidence.
Saved nine-point review
Record each item only after evidence exists:
- Administrator actions
- Teacher actions
- Read-only boundaries
- Future Student boundary remains closed
- Future Parent/Carer boundary remains closed
- Server actions
- API routes
- QR profile limits
- Audit records
Save reviewer contact, evidence reference, open findings and next review date in the minimal notes. Run pnpm account-beta:check to report the saved state, but treat it as a gate summary rather than security approval.