What is deliberately not here
Zayeed is a new product that shares a name with an older one and nothing else. Several things did not come forward, and the reasons are worth stating plainly.
The action set is closed
The program this one succeeds could spawn arbitrary processes from data inside an exam file — a remote-code-execution primitive distributed as courseware. Here the action set is closed and capability-scoped: show a message, show media, navigate, branch, set a flag, call a declared webhook. There is no process spawn, and no extension point through which one could be added.
No shared secret
The predecessor shipped with an encryption password that was identical in every copy ever sold, on a file that was an Access database with the extension changed. A secret that every customer holds is not a secret.
Here every pack gets its own random key, wrapped into key slots — a passphrase, a colleague's public key, this device, an entitlement, or a key an invigilator releases only inside the exam window. No encryption key is compiled into any binary, no key is shared between customers, and there is no master key that would let us open your files. The one key that is compiled in is a public one — the signature key that lets a shell recognise our releases and licences — and a public key opens nothing.
The container is encrypted and so is its directory, because for an exam paper the structure is disclosure: a ZIP tells you the question count before you decrypt a byte. Signing is Ed25519, with key material kept separate from encryption. The specification and its test vectors are published, the reference decoder ships with every shell, and a build gate asserts that any key holder can always export the whole thing in the clear — encryption protects a paper from a candidate, not an author from us.
The download is checked against a signed list, not a printed number
A SHA-256 printed beside a download catches a damaged file and nothing else: whoever can replace the file can replace the number printed next to it. So a release is published as one signed manifest covering every artefact — name, size, digest, build date — and the public key is pinned in the source of all four shells rather than fetched from this site, because a key served by the host a forger controls is a key the forger chose. Each shell will hash a file you pick, on your own machine, and check it against that list; a file is never reported as verified unless the manifest’s own signature verified first.
The publisher key now exists and is pinned in all four shells — key id b46538f1336eacf8, published 6 October 2026 — so the check passes for a genuine download and fails for an altered one. The signed manifest for each release is served at /dl/release.json, and the public half of the key at /dl/release-key.txt, for anyone who would rather check it outside the application than trust the application to check itself.
One honest limit remains, and it is the important one to be clear about: the signature proves continuity, not identity. A verified download came from whoever signed the previous release and was not altered between the build and your disk. Attributing a file to a paid-for identity is what a code-signing certificate from an authority your operating system already trusts does; none has been bought, so Windows and Android will still warn you about an unknown publisher.
A second factor, and never a first one
An account can carry a security key or a passkey — WebAuthn, with ES256 and Ed25519 — or an authenticator app, or both, and a key is better. Either way it is a second factor: it is presented after a password has already been accepted, and it cannot obtain a session on its own. The challenge the browser answers is a short-lived signed ticket rather than a row in a table, so the check survives a restart and a second server; and the ticket is a convenience, because every factor refuses a replayed answer by itself.
- A key that was copied says so. An authenticator’s use counter must advance. If the same count comes back twice, the sign-in is refused with a sentence about the key having been copied, rather than accepted because the signature was valid — which it would be, on a clone.
- A code works once, not for ninety seconds. A thirty-second step with a step either side means a code is live for up to a minute and a half, which is long enough to be read over a shoulder. The step a code was accepted at is stored, and anything at or below it is refused: the second presentation fails inside the code’s own window.
- Recovery codes come with the first factor, not later. Ten of them, shown once, in an alphabet with no
O/0orI/1, because they get printed and read down a phone line. An exam officer locked out at 08:50 is a cancelled sitting, so an account cannot reach the state of having a factor and no way back in. A spent code is marked spent rather than deleted — a deleted row says nothing, and an appeal needs something to read. - RS256 is refused at enrolment, by name. Two algorithms are supported and the third is declined with an explanation. A user finds that out in a browser with their other key in their pocket, instead of at 08:50 on exam day.
- Attestation is deliberately not verified, and that is stated. Checking a manufacturer certificate against the FIDO metadata service would put a network call in the sign-in path and a policy decision about acceptable hardware in our hands rather than an institution’s. What protects an account is that the key which enrolled is the key that signs.
- Enrolling a factor re-checks the password. A token can be lifted from an unlocked laptop, and quietly enrolling a key of your own onto somebody else’s account is how a stolen session becomes a permanent one.
- Federated sign-in does not ask again. Where a sitting is behind Shibboleth, Entra or Okta, the step-up policy belongs to the institution that runs the directory. A second lock on the same door is not twice the security.
Two devices, no server
Two machines on the same network sync directly. They authenticate by key rather than by account — "same account" is a claim a network cannot check, and a stolen token would satisfy it — and on the first pairing both screens show the same six digits. If the digits differ, something is answering in the other device's place, and the screen says to stop rather than showing a warning icon nobody reads. Note what that does not forbid: a rendezvous relay carrying the frames between two buildings changes nothing, because it forwards them verbatim and cannot open one — and if it swapped a key instead, it would change the handshake transcript, and the two numbers would differ. The check is the same check, which is why the same screen can offer both routes. After that the traffic is XChaCha20-Poly1305 with a counter that makes a replayed or reordered frame a refusal, because a repeated "push these changes" is exactly how two copies of a library are made to disagree. On the desktop the native side carries sealed bytes it cannot read; every decision with a security consequence is the same code Android runs.
A paper that cannot be read before the exam
A sitting has its own key pair. The paper is sealed to the public half and can be distributed a week early; the secret half stays with the invigilator until the window opens. A sealed paper has no passphrase fallback, so there is exactly one door. An early release — a fire drill, a timetable change — needs a name and a reason, and every request, granted or refused, is recorded so an appeal has something to read.
A licence can never change a mark
Entitlements are signed tokens checked on the device, with no call to us: a laptop in a hall verifies one from a public key alone, and a lapsed licence has a grace period so a school whose card expired over the holidays is not locked out mid-exam. What a licence may do is decide whether a screen opens. What it may never do is affect a grade, and that is enforced structurally rather than promised: a test fails the build if the grading engine ever depends on the licence package.
No card numbers
The old back office stored customers' credit-card numbers. Card details go to the payment processor and never to us. An assessment company that stores card numbers has taken on the compliance burden of a payments company for no revenue.
What runs where
- Marking happens on the device. A grade does not depend on reaching us, which is also why an offline sitting is not a degraded sitting.
- The LTI signing key never leaves the server, and the gradebook credential never reaches a device.
- Self-hosting is first-class. Point the server at your own Postgres and the data is on your estate.
- Webhooks must be declared by the pack before an item can call one, so a rule cannot reach an arbitrary host.
Determinism as a security property
Randomised papers are seeded and the seed is recorded with the attempt; access arrangement arithmetic is integer; scores are recomputed from stored attempts rather than served from a cache. Each of these exists so that a disputed result can be reproduced exactly, months later, by someone who was not there.
Reporting a vulnerability
Mail hello@zayeed.org with "security" in the subject. Please include enough to reproduce the issue and give us a reasonable window before publishing. Reports about the grading engine's correctness are equally welcome and are treated with the same seriousness — a marker that is wrong is a security problem for the person being marked.
Still deciding?
The fastest way to judge a marker is to try to break it. Run a paper, answer it badly on purpose, and read the reason next to every mark.