Deferred non-blocking review findings from PR #5 (compat/spaces-alpha-20261022, reviewed at commit df41959). Medium-and-above findings were fixed in the delivery; these are recorded as follow-up work. Labels housekeeping + branch-followup requested (atgc has no label flag — please apply in the UI).
src/sync/spaces/response-verification.ts @ df41959 — F1 (adversarial-review, non-blocking): on the verified listRepoOps path the commit MAC binds only commit.data (the aggregate), not the individual op values on a page; non-head pages are authenticated by TLS alone.
Why it matters: an on-path tamperer can alter op values on non-head pages of the bulk document-sync stream undetected; the PR's "verified credentialed reads" claim is stronger than what the op path enforces. The PR notes declare the filtered application projection intentional (full verification applies to STAR recovery only), so this is deferred rather than fixed.
Suggested: fold verified op CIDs into a running RepoCommit and compare it against the final page's commit.data.
Source: https://tangled.org/scarnecchia.net/ziran/pulls/5 — review round 1. (2026-10-24)
src/sync/spaces/client.ts (listRecords, listRepos) @ df41959 — F2 (adversarial-review, non-blocking): these credentialed reads send no want-mac and verify nothing; a forged invite record surfaces in the UI, a forged role label can set the local user's doc role in adopt (src/sync/spaces/provider.ts:799), and a forged head.rev equal to the stored cursor suppresses polling for that repo (src/sync/spaces/provider.ts:396). Tests pin the unverified behavior as intended today.
Why it matters: UI state and polling decisions rest on cryptographically unverified server responses.
Suggested: request and verify MACs on these endpoints, or validate repo heads out-of-band before suppressing polls.
Source: https://tangled.org/scarnecchia.net/ziran/pulls/5 — review round 1. (2026-10-24)
deno.lock:12 @ df41959 — F3 (adversarial-review, non-blocking): orphan spec npm:@atproto/crypto@* resolves to stable 0.5.7, hoisted at top-level node_modules/@atproto/crypto alongside the Spaces alpha build; nothing in-tree requests it (lock graph scan), and src/sync/spaces/dependency-resolution.test.ts never asserts the absence of non-alpha @atproto/* versions.
Why it matters: split-brain hazard if future code resolves the bare specifier; the resolution test is blind to exactly this drift.
Suggested: drop the orphan spec and add a dependency-resolution assertion that every resolved @atproto/* version is the pinned Spaces alpha.
Source: https://tangled.org/scarnecchia.net/ziran/pulls/5 — review round 1. (2026-10-24)
src/sync/spaces/fake.ts (fromBytesJson) @ df41959 — F4 (adversarial-review, non-blocking): atob throws a raw InvalidCharacterError on hostile base64 whose length % 4 == 1, instead of a clear domain error.
Why it matters: corrupt or hostile server input surfaces as an opaque internal error.
Suggested: wrap the decode and rethrow with context naming the malformed field.
Source: https://tangled.org/scarnecchia.net/ziran/pulls/5 — review round 1. (2026-10-24)
src/sync/spaces/provider.ts (#pdsCache) @ df41959 — F5 (adversarial-review, non-blocking): the DID→PDS cache never expires, while deploy/pds/README.md documents PDS migrations as a supported operation.
Why it matters: after a migration a long-running client keeps writing to the old PDS until restart.
Suggested: give the cache a TTL or entry bound, and invalidate on auth failures that indicate a moved repo.
Source: https://tangled.org/scarnecchia.net/ziran/pulls/5 — review round 1. (2026-10-24)
Round-2 review amendments (same PR, fixes now in commit c5ee7cc + the follow-up hardening commit):
F1 amended — src/sync/spaces/response-verification.ts @ c5ee7cc (adversarial-review round 2, Blocker escalation): op payloads are not bound to the signed commit on any page —
computeCommitMacHMACs only space‖repo‖rev‖commit.data, so (a) a commit-less page with any nonempty cursor passes as "intermediate" and (b) head-page ops can be edited while commit+MAC stay valid. Forged revs can poisonprocessedRevs/repoCursorsso future real ops are silently dropped (durable damage via src/sync/spaces/provider.ts #processRepoOps). Partial mitigation shipped: commit-less pages fail closed on missing/empty cursor (response-verification.ts),allRepoOpspagination is capped at 1000 pages (client.tsMAX_REPO_OPS_PAGES), and the repo cursor now advances on the MAC-verified head rev instead of raw op revs (provider.ts #pollSpace). Remaining core (needs a wire-shape/SDK decision, possibly upstream): fold verified op CIDs into a running RepoCommit and comparematches(commit.data)(pattern per @atproto/space repo-commit.ts) on full replays; the in-repo fake's RepoOp omits cid/prev so the real wire shape is unverified. (2026-10-24)F6 — src/sync/spaces/client.ts:206-208,377-385 @ c5ee7cc (adversarial-review round 2, Low, non-blocking): a content-type downgrade on listRecords/listRepos yields silent empty results instead of an error. Why it matters: a broken or hostile server looks like "no records" rather than failing loudly. Suggested: throw on an unexpected content type for JSON endpoints. Source: https://tangled.org/scarnecchia.net/ziran/pulls/5 — review round 2. (2026-10-24)
F7 — src/sync/spaces/client.test.ts:187-203 @ c5ee7cc (adversarial-review round 2, Nit, non-blocking): getRecord tamper tests rebuild responses without the original MAC header, so they reject via missing-header rather than a wrong-MAC mismatch; only badRecordMacFetch and the getRepo tests pin body-vs-MAC binding. Why it matters: the tamper path those tests claim to pin is not the one exercised. Suggested: preserve the original header and corrupt the MAC value instead. Source: https://tangled.org/scarnecchia.net/ziran/pulls/5 — review round 2. (2026-10-24)
F8 — src/sync/spaces/response-verification.ts:86 @ c5ee7cc (adversarial-review round 2, Nit, non-blocking): on commit-less pages
parseMacHeaderruns as a shape check whose result is discarded. Why it matters: reader may mistake it for verification; it only rejects malformed headers. Suggested: rename intent via comment orvoid-binding, or drop it. Source: https://tangled.org/scarnecchia.net/ziran/pulls/5 — review round 2. (2026-10-24)