You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Announce does not bind a DID to its first-seen key, so any caller can rewrite any peer's http_url #273
upsert_peer has no notion of who owns a DID's row. Any caller can rewrite any peer's http_url, because the conflict branch is an unconditional update and the announce route accepts unsigned callers by default. #270 proposes resetting last_ping_ok when the URL changes, which limits the blast radius on one consumer. This issue is the underlying question: should a DID be bound to the key that first announced it, so the rewrite is rejected rather than merely defanged?
Split out of #270 so the contained mitigation there can land without waiting on this decision.
Why the mitigation is not sufficient on its own
The reset-on-change fix keys on last_ping_ok, but three of the four consumers of http_url never read that flag:
sync.rs:204 resolves a queued sync item's origin URL by DID and hands it to clone_repo/fetch_repo as the git remote. No reachability filter.
api/repos.rs:1350 fans out the post-receive announce to every peer with a non-empty http_url. No reachability filter.
api/resolve.rs:24 serves GET /api/v1/resolve/{did} from the peer table and returns the stored URL as the answer for that DID. No reachability filter, and the route is public (server.rs:381).
So a repointed row keeps steering the sync worker, the notify fan-out, and public DID resolution regardless of what the flag says.
The decision
Options as I see them:
First-writer-wins on the DID. Record the announcing key on insert; later announces for that DID must carry a signature from the same key. Closes the rewrite outright, works whether or not require_signed_peer_writes is on, and does not depend on any downstream consumer reading a flag. Cost is a migration plus a story for peers whose rows predate the column, and a documented recovery path for a genuine key rotation.
Flip require_signed_peer_writes to true by default. Simpler, but it only requires a valid signature matching the announced DID, which is what an attacker announcing their own DID already has. It closes the hijack of someone else's row and nothing else, and it is a breaking change for peers that have not upgraded, which is why it is off today.
My read is that 1 is the actual fix and 3 is a stopgap worth landing first, but this is a federation-model call rather than a bug with an obvious patch, which is why it is its own issue.
upsert_peerhas no notion of who owns a DID's row. Any caller can rewrite any peer'shttp_url, because the conflict branch is an unconditional update and the announce route accepts unsigned callers by default. #270 proposes resettinglast_ping_okwhen the URL changes, which limits the blast radius on one consumer. This issue is the underlying question: should a DID be bound to the key that first announced it, so the rewrite is rejected rather than merely defanged?Split out of #270 so the contained mitigation there can land without waiting on this decision.
Why the mitigation is not sufficient on its own
The reset-on-change fix keys on
last_ping_ok, but three of the four consumers ofhttp_urlnever read that flag:sync.rs:204resolves a queued sync item's origin URL by DID and hands it toclone_repo/fetch_repoas the git remote. No reachability filter.api/repos.rs:1350fans out the post-receive announce to every peer with a non-emptyhttp_url. No reachability filter.api/resolve.rs:24servesGET /api/v1/resolve/{did}from the peer table and returns the stored URL as the answer for that DID. No reachability filter, and the route is public (server.rs:381).api/repos.rs:1451is the only one that filters onlast_ping_ok, and it is the one Unsigned announce repoints a peer's http_url and inherits its reachable=true federation gate #270's fix addresses.So a repointed row keeps steering the sync worker, the notify fan-out, and public DID resolution regardless of what the flag says.
The decision
Options as I see them:
require_signed_peer_writesis on, and does not depend on any downstream consumer reading a flag. Cost is a migration plus a story for peers whose rows predate the column, and a documented recovery path for a genuine key rotation.require_signed_peer_writesto true by default. Simpler, but it only requires a valid signature matching the announced DID, which is what an attacker announcing their own DID already has. It closes the hijack of someone else's row and nothing else, and it is a breaking change for peers that have not upgraded, which is why it is off today.My read is that 1 is the actual fix and 3 is a stopgap worth landing first, but this is a federation-model call rather than a bug with an obvious patch, which is why it is its own issue.
Related
reposlug on the same unsigned peer-write route.