fix(hooks): share one SSRF policy instead of a second, stale copy
The hook runner carried its own `is_blocked_ip`, a line-for-line copy of the pre-hardening web_fetch predicate: loopback allowed unconditionally, and no TEST-NET, 198.18/15, 240/4, 0.0.0.0/8, multicast, IPv6 site-local or embedded-v4 wrapper coverage. Hook URLs come from settings, and project settings from an untrusted repo are loaded today, so the gap is reachable. `kigi-hooks` already depends on `kigi-tools`, so the fix is to delete the copy and call the shared predicate — no new crate, and no third implementation to drift. `allow_local` is on for hooks: a loopback hook receiver is a legitimate local setup, which the old copy also allowed. It is now allowed only when the URL names the host literally, so a public name resolving to loopback is refused. Range coverage now lives with the policy; the runner's tests pin what it adds on top. The URL-scrubbing regression test moves off TEST-NET-1, which the policy now blocks, onto a closed loopback port — faster, and no real egress from a test.
This commit is contained in:
@@ -14,7 +14,7 @@ pub mod domain;
|
||||
pub mod error;
|
||||
mod http;
|
||||
pub(crate) mod overflow;
|
||||
mod ssrf;
|
||||
pub mod ssrf;
|
||||
|
||||
pub use client::WebFetchClient;
|
||||
pub use config::WebFetchParams;
|
||||
|
||||
@@ -16,7 +16,7 @@ use super::error::WebFetchError;
|
||||
/// Hosts allowed to reach loopback when local access is on.
|
||||
///
|
||||
/// Names that merely RESOLVE to loopback are excluded: DNS rebinding.
|
||||
pub(crate) fn is_explicit_local_host(host: &str) -> bool {
|
||||
pub fn is_explicit_local_host(host: &str) -> bool {
|
||||
let host = host.trim().trim_end_matches('.').to_ascii_lowercase();
|
||||
let host = host
|
||||
.strip_prefix('[')
|
||||
@@ -32,7 +32,7 @@ pub(crate) fn is_explicit_local_host(host: &str) -> bool {
|
||||
}
|
||||
|
||||
/// Whether an IP is not globally routable.
|
||||
pub(crate) fn is_non_public_ip(ip: &IpAddr) -> bool {
|
||||
pub fn is_non_public_ip(ip: &IpAddr) -> bool {
|
||||
match ip {
|
||||
IpAddr::V4(v4) => is_non_public_ipv4(*v4),
|
||||
IpAddr::V6(v6) => is_non_public_ipv6(*v6),
|
||||
@@ -126,7 +126,8 @@ fn is_loopback_addr(ip: &IpAddr) -> bool {
|
||||
/// Dual gate: loopback opens only for an explicit local host.
|
||||
///
|
||||
/// Private and link-local never open through this flag.
|
||||
pub(crate) fn is_blocked_for_host(ip: &IpAddr, host: &str, allow_local: bool) -> bool {
|
||||
/// Shared with the hook runner: one policy, every outbound URL.
|
||||
pub fn is_blocked_for_host(ip: &IpAddr, host: &str, allow_local: bool) -> bool {
|
||||
if !is_non_public_ip(ip) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user