Skip to content

forge/: mirror binds the trail to the followed key, checks reachability, drops replays, hides refs/mirror - #15

Open
jjohare wants to merge 1 commit into
JavaScriptSolidServer:gh-pagesfrom
jjohare:mirror-trail-key-reachability-replay
Open

jjohare wants to merge 1 commit into
JavaScriptSolidServer:gh-pagesfrom
jjohare:mirror-trail-key-reachability-replay

Conversation

@jjohare

@jjohare jjohare commented Oct 19, 2026

Copy link
Copy Markdown

What

Four hardening fixes to the opt-in kind-30618 mirror (40efb47, bd55eda, 80a83d9). The mirror is off unless config.mirror is set, so default installs are unaffected. These came out of porting the mirror to Rust (solid-pod-rs, solid-pod-rs-forge) and having it reviewed independently.

  1. Trail not bound to the followed key. trailLists checks profile and states only. A blocktrails.json copied from any other repository, listing the marked commit, satisfies the check. The trail's pubkeyBase (02/03 + x, or bare x) must now equal the followed agent's key (trailBelongsTo).
  2. Marked commit not required to be reachable. After the fetch, git merge-base --is-ancestor <mark.commit> <sha> must hold, so the mark is the moved-to commit or in its history.
  3. Older signed state can roll branches back. If a newer state is held by the rate limit, a relay can replay an older validly signed one and update-ref force-moves to it. The record now keeps newestSeenAt; older states are dropped, an equal one (the catch-up resend) is kept.
  4. Garbled mark reads as "no mark". A state with a partial or malformed chain/txo/commit set returned mark: null, which moves the branch with no trail check. readState now returns null for it.

Also: refs/mirror/ is added to uploadpack.hideRefs. The fetch lands every branch there before any verdict, so a branch the mirror held back was still fetchable by name from the served repo. The verdict text no longer reads as chain verification (the chain is not consulted; the stance that the browser checks it is unchanged).

Not in this PR

Residual items left alone: DNS rebinding between the host check and git's own resolution, no fetch size cap beyond the 60 s timeout, and apply being awaited inside the socket loop.

Tests

node --test --test-concurrency=1 forge/test.js: 150 pass (was 149). Two existing tests are updated for the stricter rules (the verdict test now supplies a key; the replay test expects the new reason). Added: partial/garbled marks read as null, foreign trail refused, unreachable mark refused, trailBelongsTo cases.

…rk is reachable, drops replays and hides refs/mirror

An independent read of the opt-in kind-30618 mirror found four gaps:

- trailLists only checked profile and states, so a blocktrails.json copied from
  any other repository and listing the marked commit passed. The trail's
  pubkeyBase must now carry the followed agent's x-only key.
- the marked commit was never required to be the moved-to commit or in its
  history; merge-base --is-ancestor now checks it after the fetch.
- a validly signed older state could roll branches back when a newer one had
  been held by the rate limit. The record keeps newestSeenAt and drops anything
  older; an equal one (the catch-up resend) is kept.
- a state with a partial or garbled chain/txo/commit read as "no mark", which
  switches the trail check off. It now reads as no state.

Also: refs/mirror/ joins uploadpack.hideRefs, because the fetch lands every
branch there before any verdict and a held branch was still fetchable by name.
The verdict text no longer implies chain verification. Two existing tests are
updated for the stricter rules; four cases added. 150 forge tests pass.

Co-Authored-By: jjohare <github@thedreamlab.uk>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants