Whitepaper · Updated 2026-06-29 (v3: adds the stateless notification-send path + honest trust-model framing for the non-SSO branch)

Student Privacy Architecture

This document describes how Prism stores, encrypts, and controls access to student information. It is written for school principals and district counsel evaluating Prism against FERPA, state student-privacy statutes, and their own district policies. The first section is in plain English. The technical detail follows for security review.

The one-paragraph version

If your district uses Google Workspace for Education, Microsoft 365 Education, or another OIDC IdP, Prism federates sign-in through it. We never receive or store student emails. We see only the opaque subject ID your IdP issues. We structurally cannot mishandle data we don't have. Student names are then encrypted on the teacher's device with a key derived from the teacher's password; Prism cannot decrypt them. All other data (quiz scores, mastery estimates, P-Scores) is keyed by an opaque per-class ID. If our database were published in full tomorrow, it would contain no attributable student record.

What the database actually contains

Prism stores three categories of student-related data. They live in different places, with different protections:

1. Pseudonymous analytics (the most common case)

Every student in a class gets an opaque per-class identifier like stu_xb7q3m1lo9k4. That string is what our servers know the student as. Quiz scores, mastery estimates, page-view counts, the P-Score, every assignment grade: they all reference the pseudonym. There is no student name in any of these tables. A different class gets a different pseudonym for the same human; you cannot cross-correlate by ID.

2. The teacher's encrypted name vault

The mapping from pseudonym to real name lives in a vault encrypted with a key derived from the teacher's password. The encryption happens in the teacher's browser before anything reaches our servers. Prism stores the resulting ciphertext, but doesn't store the password, can't derive the password, and can't decrypt the vault. The teacher unlocks it in their browser when they sign in.

3. Auth-system identity (the student's own account)

How the student signs in depends on your district:

  • SSO-federated districts: the student signs in through your IdP. Prism receives an authentication token, and a trigger immediately replaces the IdP-provided email with an opaque synthetic address. Our application-level profiles.email column is set to NULL. We have no readable identifier for the student anywhere in our database.
  • Non-SSO districts:the student signs in with email and password. The plaintext email is hashed at sign-up and the readable form is encrypted in the user's vault.

What this means under specific scenarios

If Prism receives a subpoena for student data

We can produce pseudonymous analytics. We cannot produce student names. We don't have them in a form we can decrypt. For SSO-federated students, we cannot produce emails either; the auth schema stores an opaque synthetic value. A subpoena could compel us to hand over the encrypted vault, but the vault is unreadable without the teacher's password, which we don't have. Compelling that password from the teacher is a separate legal matter that doesn't involve Prism.

If Prism's database is breached

The attacker gets the same thing a subpoena would: opaque analytics + encrypted vaults. For SSO-federated students, even the email column is opaque. Decrypting the vault requires a successful brute force against the teacher's password through Argon2id at 64 MiB / 3 iterations. At the speed of common GPUs, that's tens of millions of dollars in compute per teacher for a password of decent length. The economics defeat any large-scale breach.

If a Prism employee goes rogue

An employee with database administrator access can see the analytics, the ciphertext, the salts. They cannot see names. The architecture removes the "trust us" promise; the math does it for us.

If a teacher forgets their password

The vault is lost. The teacher re-enrolls students by name; previous analytics history (P-Scores, mastery estimates) carries forward unchanged because it's keyed by pseudonym, not by name. This is intentional. The only way we can truthfully claim Prism cannot decrypt the vault is to not have a recovery key. We surface this explicitly when the teacher creates the vault so there are no surprises.

If your district leaves Prism

We delete the encrypted vault. Once deleted, the link between pseudonyms and names is permanently severed. Even if we kept the analytics, no future engineer could re-attach a name to any row. This satisfies FERPA's deletion requirements more strongly than a normal "delete the row" pattern would, because the deletion is cryptographically irreversible rather than just procedural.

Technical architecture

For security teams that want to verify our claims, this is the exact mechanism.

Identity layer

Two sign-in paths exist depending on whether your district has an identity provider:

SSO federation (preferred). If the district uses Google Workspace for Education, Microsoft Entra, or any OIDC-compliant IdP, students sign in through it. Supabase Auth receives an authentication token from the IdP, and a trigger immediately replaces the IdP-provided email address with an opaque synthetic value (stu_<hash>@no-pii.prism.invalid, using the unroutable .invalid TLD). The application-level profiles.email column is set to NULL. Result: no readable student email exists in either the public schema or the auth schema. The IdP still has the email; your district's directory still has the email; Prism does not.

Hashed identity (for districts without an IdP). When students sign in with email and password, the browser hashes the email (SHA-256 with a public per-deployment salt) and uses hash_<hex>@no-pii.prism.invalid as the address Supabase Auth sees. The real email is encrypted with RSA-OAEP-3072 under a Prism-held public key and stored on user_vault_meta.notification_email_ciphertext. The matching private key lives in the server's secrets manager (not the database). Notification delivery uses a stateless send path: the server fetches the ciphertext, decrypts in-process, hands the plaintext recipient + body to the SMTP vendor in a single call, and returns without logging the address. No persistent queue ever holds it.

Honest trust model for the non-SSO branch: a database compromise alone yields ciphertext only. A simultaneous compromise of the database AND the env-var private key (a rogue Prism employee with both, for example) yields the readable email. This is weaker than the SSO path's structural "we don't have the data" guarantee. We recommend SSO for districts where it's available. We support the non-SSO path because the alternative (plaintext email in two tables) is strictly worse.

Teachers are treated as standard auth-system users: teacher emails are stored in cleartext, same as any SaaS. Teachers are consenting adults; FERPA addresses the protected class, students.

Key derivation

When a teacher creates a vault, their password is run through Argon2id with parameters: m = 65536 KiB (64 MiB), t = 3, p = 1, output length 32 bytes, with a random 16-byte salt unique to that teacher. These parameters meet OWASP's 2023 recommendations for password hashing. The output is the teacher's master key. It is held in browser memory for the session and never persisted.

Class encryption key (CEK)

Each class has a random 256-bit AES-GCM key generated in the teacher's browser at first vault setup. This CEK is wrapped using AES-KW under the teacher's master key, and the wrapped form is stored in the database. The unwrapped CEK never touches the network.

Field encryption

Student display names and emails are encrypted with the CEK using AES-256-GCM. Each row gets a fresh 12-byte IV. The ciphertext, IV, and pseudonym are stored together; the plaintext is generated on demand inside the teacher's browser when they open a roster view.

Where the keys live

  • Password: entered by the teacher, never transmitted, never stored.
  • Master key: derived in-browser, kept in a React ref, gone on tab close or sign-out.
  • CEK: unwrapped in-browser when the teacher opens a class, kept in a memoized cache for the session, gone when the master key is gone.
  • Encrypted vault:stored in our Postgres database, protected by row-level security so only the class teacher's session can read it.

Database surfaces

Four tables make up the vault layer; everything else in the database is pseudonymous and therefore irrelevant to the PII story:

  • user_vault_meta: per-user Argon2 salt, a verifier blob used to check "was the password right" without revealing anything, plus the user's ECDH public key (for messaging) and their wrapped ECDH private key.
  • class_vault_keys: per-class wrapped CEK. Readable only by the class teacher under row-level security.
  • student_pseudonyms: per-(class, student) opaque ID plus encrypted display name, encrypted email, and the IV.
  • message_thread_keys: one row per (thread, participant) holding that participant's wrapped copy of the thread CEK, plus the sender's public ECDH JWK so the recipient can pair it with their own private to unwrap.

Every analytics table (quiz attempts, mastery estimates, lesson progress, P-Score components) joins only to student_pseudonyms.pseudonym, never to a profile name. The single function that materializes names is public.teacher_student_display_map(_class_id) and it is gated by public.is_class_teacher(); any other code path that wants names has to call this function, which gives auditors one spot to check.

End-to-end encrypted messaging

Messages between teachers, students, and parents are encrypted with the same primitives but a different key topology. Each user's vault holds an ECDH P-256 keypair: the public half is published in the database, the private half is wrapped under their master key.

At thread creation, the sender generates a fresh 256-bit AES-GCM Class-Encryption-Key (CEK), then wraps it once per recipient using an AES-KW key derived from ECDH(sender_private, recipient_public). The wrapped copies, one per participant, are stored in message_thread_keys. The recipient mirrors the derivation with their own private key + the sender's public to unwrap the CEK in their browser.

Each message body is encrypted under the thread CEK with AES-256-GCM and a fresh 12-byte IV. Prism's servers can route a message (thread id, sender id, recipient ids) but never see plaintext content.

FERPA mapping

FERPA's 34 CFR § 99.3 enumerates personally identifiable information. The table below maps each category to where it lives in Prism.

  • Student's name: only stored in the teacher's encrypted vault and the student's own auth account. Never present in analytics tables.
  • Student's email address: for SSO-federated students, scrubbed at sign-up to an opaque synthetic value; the real email never reaches our database. For non-federated students, the plaintext email is hashed at sign-up and the readable form is encrypted in the user's vault.
  • Parent or family member name: same treatment as student name; encrypted in the vault.
  • Personal identifier (SSN, student number): Prism does not collect these.
  • Indirect identifiers (date of birth, place of birth, mother's maiden name): Prism does not collect these.
  • Other information that, alone or in combination, is linked or linkable: quiz scores, mastery estimates, etc. are pseudonymously stored; without the teacher's vault, they cannot be linked to a specific student.
  • Information requested by a person who Prism reasonably believes knows the identity of the student: Prism does not have an identity to confirm.

What this architecture does not protect against

We'd rather be specific about the limits than over- claim:

  • A compromised teacher device.If a teacher's laptop is stolen while the browser session is unlocked, the attacker has the same access the teacher had. We can mitigate (short session timeouts, vault re-lock on idle) but not eliminate.
  • A weak teacher password. Argon2id buys time, not invincibility. A 6-character common password can be brute-forced. Strong passwords are the last line of defense.
  • The student's own account.A student's own name and email are visible to the student. That hasn't changed; that's how sign-in works.
  • Misuse by the teacher.A teacher who shares their students' names with an unauthorized third party has the data they need to do so. That's a school-policy issue, not an architectural one.
  • Forward secrecy.Teacher↔student messaging is end-to-end encrypted (see below), but we don't run a Signal-style key ratchet. A compromised master key reveals past messages on that user's threads. We accept this trade so parents (who may not have accounts) can participate in encrypted threads with one static keypair per user.
  • Your identity provider.If your district uses SSO, the IdP (Google, Microsoft, etc.) still has the student's email; that's how they authenticate. Prism doesn't see it, but it exists in the IdP's logs. This is the same trust boundary every SSO-using service has and is generally considered an improvement over Prism holding the email directly, because the IdP is something the district already controls and audits.
  • The SMTP vendor.For non-SSO students, Prism's notification flow has to put the plaintext recipient on the wire to a vendor (Resend or Postmark in our current configuration) so the email can be delivered. The vendor sees the recipient and the message body. We chose vendors with strict log policies but cannot make this trust boundary disappear. Our server doesn't persist the recipient itself: the stateless send path decrypts in-process, hands off to the vendor in a single call, and returns without logging. SSO students don't receive Prism-originated email at all and so don't cross this boundary.

Questions and contact

Procurement officers and district counsel can reach us at art@onprism.org. For pricing inquiries see our pricing page; for the related legal documents see our Terms, Privacy Policy, and AI disclosure.