fix(share): stop leaking content-derived turn ids in published snapshots#394
Merged
Conversation
Turn ids from ensureTurnIds are fnv1a32 content hashes; emitting them in turns[].id / turn_order / hidden_turns let any reader of the public snapshot JSON fingerprint content and enumerate which turns the author excluded. Remap ids to opaque positional tokens before upload. Also: external links in rendered markdown now open in a new tab with rel="noreferrer noopener", and decodeSnapshot warns (best-effort decode) on an unknown future schema_version instead of trusting it silently. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Remap every turn id to an opaque positional token (`t0`, `t1`, …) before a snapshot is uploaded, so neither `turns[].id`, `turn_order`, nor `hidden_turns` carry content-derived values. Also: external links in rendered markdown now open in a new tab with `rel="noreferrer noopener"`, and the reader's `decodeSnapshot` best-effort decodes (and warns) on an unknown `schema_version` instead of trusting it silently.
Why
Turn ids came from `ensureTurnIds`, an fnv1a32 hash of the turn body. Emitting them on the public snapshot let any reader of `/api/snapshots/` fingerprint content (reversible for known content classes) and enumerate exactly which/how many turns the author excluded — contradicting the blank-body guarantee for hidden turns. The markdown links lacked `rel`/`target`, allowing top-frame navigation and a window.opener handle.
How it connects
The reader only uses ids for ordering and hidden-lookup, never for content, so opaque positional ids are fully compatible. Adds a regression test that fails on the pre-fix code (asserts no content-hash id appears in the emitted snapshot) plus link-hardening and unknown-version tests.