An unauthenticated caller can repoint an existing, currently-reachable peer's http_url to a host they control and inherit that peer's reachable=true federation gate, with no probe in between.
Mechanism
upsert_peer (crates/gitlawb-node/src/db/mod.rs:2093-2095):
INSERT INTO peers (did, http_url, last_seen, last_ping_ok, announced_at)
VALUES ($1, $2, $3, FALSE, $3)
ON CONFLICT(did) DO UPDATE SET http_url = $2, last_seen = $3
The conflict branch rewrites http_url and leaves last_ping_ok alone. Reaching it needs no signature: require_signed_peer_writes defaults false (config.rs:48), and the announce handler's unsigned branch only logs a warning (api/peers.rs:196-201). There is no lookup of the existing row, no stored-key check, and no first-writer-wins rule, so one party can rewrite another's row.
Verified by execution
A throwaway #[sqlx::test] seeded a peer with last_ping_ok = TRUE and http_url = https://honest-peer.example.com, then drove the mounted announce handler with no auth extension at all and a body naming the same DID with http_url = https://attacker.example.com. Result: 2xx, the URL was rewritten, and last_ping_ok was still TRUE.
Why it matters
api/repos.rs:1451 filters the federated fan-out on that flag, and the response handling at api/repos.rs:1464-1476 takes the peer's JSON body verbatim and stamps each entry with node_did set to the hijacked peer's DID. No signature check, no content addressing. So the attacker injects arbitrary repo entries attributed to a legitimate peer.
Bound on the attack
A new DID inserts with last_ping_ok FALSE (db/mod.rs:2094) and stays out of the fan-out until a gossip round probes it. So the instant, probe-free version requires hijacking an existing reachable DID. An attacker announcing their own DID with their own live host still enters the fan-out, just after a probe and without stolen attribution.
Fix direction
Reset the gate whenever the URL actually changes, so a repointed peer has to re-earn reachability:
ON CONFLICT(did) DO UPDATE SET
http_url = $2,
last_seen = $3,
last_ping_ok = CASE WHEN peers.http_url IS DISTINCT FROM $2 THEN FALSE ELSE peers.last_ping_ok END
That leaves a plain liveness re-announce alone while closing the carry-over on a swap. It is a direction, not something I have compiled and run; the deeper question is whether announce should bind a DID to its first-seen key regardless of the signing default, which would close the rewrite rather than just its blast radius.
Scope
Pre-existing and untouched by #248: git diff 111cff7e...32e45787 does not include db/mod.rs, and the peers.rs hunks touch only ping_peer and tests. #248 does widen the window slightly in passing, since its two-consecutive-failure hysteresis means a hijacked-then-dead URL holds the gate for two gossip rounds instead of one.
An unauthenticated caller can repoint an existing, currently-reachable peer's
http_urlto a host they control and inherit that peer'sreachable=truefederation gate, with no probe in between.Mechanism
upsert_peer(crates/gitlawb-node/src/db/mod.rs:2093-2095):The conflict branch rewrites
http_urland leaveslast_ping_okalone. Reaching it needs no signature:require_signed_peer_writesdefaults false (config.rs:48), and the announce handler's unsigned branch only logs a warning (api/peers.rs:196-201). There is no lookup of the existing row, no stored-key check, and no first-writer-wins rule, so one party can rewrite another's row.Verified by execution
A throwaway
#[sqlx::test]seeded a peer withlast_ping_ok = TRUEandhttp_url = https://honest-peer.example.com, then drove the mountedannouncehandler with no auth extension at all and a body naming the same DID withhttp_url = https://attacker.example.com. Result: 2xx, the URL was rewritten, andlast_ping_okwas stillTRUE.Why it matters
api/repos.rs:1451filters the federated fan-out on that flag, and the response handling atapi/repos.rs:1464-1476takes the peer's JSON body verbatim and stamps each entry withnode_didset to the hijacked peer's DID. No signature check, no content addressing. So the attacker injects arbitrary repo entries attributed to a legitimate peer.Bound on the attack
A new DID inserts with
last_ping_okFALSE (db/mod.rs:2094) and stays out of the fan-out until a gossip round probes it. So the instant, probe-free version requires hijacking an existing reachable DID. An attacker announcing their own DID with their own live host still enters the fan-out, just after a probe and without stolen attribution.Fix direction
Reset the gate whenever the URL actually changes, so a repointed peer has to re-earn reachability:
That leaves a plain liveness re-announce alone while closing the carry-over on a swap. It is a direction, not something I have compiled and run; the deeper question is whether announce should bind a DID to its first-seen key regardless of the signing default, which would close the rewrite rather than just its blast radius.
Scope
Pre-existing and untouched by #248:
git diff 111cff7e...32e45787does not includedb/mod.rs, and thepeers.rshunks touch onlyping_peerand tests. #248 does widen the window slightly in passing, since its two-consecutive-failure hysteresis means a hijacked-then-dead URL holds the gate for two gossip rounds instead of one.