Correxit Security Model¶
Correxit is a serverless, frontend-only JupyterLab extension. All logic runs
in the browser. There is no backend authority or trusted third party.
Cryptographic primitives use window.crypto and openpgp.js.
Threat Profile¶
- Student reads the reference cells:
secret reference encryption (
openpgp, AES-256). - Student reads answerable payload: answer payloads are SHA-256 digests.
- Student reads the roster:
roster encryption (
openpgp, AES-256). - Student alters authored assignment state: assignment MAC (keyed SHA-256).
- Student edits cells after submission: workbook locking and freezing.
- Peer reads answers from file: sealed submissions to the author key.
- Student tampers after submit: seal hash plus transport integrity.
- Student copies peer's sealed blobs: assignee and cell id are bound into the encrypted payload.
- Student starts from a forged blank slate:
issuedigest plusissuerPGP signature.
Out of scope: malicious authors, browser memory extraction, compromised JupyterLab servers, and preventing cross-student file access itself. Those are trust, host, or LMS access-control problems, not workbook-format problems. If a peer does obtain another student's file, Correxit still aims to protect the contents it actually encrypts or seals: secret references, the roster, and sealed submission sources.
Key Management¶
Plaintext keys are never written to notebook metadata, settings JSON, or
assignment files. They enter via user input or a configured Unlocker.
Rubric.Unlocked carries the PBKDF2 key; Rubric.Locked has key: null.
Notebook metadata stores only locked rubrics.
The shipped secrets-manager connector is in-memory. Deployments may configure
a different secrets connector or Unlocker; that is an intentional key custody
choice made by the deployment, not a hidden Correxit write path.
The author's PGP private key is stored in
assignment.keys.private.author, encrypted with the same
PBKDF2-derived symmetric key that encrypts the roster. unlock
recovers it into local scope, uses it, and discards it before
returning.
Settings Secrets¶
Provider-dispatched plugins may receive API tokens via the JupyterLab settings
editor. dispatcher.ts registers a compose transform that intercepts token
values, moves them to SecretsManager, blanks them from persisted JSON, and
re-injects them into composite settings on later loads.
Integrity¶
Rubric MAC¶
Authenticates the mutable author-controlled grading state:
rubric.cxtformat, rubric.id, rubric cells, rubric references, assignment
assignee, expiration, id, issue, issuer, keys
(author components only), name, overdue, penalty,
resources, report (interventions + scores, sorted), and
roster.
The Rubric.terms function extracts Keys.author(keys) to include only
{ private, public } for the author. Student key fields
(keys.private.assignee, keys.public.assignee) are absent from
Rubric.Terms, so they do not affect the HMAC. A student setting
their own keypair at submit time does not invalidate the MAC.
Any modification to authenticated fields invalidates the MAC.
This is different from Workbook.Identifier. Identifier is a small routing
key. The MAC proves that the broader authored assignment state still matches
the secret key held by the author side of Correxit.
Authenticated: rubric.cxtformat, rubric.id, cells, references,
assignee, expiration, id, issue, issuer, keys (author only), name,
overdue, penalty, resources, report, roster.
Not authenticated: certification, collected, distribution, seal,
submission, submitted. These change after signing or are set by the
student (who does not have the symmetric key).
Submission Time and Late Policy¶
Correxit can record two different kinds of submission evidence:
submission: a local timestamp written into the workbook when the student clicks submitsubmitted: an optional external receipt returned by aSubmitterplugin
Only the second can come from an authority outside the document.
The local submission field is intentionally not authenticated. In a
frontend-only system there is no trusted Correxit clock, and there is no
backend authority that can witness when the student clicked submit. This means
the local timestamp is useful workflow state, but not cryptographic proof of
when delivery happened.
That distinction matters for overdue handling:
- In backendless workflows, Correxit may still apply a local overdue policy to
the document being graded. This is a convenience policy for manual exchange
workflows such as git, email, or shared storage. Corrector may also choose
to accept only workbooks with a non-null local
submissiontimestamp. - In backend-backed workflows, the upstream system's deadline rules and receipt should be treated as authoritative. Correxit should not be described as the authority on timeliness in that mode.
Put differently: Correxit can protect workbook contents and preserve local submission state, but it cannot by itself prove that a submission was on time.
Blank-Slate Authenticity¶
Each propagated workbook carries two additional assignment fields:
issue: a deterministic digest of the issued blank-slate stateissuer: a cleartext PGP signature over that digest made with the author key
These fields stay stable after later author-side edits. They answer a different question from the MAC: whether the student started from the authentic issued workbook.
Seal Integrity¶
Sealed submissions use a separate integrity mechanism. At submit time, the
SHA-256 hash of all ciphertexts, sorted by cell id and joined with newline, is
stored as assignment.seal. At unlock time, Workbook.unlock recomputes the
hash from the current ciphertexts. A mismatch fails the workbook.
The seal hash is not signed. It cannot be: at submit time the workbook is
locked and the HMAC key is absent. The hash detects accidental corruption and
casual tampering. It does not stop a sophisticated student from re-encrypting
cells with the cleartext public key, recomputing the hash, and updating
assignment.seal. Strong post-submission integrity against deliberate file
modification depends on the Submitter plugin delivering an independent copy to
the LMS at submit time.
Transport Integrity (Plugin Responsibility)¶
The distributor does not return a receipt. Correxit records only the local
assignment.distribution timestamp after a successful delivery and save.
Blank-slate authenticity comes from issue and issuer, not from
distribution. If an LMS needs a delivery receipt, that record belongs to the
external system.
Encryption¶
Symmetric (AES-256 via openpgp, password-based)¶
- Reference cells:
comparableandcorrectablecells markedsecret: truehave their reference cells encrypted with the assignment key on lock. Students cannot read secret reference answers without the key. Cells markedsecret: falseleave reference cells visible. - Roster: Encrypted with the assignment key on lock. Students cannot enumerate the roster.
- Answerable cells: Store a SHA-256 digest of the expected output, not the output itself. One-way.
- Author PGP private key: Encrypted with the PBKDF2 key at
authoring time. Stored in
assignment.keys.private.author. - Student PGP private key: Encrypted with the student's own
PBKDF2 key (derived from their passphrase). Stored in
assignment.keys.private.assignee. Null for fire-and-forget submissions.
Asymmetric (PGP, Curve25519)¶
- Sealed submissions: At submit time, each rubric cell's source
is encrypted as
JSON.stringify({ assignee, id, source, type })usingsecurity.sealwith the author's PGP public key (and optionally the student's). The cell is converted torawtype witheditable: falseandsource_hidden: true.
At grading time, Workbook.unlock decrypts the author's PGP
private key, verifies the seal hash, and unseals each cell. Payload binding
prevents cross-student replay (assignee) and cell rearrangement (id).
Sealed Submissions¶
Lifecycle¶
-
Authoring (
Workbook.convert):security.keypair()generates a Curve25519 PGP keypair. The public key is stored in cleartext. The private key is encrypted with the PBKDF2 key before storage. -
Propagation (
propagator.ts): No changes. Keys travel in assignment metadata, copied to each student notebook. Thesealand student key fields start as null. -
Submit, fire-and-forget (
Workbook.submit): Each rubric cell is sealed to[keys.public.author]. Cells are sorted by id. The seal hash is computed and stored. The workbook is frozen. -
Submit, with passphrase (
commands.ts: submit): Student generates their own keypair. Their private key is encrypted with their PBKDF2 key. Cells are sealed to both[keys.public.author, keys.public.assignee]. -
Revise (
commands.ts: revise,Workbook.revise): Student enters a passphrase, derives a key, decrypts their PGP private key, unseals all cells, clearsseal,submission,submitted, and student key fields, then defrosts the notebook. -
Grading (
Workbook.unlock): Validates metadata first so a tampered workbook never gets plaintext written. Ifassignment.sealis non-null, it decrypts the author PGP private key in local scope, verifies the seal hash when all rubric cells are present, unseals each cell, and clearssealbecause the cells are now plaintext. Finally, it decrypts reference cells.
If rubric cells are missing from a sealed notebook, headless unlock and revise
hard-fail. In a live headed notebook, Correxit warns and unseals the cells that
remain so the secret holder can repair the file. That repair path is not seal
authentication and must not be used as grading or collection authority.
Workbook.recover remains the explicit forensic escape hatch for damaged
workbooks.
Payload Binding¶
Each sealed cell payload is { assignee, id, source, type }. At unseal time:
assigneemust matchassignment.assignee. Prevents Student B from copying Student A's ciphertexts.idmust match the cell id in the notebook. Prevents cell rearrangement after submission.typerestores the original cell type (e.g.code,markdown) fromraw.
Cross-assignment replay is not a concern: each rubric gets a fresh random PGP keypair at authoring time, so ciphertext from one assignment cannot be decrypted by another assignment's key.
Post-Grading Plaintext¶
Graded notebooks are saved with student answers decrypted. The seal protects the submission-to-grading corridor. After certification, the notebook is a feedback receipt, so students need plaintext to review scores and outputs. Re-encrypting after grading would lock fire-and-forget students out of their own feedback for no security gain.
PGP Nondeterminism¶
PGP encryption uses random session keys. Encrypting the same plaintext twice produces different ciphertext. The seal hash can only be verified by hashing the existing ciphertexts, never by re-encrypting and comparing.
Lifecycle¶
Assignment Fields¶
assignee:string, signed, student identifier.roster:string[], signed, encrypted on lock.expiration:number | null, signed, deadline.id:string | null, signed, external assignment id.keys:Keys, partially signed. Author keys are MACed, student keys are not.name:string, signed, assignment display name.overdue:'accept' | 'dock' | 'reject' | null, signed, local backendless overdue policy.penalty:number | null, signed, percentage deduction used by the localdockpolicy.report:Report, signed, scores and interventions.issue:string, signed, deterministic blank-slate digest.issuer:string, signed, author PGP signature overissue.seal:string | null, unsigned, SHA-256 of concatenated ciphertexts.mac:string, mutable rubric authenticity MAC.certification:number | null, unsigned, final grade timestamp.submission:number | null, unsigned, student submission timestamp written into the workbook.submitted:string | null, unsigned, external submission receipt from a submitter authority.distribution:number | null, unsigned, local distribution timestamp.collected:string | null, unsigned, external collection receipt.
Certification Sequence¶
correct()- execute cells, compute scores, sign report if all cells resolvecertify()- write certification timestamplock()- encrypt reference cells and roster, erase keyfreeze()- set cells to non-editable
A workbook cannot be collected without a non-null certification.
Corrector may display locked certified metadata while scanning, but it does not
trust that metadata for grade skipping or collection until the workbook has
been unlocked and authenticated with the rubric key.
Verification¶
Rubric.validate(rubric)- structural and authenticity checks: assignee must appear in roster, MAC must be valid if assignee or roster exists, issue and issuer must appear together and verify, author keys must be present, sealed assignments must have an author public key, assignee private key requires a corresponding public key.
Serialization Invariant¶
The required cxtformat discriminator identifies the persisted Correxit
metadata format. It is authenticated by both the assignment issue digest and
the rubric MAC. Missing and unknown formats are rejected before their contents
are interpreted.
All optional fields use Type | null, never Type?. This keeps
JSON.stringify deterministic: null is serialized, undefined is omitted.
Since MACs hash stringified JSON, field presence must stay stable.
Key Representation¶
The PBKDF2-derived key is a 256-bit value stored as a 64-character lowercase
hex string. hmac decodes this to 32 raw bytes before importing it as HMAC
key material. The same hex string is used as-is for the openpgp symmetric
password.
Assignee Key Lifecycle¶
Assignee key fields start as null from propagation and remain null unless the
student chooses "Set passphrase" at submit time. Rubric.unseal() clears them
back to null on revise. Fire-and-forget submissions therefore never offer a
revise option.
Rubric.assign() preserves keys when reassigning, but it only operates on
unlocked rubrics, where assignee key fields are always null. Stale student
keys therefore cannot leak across assignments.
Known Limitations¶
-
Client-side only. A technically sophisticated student could modify extension code in-browser to bypass locking or encryption. The cryptographic mechanisms detect tampering after the fact.
-
No separation of duties. The author holds the key and can perform any operation. There is no independent audit role.
-
Seal is unsigned. The seal hash is SHA-256, not HMAC. A student with file access and the cleartext public key could forge new ciphertexts and a matching hash. The seal detects corruption, not deliberate forgery. See Seal Integrity.
-
Grading executes student code. Batch grading runs submitted notebooks in Jupyter kernels. Use an isolated grading Jupyter environment for adversarial submissions.