feat: SSRF protection — block private IP ranges on outgoing requests
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:
@@ -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(¤t_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(¤t_url)
|
||||
.header("Accept", "application/activity+json")
|
||||
|
||||
@@ -450,6 +450,8 @@ impl ActivityPubService {
|
||||
domain_str, user, domain_str
|
||||
);
|
||||
tracing::debug!(handle, wf_url, "resolving webfinger");
|
||||
let wf_parsed = Url::parse(&wf_url)?;
|
||||
crate::security::validate_url(&wf_parsed).await?;
|
||||
let wf: serde_json::Value = reqwest::Client::new()
|
||||
.get(&wf_url)
|
||||
.header("Accept", "application/jrd+json, application/json")
|
||||
|
||||
Reference in New Issue
Block a user