-
Notifications
You must be signed in to change notification settings - Fork 0
Security
Written by reading the application's own route table, gates and tests. It describes the posture as built, including the parts that are recent and the reason they are recent.
The application does not decide access with scattered checks. Every route is declared in one table with a required field naming its gate, and there are exactly three values:
- public — reachable without signing in,
- session — any signed-in learner,
- curator — administrative.
A route cannot be added without answering the question, because the field is required and the code will not compile without it. Forgetting a gate is a build failure rather than an open door.
A public route must additionally state, on the route itself, why a
signed-out caller may reach it. And a test asserts that the set of public routes
equals an explicit allowlist, exactly.
So opening a route to the world is a deliberate two-place edit: the table and the list. A new public route fails the test until someone writes it down on purpose, and the change shows up in review as the public surface growing.
The public set is small and falls into two categories, both bounded:
- The routes that make signing in possible — the sign-in redirects and callbacks, "who am I", "which sign-in methods work", and sign-out. Requiring a session on any of these would make a session unobtainable.
- The routes a person the gate turned away still needs — the list of subject names the landing page prints, and the support form.
Everything else requires a session or administrative authority.
Honesty is more useful here than a clean story.
Nine routes reached production ungated — the question bank, the lesson text, the concept map, the leaderboard, practice history, concept state, and an unauthenticated database write among them.
Not one was a mistake when it was written. Each was correct under an earlier design in which practice ran without an account. They became defects when that decision changed and nothing noticed, because a route's posture was expressed only by whether a human remembered to wrap the handler. A network-level access control in front of the whole site had been masking all of it, which is a thing that is invisible precisely while it works.
The route table exists so that the tenth route cannot repeat this. The property under test is not "these nine are gated" — that would pass forever while the tenth was added open. The property is structural.
Administrative authority has three independent sources, any one sufficient, all failing closed:
- a role on the signed-in session,
- an allowlist of identities held in configuration,
- a verified identity from the network access layer.
They are evaluated at the gate on every request, never trusted from the token the session was minted with, so a change in authority takes effect on the next request rather than at the next sign-in.
The allowlist exists for a specific reason: a role-only check would lock everyone out, because every account is created with the ordinary role and there would be no way to promote the first administrator. Configuration also survives a database rebuild.
An empty allowlist grants nothing.
A request to an administrative route without authority is answered with 404 — byte-identical to the response for a route that does not exist.
An attacker cannot map the administrative surface by watching which paths refuse differently from which paths are absent. The diagnosis — what identity the caller presented, and what would need to change — is written to the server log, where a legitimate locked-out reviewer can retrieve it, and never returned to the caller.
The network access layer identifies a caller by adding a header. Any caller can add a header. The application therefore verifies that assertion cryptographically against the provider's published signing keys, and checks the audience, issuer and expiry, before it counts for anything.
Trusting the header's presence would have been an authentication bypass available to anyone who could type a header name.
Exactly one route accepts a write without a session: the support form. It exists because a person the sign-in turned away still needs to be able to say so.
It carries hard caps as named constants, checked before the write — a message limit and a page-field limit, with over-long input refused rather than silently truncated.
For authenticated writes, caller-asserted results are not believed. A practice session's score is derived on the server from the answers submitted, not taken from what the client claims the score was.
A signed token in an HttpOnly, Secure, SameSite=Lax cookie, valid 24 hours, cleared immediately on sign-out. See Privacy for exactly what it carries.
- No third-party audit. Nothing here has been reviewed by anyone outside the project.
- No penetration test.
- No bug-bounty programme. Reports are welcome through the support form.
- No formal threat model is published.
- The nine ungated routes above were found by a deliberate audit, not by an attacker and not by a monitoring system. There is no evidence they were exploited, and no monitoring in place that would have shown it if they had been. Both halves of that sentence matter.
Start here
| You are… | Go to |
|---|---|
| Sitting an exam | Studying with Chiron |
| Sceptical of AI questions | Can You Trust These Questions? |
| Here for the learning science | How Questions Are Classified |
| A technical reader | How Chiron Works |
| Reviewing security or privacy | Security · Privacy |
The method
The exams
Boundaries
Chiron is the teacher. Antron is the cave he taught in.