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
Commit a8cc33a
Browse filesBrowse the repository at this point in the historyBrowse files
* fix(security): reject non-public peer URLs + prune poisoned peers
The unauthenticated POST /api/v1/peers/announce only validated the
http(s) scheme, so anyone could register loopback/private 'peers'
(localhost:5432, localhost:22, internal admin URLs). On every push the
node fans out signed sync-notify POSTs to all peers — turning this into
an SSRF probe and burying the real peers under junk so node-origin repos
stopped replicating. Observed on node.gitlawb.com: 126/194 peer rows were
injected junk (fake DIDs z6MkScan…/z6MkAdmin…, localhost targets).
- is_public_http_url(): reject non-http(s), loopback, unspecified, private
(RFC1918 + ULA), link-local, and localhost/.local/.internal hosts.
Applied at announce time.
- db.prune_non_public_peers(): boot-time sweep removing already-injected
rows, run from main() alongside prune_self_peers — self-heals poisoned
tables on deploy.
- tests for the validator (accept public, reject loopback/private/internal,
incl. IPv6 [::1]).
Co-Authored-By: OpenClaude <openclaude@gitlawb.com>
* fix(security): address PR #78 review — build, redirect SSRF, validation gaps
Resolves the P1/P2 review feedback on the peer-announce SSRF fix:
- main.rs: revert the unrelated, incomplete `storage::build()` refactor back
to the tigris init from main (the `storage` module never existed, breaking
the build). Keeps only the prune_non_public_peers boot call.
- Redirect bypass: set redirect::Policy::none() on all peer-facing reqwest
clients (shared http_client, sync worker, ping_peer) so a peer cannot answer
`302 -> http://127.0.0.1/` and bypass the public-URL check. ping_peer now
reuses the shared no-redirect client instead of bare reqwest::get.
- Move the public-URL guard into db::upsert_peer so the bootstrap announce-back
path (main.rs) can no longer re-poison the table after the prune.
- is_public_http_url: strip trailing-dot FQDNs (`localhost.`), fold
IPv4-mapped/compatible IPv6 (`::ffff:127.0.0.1`, `::127.0.0.1`) to IPv4 before
range checks, and reject CGNAT (100.64.0.0/10). Added tests for each.
- prune_non_public_peers: collect bad DIDs then DELETE ... WHERE did = ANY($1)
in one statement, so a transient error no longer abandons remaining rows.
Follow-up (separate issue): DNS-resolution rebinding guard rejecting peer
hostnames that resolve to private IPs.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(security): satisfy rustfmt + don't drop redirect guard on builder failure
- peers.rs: collapse the CGNAT/private check onto one line (cargo fmt --check).
- sync.rs: replace the unwrap_or_else(reqwest::Client::new()) fallback with
.expect(). The default client follows redirects, so the fallback silently
reintroduced the SSRF vector Policy::none() exists to close (CodeRabbit).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: OpenClaude <openclaude@gitlawb.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
0 commit comments