The SSRF blocklist has no escape hatch, so a self-hosted proxy cannot forward to your own network #204

Closed
opened 2026-08-20 05:47:37 +02:00 by clawbot · 0 comments
Collaborator

internal/delivery/ssrf.go:38-72 hardcodes the blocked ranges with no configuration field. RFC1918, loopback and CGNAT are permanently unreachable as delivery destinations.

That guard is correct as a default and must stay the default — it is what #36, #68 and #69 bought. But webhooker is self-hosted software whose main use is receiving a public webhook and forwarding it to an internal service: a container on the same Docker network, a box on 10.x, a service on 127.0.0.1. All of those are refused today, with no way to permit one.

Concrete evidence of the bite: during the deployability run I could not point a target at a sink on the same host, because every address on it is in the blocklist. The end-to-end test had to use a public internet echo service instead.

Definition of done:

  • a config value (e.g. ALLOWED_EGRESS_CIDRS, defaulting empty) allows specific CIDRs through the guard
  • it is a CIDR allowlist, not a boolean off switch: no setting disables SSRF protection wholesale
  • the link-local metadata range stays blocked unconditionally, even if a supplied CIDR would cover it, since that is credential theft rather than an internal target
  • set-but-unparseable values abort startup, per #80 and repo policy
  • the allowlist applies at BOTH target-creation validation and delivery time, so the two paths cannot disagree (that split was the #68 and #69 bug)
  • startup logs the effective allowlist when non-empty, so an operator can see the hole they opened
  • tests: a permitted CIDR delivers, a non-permitted private address is still refused, metadata stays refused even when covered by a supplied CIDR
  • README documents it with the risk stated plainly
`internal/delivery/ssrf.go:38-72` hardcodes the blocked ranges with no configuration field. RFC1918, loopback and CGNAT are permanently unreachable as delivery destinations. That guard is correct as a default and must stay the default — it is what #36, #68 and #69 bought. But webhooker is self-hosted software whose main use is receiving a public webhook and forwarding it to an internal service: a container on the same Docker network, a box on `10.x`, a service on `127.0.0.1`. All of those are refused today, with no way to permit one. Concrete evidence of the bite: during the deployability run I could not point a target at a sink on the same host, because every address on it is in the blocklist. The end-to-end test had to use a public internet echo service instead. Definition of done: - a config value (e.g. `ALLOWED_EGRESS_CIDRS`, defaulting empty) allows specific CIDRs through the guard - it is a CIDR allowlist, not a boolean off switch: no setting disables SSRF protection wholesale - the link-local metadata range stays blocked unconditionally, even if a supplied CIDR would cover it, since that is credential theft rather than an internal target - set-but-unparseable values abort startup, per #80 and repo policy - the allowlist applies at BOTH target-creation validation and delivery time, so the two paths cannot disagree (that split was the #68 and #69 bug) - startup logs the effective allowlist when non-empty, so an operator can see the hole they opened - tests: a permitted CIDR delivers, a non-permitted private address is still refused, metadata stays refused even when covered by a supplied CIDR - README documents it with the risk stated plainly
clawbot added this to the 1.0.0 milestone 2026-08-20 05:47:37 +02:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/webhooker#204