Summary
The unauthenticated POST /api/v1/peers/announce endpoint accepts any http(s) URL as a peer address without validating that the host is publicly routable. An attacker can register loopback, RFC1918, ULA, or link-local addresses as peers. The node then fans out signed
sync-notify POSTs to every registered peer, turning the peer registry into an SSRF probe into the node's internal network.
Impact
On the live node.gitlawb.com instance, 126 of 194 peer rows were poisoned with fake DIDs and localhost/internal targets. An attacker can:
- Probe internal services (
127.0.0.1, 10.x, 192.168.x, 169.254.169.254, etc.) by observing timing/response behavior of the signed fan-out requests.
- Poison the peer table so legitimate sync traffic is misrouted.
- Use the node as an amplification point for signed requests to arbitrary internal endpoints.
Affected path
crates/gitlawb-node/src/api/peers.rs — announce route
crates/gitlawb-node/src/main.rs — sync-notify fan-out
Proposed fix
- Validate peer URLs on ingress. Add
is_public_http_url() that rejects loopback, unspecified, RFC1918, ULA, link-local, and localhost hosts. Reject invalid URLs at the announce boundary.
- Disable redirect following on the sync-notify client. Configure
reqwest with redirect::Policy::none(), or a custom policy that re-runs is_public_http_url() against every hop. This closes the SSRF even when a public URL 302s to an internal target.
- Route bootstrap announce through the same validator so the bootstrap path can't be used to bypass ingress validation.
- Prune poisoned peers at boot. Add
Db::prune_non_public_peers() that iterates all rows, logs and continues on per-row failure (do not abort on first error), and removes any peer whose URL fails is_public_http_url().
- Tests covering: each rejected address family, redirect-to-internal rejection, bootstrap-path validation, and prune resilience.
Summary
The unauthenticated
POST /api/v1/peers/announceendpoint accepts anyhttp(s)URL as a peer address without validating that the host is publicly routable. An attacker can register loopback, RFC1918, ULA, or link-local addresses as peers. The node then fans out signedsync-notify POSTs to every registered peer, turning the peer registry into an SSRF probe into the node's internal network.
Impact
On the live
node.gitlawb.cominstance, 126 of 194 peer rows were poisoned with fake DIDs and localhost/internal targets. An attacker can:127.0.0.1,10.x,192.168.x,169.254.169.254, etc.) by observing timing/response behavior of the signed fan-out requests.Affected path
crates/gitlawb-node/src/api/peers.rs—announceroutecrates/gitlawb-node/src/main.rs— sync-notify fan-outProposed fix
is_public_http_url()that rejects loopback, unspecified, RFC1918, ULA, link-local, andlocalhosthosts. Reject invalid URLs at the announce boundary.reqwestwithredirect::Policy::none(), or a custom policy that re-runsis_public_http_url()against every hop. This closes the SSRF even when a public URL 302s to an internal target.Db::prune_non_public_peers()that iterates all rows, logs and continues on per-row failure (do not abort on first error), and removes any peer whose URL failsis_public_http_url().