fix(web_fetch): block non-public targets by default and gate every hop

kigi allowed loopback unconditionally and missed several non-public
ranges, and the SSRF check ran only on the initial URL.

Policy (ssrf.rs):
- loopback is blocked unless `[toolset.web_fetch] allow_local` (or
  KIGI_WEB_FETCH_ALLOW_LOCAL) is on, AND the URL names it explicitly,
  so a public name resolving to loopback stays blocked (DNS rebinding)
- add 0.0.0.0/8, 100.64/10, 192.0.0.0/24, TEST-NET-1/2/3, 198.18/15,
  240/4, IPv6 site-local and documentation prefixes
- inherit the IPv4 verdict through mapped, compatible, NAT64 and 6to4
  wrappers; network-specific NAT64 prefixes remain uncovered (see doc)

Plumbing (client.rs), where the exploitable half lived:
- re-check every redirect hop, not just the first
- compare hosts exactly; a `www` sibling has its own A records, so it
  is a cross-host redirect rather than an auto-followed hop
- run the check before the fetch service, so a blocked URL is never
  posted to an endpoint that egresses elsewhere
- exempt explicit local hosts from the https upgrade and from the
  single-label filter, and re-upgrade each followed hop

Wiring: allow_local reaches WebFetchParams from both construction
paths; documented in the config guide and the README env table.
This commit is contained in:
2026-07-27 11:44:20 -04:00
parent ac23ebc9a1
commit ad3840f9ec
7 changed files with 457 additions and 115 deletions
+2
View File
@@ -1309,6 +1309,7 @@ output_byte_limit = 65536 # max output size (64KB)
[toolset.web_fetch]
proxy_endpoint = "https://proxy.example.com" # egress proxy URL (all requests routed through it)
allowed_domains = ["docs.rs", "x.ai"] # override the built-in ~84-domain allowlist
allow_local = false # true = reach an explicit localhost / 127.0.0.0/8 / ::1 URL
[shortcuts]
send = ["Enter"]
@@ -2384,6 +2385,7 @@ The agent persists all session updates automatically. Clients can reconnect and
| `KIGI_AGENT` | Custom agent definition path or name (see [Agent Profiles](#agent-profiles)) |
| `KIGI_WEB_FETCH` | Enable (`1`) or disable (`0`) the `web_fetch` tool |
| `KIGI_WEB_FETCH_PROXY` | Egress proxy URL for `web_fetch` requests (overridden by `[toolset.web_fetch] proxy_endpoint`) |
| `KIGI_WEB_FETCH_ALLOW_LOCAL` | `1` lets `web_fetch` reach an explicit loopback URL; private and metadata ranges stay blocked |
| `KIGI_RESPECT_GITIGNORE` | Disable `.gitignore` filtering in tools when set to `0` |
| `KIGI_FEEDBACK_ENABLED` | Enable (`1`) or disable (`0`) feedback system independently from telemetry |
| `KIGI_DEPLOYMENT_KEY` | Management API key for enterprise deployments |