NEC-93: Presence envelope: end-to-end encrypted, tagged by surface #144

Merged
nectenda-agent merged 4 commits from worktree-nec-93-presence-envelope-end-to-end-encrypted-ta into main 2026-09-24 22:53:53 +01:00
Collaborator

Task: NEC-93 — https://projectron.nerchure.com/tasks/93

Seals presence (Yjs awareness) end to end. Each non-null state is now an AES-GCM envelope {e,k} under the folder content key, with AAD binding domain label, docName, clientID and clock. Plaintext is padded to 512-byte multiples, removals stay plain, and a client refuses any state that is not sealed. The server needs no code change, because it never read a state's contents. Also adds presence format v1 in packages/shared (surface-tagged pointer, viewport and selection) and the optional additionalData on shared encrypt/decrypt. Docs: crypto.md section 12 (CRYPTO-110..115); security-model.md now lists presence as metadata only, and says it is not authenticated as live. Review changes the approved plan in one place: the padding moved from 256 to 512 bytes, because 256 was measured to leak editor focus (y-codemirror nulls the cursor on blur: about 310 bytes focused, 140 not). Mutation-checked: AAD, plaintext refusal, epoch guard, send ordering and padding each fail their test when broken. Unchaining the receive side fails nothing, because y-protocols' clock check already rejects stale states, and CRYPTO-114 says so rather than claiming otherwise. Found and not fixed here, because it predates this change: close-time retraction leaves remote clocks unchanged, so the QueryAwareness reply after a reconnect is rejected, and peers reappear only on their next renewal (up to ~18 s). multiplexed-provider.ts's onclose comment claims otherwise. Held for human review by risk.mjs (e2ee-constrained, crypto sources, security model), as intended.

Spec: docs/changes/NEC-93-presence-envelope/spec.md

Changelog

Presence (collaborators' names, colours and cursor positions) is now end-to-end encrypted, so the server can no longer see who is where in a note.

Task: NEC-93 — https://projectron.nerchure.com/tasks/93 Seals presence (Yjs awareness) end to end. Each non-null state is now an AES-GCM envelope {e,k} under the folder content key, with AAD binding domain label, docName, clientID and clock. Plaintext is padded to 512-byte multiples, removals stay plain, and a client refuses any state that is not sealed. The server needs no code change, because it never read a state's contents. Also adds presence format v1 in packages/shared (surface-tagged pointer, viewport and selection) and the optional additionalData on shared encrypt/decrypt. Docs: crypto.md section 12 (CRYPTO-110..115); security-model.md now lists presence as metadata only, and says it is not authenticated as live. Review changes the approved plan in one place: the padding moved from 256 to 512 bytes, because 256 was measured to leak editor focus (y-codemirror nulls the cursor on blur: about 310 bytes focused, 140 not). Mutation-checked: AAD, plaintext refusal, epoch guard, send ordering and padding each fail their test when broken. Unchaining the receive side fails nothing, because y-protocols' clock check already rejects stale states, and CRYPTO-114 says so rather than claiming otherwise. Found and not fixed here, because it predates this change: close-time retraction leaves remote clocks unchanged, so the QueryAwareness reply after a reconnect is rejected, and peers reappear only on their next renewal (up to ~18 s). multiplexed-provider.ts's onclose comment claims otherwise. Held for human review by risk.mjs (e2ee-constrained, crypto sources, security model), as intended. Spec: `docs/changes/NEC-93-presence-envelope/spec.md` ## Changelog Presence (collaborators' names, colours and cursor positions) is now end-to-end encrypted, so the server can no longer see who is where in a note.
Awareness states reached the server as readable JSON: names, colours and
caret positions. The server never needed to read them — it coalesces by
client id and clock, vouches by socket, and treats null as removal — so
each non-null state is now an AES-GCM envelope under the folder key,
bound by AAD to the note, client id and clock, and padded to 256-byte
multiples. A client refuses any state that is not sealed.

Presence format v1 (shared/presence.ts) tags pointer, viewport and
selection by surface so text, canvas and Excalidraw share one shape.

Task: NEC-93

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018AVesyLGq78y4Zgn9oE8Aw
The local state now carries v: 1 and null pointer/viewport/selection.
The server needed no change; a new test drives coalescing, QueryAwareness
vouching and close retraction with envelopes and checks the bytes relayed
are the sender's. The multi-vault presence e2e now also asserts the
server's own Awareness holds only {e, k}.

crypto.md gains section 12 (CRYPTO-110..115). security-model.md states
that presence contents are sealed and its rate is not.

Task: NEC-93

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018AVesyLGq78y4Zgn9oE8Aw
Task: NEC-93

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018AVesyLGq78y4Zgn9oE8Aw
Pad presence to 512-byte buckets, and stop overclaiming what the seal gives
All checks were successful
Release note / release-note (pull_request) Successful in 13s
CI / build (pull_request) Successful in 4m33s
CI / e2e (pull_request) Successful in 5m2s
CI / promote (pull_request) Has been skipped
e6e0ddc157
Review measured the 256-byte buckets leaking editor focus: y-codemirror
nulls the cursor on blur, and a real focused state (~310 bytes) and an
unfocused one (~140) sealed to different lengths. 512 holds a text state
with caret, pointer and viewport in one bucket; the padding test now uses
real relative positions and fails at 256.

The docs now also say what the seal does not give: presence is not
authenticated as live, so the server can withhold, retract or replay
states, though it cannot read, alter or relocate one.

Task: NEC-93

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018AVesyLGq78y4Zgn9oE8Aw
cruelacid force-pushed worktree-nec-93-presence-envelope-end-to-end-encrypted-ta from e6e0ddc157
All checks were successful
Release note / release-note (pull_request) Successful in 13s
CI / build (pull_request) Successful in 4m33s
CI / e2e (pull_request) Successful in 5m2s
CI / promote (pull_request) Has been skipped
to 9f9ed90570
All checks were successful
Release note / release-note (pull_request) Successful in 13s
CI / build (pull_request) Successful in 4m30s
CI / e2e (pull_request) Successful in 5m26s
CI / promote (pull_request) Has been skipped
2026-09-24 17:11:13 +01:00
Compare
cruelacid force-pushed worktree-nec-93-presence-envelope-end-to-end-encrypted-ta from 9f9ed90570
All checks were successful
Release note / release-note (pull_request) Successful in 13s
CI / build (pull_request) Successful in 4m30s
CI / e2e (pull_request) Successful in 5m26s
CI / promote (pull_request) Has been skipped
to fc272844ac
Some checks failed
Release note / release-note (pull_request) Successful in 16s
CI / build (pull_request) Successful in 5m5s
CI / e2e (pull_request) Successful in 5m8s
CI / promote (pull_request) Has been skipped
e2e-soak / soak (push) Failing after 24m22s
2026-09-24 22:22:11 +01:00
Compare
cruelacid force-pushed worktree-nec-93-presence-envelope-end-to-end-encrypted-ta from fc272844ac
Some checks failed
Release note / release-note (pull_request) Successful in 16s
CI / build (pull_request) Successful in 5m5s
CI / e2e (pull_request) Successful in 5m8s
CI / promote (pull_request) Has been skipped
e2e-soak / soak (push) Failing after 24m22s
to 541382b010
All checks were successful
Release note / release-note (pull_request) Successful in 13s
CI / build (pull_request) Successful in 4m37s
CI / e2e (pull_request) Successful in 4m49s
CI / promote (pull_request) Has been skipped
Deploy site / deploy (push) Successful in 49s
CI / e2e (push) Successful in 4m50s
CI / build (push) Successful in 4m58s
CI / promote (push) Successful in 31s
2026-09-24 22:47:52 +01:00
Compare
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Nectenda/nectenda!144
No description provided.