ssrf_guard/lib.rs
1//! Fetch user-supplied URLs from a server without letting them reach your
2//! private network.
3//!
4//! When a server fetches a URL someone else gave it (a webhook, a link
5//! preview, an uptime check), the URL can point at `127.0.0.1`, at the cloud
6//! metadata service on `169.254.169.254`, or at a name that resolves to one
7//! of them.
8//!
9//! A [`Fetcher`] resolves every hop itself and refuses the whole name if any
10//! address is loopback, private, link-local or otherwise not public
11//! ([`is_public`] has the list, IPv6 spellings included). It then connects
12//! only to the addresses it checked. A DNS answer that changes afterwards
13//! changes nothing. Redirects are followed by hand and checked again, a
14//! redirect to another origin goes without the headers and body you set,
15//! proxies from the environment are ignored, and one timeout covers the
16//! whole chain.
17//!
18//! ```no_run
19//! use ssrf_guard::{Fetcher, Policy};
20//!
21//! # async fn run() -> Result<(), ssrf_guard::Error> {
22//! let fetcher = Fetcher::new(Policy::default());
23//! let resp = fetcher.get("https://example.com/").await?;
24//! println!("{} from {:?}", resp.status(), resp.remote_addr());
25//! let body = resp.text().await?;
26//! # Ok(())
27//! # }
28//! ```
29//!
30//! A redirect to `http://localhost/` or to a name that resolves to `10.1.2.3`
31//! fails with [`Error::Denied`]. [`Fetcher::connect`] does the same checks
32//! for a plain TCP connection, and [`Fetcher::resolve`] for anything else
33//! you connect yourself.
34//!
35//! To refuse more, such as your own servers' public addresses, add blocks
36//! with [`PolicyBuilder::deny`]. To let something through that the list refuses,
37//! such as loopback in tests, use [`PolicyBuilder::allow`].
38//!
39//! # Platform notes
40//!
41//! Needs a tokio runtime. TLS is rustls with the platform verifier, which
42//! checks certificates against the operating system's trust store. On
43//! Linux that needs a CA bundle installed (`ca-certificates` on Debian and
44//! Alpine), which slim container images often leave out. TLS session
45//! resumption is off. A server can't use session tickets to tie one
46//! caller's fetches to another's. HTTP/1.1 only.
47//!
48//! The system resolver blocks a thread per lookup, and the policy's timeout
49//! can't stop it. See [`PolicyBuilder::timeout`].
50//!
51//! The checks run on the addresses this process connects to. They can't see
52//! past a NAT64 gateway or a VPN that routes public addresses somewhere
53//! private, and they don't stop a public server you fetch from doing
54//! whatever it likes with the request.
55
56#![forbid(unsafe_code)]
57#![warn(missing_docs)]
58
59mod addr;
60mod error;
61mod fetch;
62mod policy;
63
64pub use addr::{Cidr, is_public};
65pub use error::Error;
66pub use fetch::{Fetcher, Request, Resolve, Response};
67pub use policy::{Policy, PolicyBuilder};
68
69pub use bytes::Bytes;
70pub use http::header;
71pub use http::{Method, StatusCode};
72pub use url::Url;
73
74// Compiles the README's example with the doctests.
75#[cfg(doctest)]
76#[doc = include_str!("../README.md")]
77struct Readme;