feat: SSRF protection — block private IP ranges on outgoing requests
Some checks failed
CI / fmt (push) Successful in 23s
CI / test (push) Has been cancelled
CI / clippy (push) Has been cancelled

SsrfVerifier rejects private/reserved IPs (loopback, RFC1918, link-local,
CGNAT, ULA) on all federation fetches. Raw reqwest calls in webfinger and
backfill also validated. Debug mode bypasses via PermissiveVerifier.

Closes #4
This commit is contained in:
2026-05-30 02:48:35 +02:00
parent 7171a1791a
commit 4cb8efb6ce
5 changed files with 148 additions and 1 deletions

View File

@@ -21,6 +21,8 @@ impl ActivityPubService {
outbox_url: &str,
actor_url: &str,
) -> anyhow::Result<()> {
let outbox_parsed = url::Url::parse(outbox_url)?;
crate::security::validate_url(&outbox_parsed).await?;
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(
super::HTTP_FETCH_TIMEOUT_SECS,
@@ -49,6 +51,12 @@ impl ActivityPubService {
tracing::warn!(url = %current_url, "backfill: loop detected, stopping");
break;
}
if let Ok(page_url) = url::Url::parse(&current_url)
&& let Err(e) = crate::security::validate_url(&page_url).await
{
tracing::warn!(url = %current_url, error = %e, "backfill: SSRF check failed");
break;
}
let page: serde_json::Value = match client
.get(&current_url)
.header("Accept", "application/activity+json")