Compare commits

...

2 commits

Author SHA1 Message Date
Felix Ableitner
f20cf41c39 fix 2026-01-28 14:39:32 +01:00
Felix Ableitner
5880a52a47 Improve error message, allow local IP federation via env var (fixes #152) 2026-01-20 10:55:57 +01:00
4 changed files with 25 additions and 22 deletions

View file

@ -33,10 +33,10 @@ use url::Url;
/// ///
/// - `activity`: The activity to be sent, gets converted to json /// - `activity`: The activity to be sent, gets converted to json
/// - `private_key`: Private key belonging to the actor who sends the activity, for signing HTTP /// - `private_key`: Private key belonging to the actor who sends the activity, for signing HTTP
/// signature. Generated with [crate::http_signatures::generate_actor_keypair]. /// signature. Generated with [crate::http_signatures::generate_actor_keypair].
/// - `inboxes`: List of remote actor inboxes that should receive the activity. Ignores local actor /// - `inboxes`: List of remote actor inboxes that should receive the activity. Ignores local actor
/// inboxes. Should be built by calling [crate::traits::Actor::shared_inbox_or_inbox] /// inboxes. Should be built by calling [crate::traits::Actor::shared_inbox_or_inbox]
/// for each target actor. /// for each target actor.
pub async fn queue_activity<A, Datatype, ActorType>( pub async fn queue_activity<A, Datatype, ActorType>(
activity: &A, activity: &A,
actor: &ActorType, actor: &ActorType,

View file

@ -52,8 +52,8 @@ impl SendActivityTask {
/// ///
/// - `activity`: The activity to be sent, gets converted to json /// - `activity`: The activity to be sent, gets converted to json
/// - `inboxes`: List of remote actor inboxes that should receive the activity. Ignores local actor /// - `inboxes`: List of remote actor inboxes that should receive the activity. Ignores local actor
/// inboxes. Should be built by calling [crate::traits::Actor::shared_inbox_or_inbox] /// inboxes. Should be built by calling [crate::traits::Actor::shared_inbox_or_inbox]
/// for each target actor. /// for each target actor.
pub async fn prepare<A, Datatype, ActorType>( pub async fn prepare<A, Datatype, ActorType>(
activity: &A, activity: &A,
actor: &ActorType, actor: &ActorType,

View file

@ -25,6 +25,7 @@ use async_trait::async_trait;
use bytes::Bytes; use bytes::Bytes;
use derive_builder::Builder; use derive_builder::Builder;
use dyn_clone::{clone_trait_object, DynClone}; use dyn_clone::{clone_trait_object, DynClone};
use itertools::Itertools;
use moka::future::Cache; use moka::future::Cache;
use regex::Regex; use regex::Regex;
use reqwest::{redirect::Policy, Client, Request}; use reqwest::{redirect::Policy, Client, Request};
@ -186,27 +187,26 @@ impl<T: Clone> FederationConfig<T> {
// Resolve domain and see if it points to private IP // Resolve domain and see if it points to private IP
// TODO: Use is_global() once stabilized // TODO: Use is_global() once stabilized
// https://doc.rust-lang.org/std/net/enum.IpAddr.html#method.is_global // https://doc.rust-lang.org/std/net/enum.IpAddr.html#method.is_global
let invalid_ip = let mut ips = lookup_host((domain.to_owned(), 80)).await?;
lookup_host((domain.to_owned(), 80)) let allow_local = std::env::var("DANGER_FEDERATION_ALLOW_LOCAL_IP").is_ok();
.await? let invalid_ip = !allow_local
.any(|addr| match addr.ip() { && ips.any(|addr| match addr.ip() {
IpAddr::V4(addr) => { IpAddr::V4(addr) => {
addr.is_private() addr.is_private()
|| addr.is_link_local() || addr.is_link_local()
|| addr.is_loopback() || addr.is_loopback()
|| addr.is_multicast() || addr.is_multicast()
} }
IpAddr::V6(addr) => { IpAddr::V6(addr) => {
addr.is_loopback() addr.is_loopback()
|| addr.is_multicast() || addr.is_multicast()
|| ((addr.segments()[0] & 0xfe00) == 0xfc00) // is_unique_local || ((addr.segments()[0] & 0xfe00) == 0xfc00) // is_unique_local
|| ((addr.segments()[0] & 0xffc0) == 0xfe80) // is_unicast_link_local || ((addr.segments()[0] & 0xffc0) == 0xfe80) // is_unicast_link_local
} }
}); });
if invalid_ip { if invalid_ip {
return Err(Error::UrlVerificationError( let ip_addrs = ips.join(", ");
"Localhost is only allowed in debug mode", return Err(Error::DomainResolveError(domain.to_string(), ip_addrs));
));
} }
} }

View file

@ -28,6 +28,9 @@ pub enum Error {
/// url verification error /// url verification error
#[error("URL failed verification: {0}")] #[error("URL failed verification: {0}")]
UrlVerificationError(&'static str), UrlVerificationError(&'static str),
/// Resolving domain points to local IP.
#[error("Resolving domain {0} points to local IP {1}. This may indicate an attacker attempting to access internal services. If intentional, you can ignore this error by setting DANGER_FEDERATION_ALLOW_LOCAL_IP=1")]
DomainResolveError(String, String),
/// Incoming activity has invalid digest for body /// Incoming activity has invalid digest for body
#[error("Incoming activity has invalid digest for body")] #[error("Incoming activity has invalid digest for body")]
ActivityBodyDigestInvalid, ActivityBodyDigestInvalid,