mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-26 18:31:34 +01:00
assertPublicRemoteHttpEndpoint resolved an operator-supplied hostname, approved it, and then handed the *hostname* to global fetch, which resolved it a second time. A name server the attacker controls can answer with a public address for the guard's lookup and a loopback, RFC 1918 or link-local address for the connection, so an authenticated public deployment could be walked onto internal services or cloud metadata. The guard now returns the address set it approved, and guardedRemoteHttpFetch dials that address itself: one resolution, no second lookup below it, the original hostname kept for Host, TLS SNI and certificate identity, and a peer-address check before a single request byte (or ClientHello) is written. Redirects stay manual so the caller re-runs the whole guard on every hop it follows. Wired into all three guard-then-fetch sites: OAuth discovery/registration/ token exchange, catalog tools/list, and the gateway tools/call path. Two cases keep platform fetch, both because there is nothing to pin: deployments that allow private endpoints, and URLs that already carry an IP literal. Regression coverage models the rebinding name server deterministically — public on the first answer, 127.0.0.1 after it — and proves the request lands on the approved address while the loopback listener sees no connection at all. Reintroducing either half of the fix (a second lookup at dial time, or dropping the peer check) turns those tests red. Residual risk unchanged: egress allowlisting and network policy are still recommended as defence in depth. Refs PAP-17098