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:
@@ -47,12 +47,20 @@ pub struct WebFetchParams {
|
||||
/// on any failure (kimi-cli `tools/web/fetch.py FetchURL.__call__`).
|
||||
#[serde(default)]
|
||||
pub service_url: Option<String>,
|
||||
/// Opt-in for loopback targets; off means no local access.
|
||||
#[serde(default)]
|
||||
pub allow_local: Option<bool>,
|
||||
}
|
||||
|
||||
register_resource!("kigi", "WebFetch", WebFetchParams);
|
||||
|
||||
// Keep defaults here so call-sites don't have to manage unwrapping.
|
||||
impl WebFetchParams {
|
||||
/// From config or `KIGI_WEB_FETCH_ALLOW_LOCAL`, never tool input.
|
||||
pub fn allow_local(&self) -> bool {
|
||||
self.allow_local.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn cache_ttl_secs(&self) -> Duration {
|
||||
Duration::from_secs(self.cache_ttl_secs.unwrap_or(15 * 60))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user