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 and authenticated issued contents (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 symmetric 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 Unlocker also acquires credentials for creation, submission, revision,
and forensic recovery. A hosted provider can supply generated keys directly,
without user passphrases. Author keys and student revision keys have separate
purposes: student credentials must never unlock the rubric or enter the author
cache. Hosted stores must authorize access independently of notebook-supplied
identifiers and scope student credentials by rubric and assignee. Creation
awaits author-key storage; a hosted provider must persist student credentials
before allowing submission to proceed.
The author's PGP private key is stored in
assignment.keys.private.author, encrypted with the same
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, format-2
rubric.contents, 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,
format-2 contents,
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).
Issued Contents¶
Format 2 records an ordered contents array when issuing a workbook. Each
entry contains { id, type, digest }. The digest is null for a rubric cell,
whose source is the student's answer. For every other issued cell, including
visible references, secret references, instructions, and setup code, it is:
Secret references are represented by their decrypted source and code type.
Re-encryption therefore does not change their commitment. Keyed commitments
avoid exposing plaintext hashes of easily guessed secret sources. The entire
array is authenticated by the rubric MAC and included in the issue digest.
Unissued author templates use contents: null.
Issuance and authentication require unique, non-empty string IDs on every notebook cell, including added working cells. Invalid IDs are rejected before building or checking the manifest; they are never coerced into identities.
Rubric cells, references, scores, and interventions use own entries when
looking up IDs. Graded cells and references reject __proto__ because
JupyterLab's metadata copier cannot preserve that entry.
Grading and collection require the issued cells to retain their identities, types, relative order, and fixed sources. Students may edit rubric cell sources and add working cells; added cells are not authenticated instructor content. Missing or duplicated issued cells, substituted ciphertexts, altered visible references, and changed setup code fail authentication.
Unlock prepares all decrypted replacements, authenticates the prospective plaintext notebook, and only then writes them. An assigned workbook cannot silently repair away missing cells or references. Forensic recovery is explicit and does not confer grading authority.
Symmetric decryption rejects malformed PGP messages. A plaintext reference is accepted only when it matches its authenticated commitment, supporting an author-side save of an unlocked workbook without accepting substituted tests.
Grade Provenance¶
Parsing metadata is not authentication. Scanning reads notebook identity and workflow metadata without acquiring credentials, and cannot produce trusted scores. Batch grading authenticates the rubric and issued contents before either executing cells or reusing a stored report, including reports awaiting manual review. Certification alone is an unsigned workflow marker and cannot skip an ungraded submission.
Student and unassigned author previews can return unverified scores for local feedback. They cannot supply certified grades, CSV scores, or collection.
Workbook.Grade discriminates in-memory results with verified: true | false.
Workbook.Certified requires a verified grade. This provenance is never read
from notebook metadata. CSV exports use only verified command results; scanned
reports and failed authentication produce blank points, possible points,
certification, and collection fields, with resolved=false. To export existing
certified scores, run batch grading first; authenticated cached reports do not
re-execute the notebook. Collection independently authenticates before calling
the configured collector.
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. The distribution path can recompute the whole blank-slate digest before handing it to a student. Grading cannot recompute that whole digest after answers change. It verifies the rubric MAC and the separate format-2 contents commitments instead; checking the stored issue signature alone is not proof of current reference sources.
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 author key at
authoring time. Stored in
assignment.keys.private.author. - Student PGP private key: Encrypted with the student's own
key (derived from their passphrase or supplied by the
Unlocker). Stored inassignment.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 supplied or derived author 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 revision access (
commands.ts: submit): TheUnlockersupplies student credentials, prompting for a passphrase by default. Correxit generates a student keypair and encrypts its private key with the supplied or derived key. Cells are sealed to both[keys.public.author, keys.public.assignee]. -
Revise (
commands.ts: revise,Workbook.revise): TheUnlockeracquires student credentials to decrypt their PGP private key, unseals all cells, clearsseal,submission,submitted, and student key fields, then defrosts the notebook. -
Grading (
Workbook.unlock): Authenticates metadata first. Ifassignment.sealis non-null, it decrypts the author PGP private key in local scope, verifies the seal hash, and prepares unsealed rubric cells. It also prepares decrypted references and authenticates the complete prospective contents before writing any replacement. Only then issealcleared.
Missing issued cells or references fail assigned format-2 unlock in both live
and headless notebooks. Workbook.recover remains the explicit forensic escape
hatch for damaged workbooks. Student revision can still recover remaining cells
in a live notebook, but that repair is not grading or collection authority.
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 can scan locked metadata, but scores require authenticated command
results. Grade skipping, score export, and collection never trust a certification
timestamp or stored report merely because it was parsed successfully.
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.
Format 2 is the only supported format. Format 1 was a draft with no usage in the wild and is superseded. Its authenticated terms did not bind issued reference sources, so it cannot supply the current security guarantees. Readers reject it rather than infer missing commitments from notebook contents. There is no automatic migration or legacy reader. The bundled example uses format 2. Package and workbook-format versions are independent.
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 supplied or 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
Unlocker provides student credentials at submit time (the default provider's
"Set passphrase" choice). 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 and trusted kernel specifications. Source authentication does not sandbox student code or guarantee honest execution in a shared kernel. Additional working cells, outputs, cell metadata, kernel selection, and supporting resource-file contents are outside the issued-source commitment.