url_jail
SSRF-safe URL validation for Rust and Python.
The Problem
Your application fetches a user-provided URL:
=
An attacker submits http://169.254.169.254/latest/meta-data/iam/credentials.
Result: Your AWS keys are in their inbox. Your S3 buckets are public. Your cloud bill is six figures.
This is Server-Side Request Forgery (SSRF), the vulnerability behind:
- CVE-2024-0243: LangChain RecursiveUrlLoader (CVSS 8.6)
- CVE-2025-2828: LangChain RequestsToolkit (CVSS 9.1)
- Capital One 2019 breach (100M+ records)
"I'll just block 169.254.169.254"
Attackers encode IPs in ways your blocklist won't catch:
| Attack | Your Blocklist | url_jail |
|---|---|---|
http://0x7f000001/ (hex) |
Passes | Blocked |
http://0177.0.0.1/ (octal) |
Passes | Blocked |
http://2130706433/ (decimal) |
Passes | Blocked |
http://127.1/ (short-form) |
Passes | Blocked |
http://[::ffff:127.0.0.1]/ (IPv6-mapped) |
Passes | Blocked |
http://metadata.google.internal/ |
Maybe | Blocked |
| DNS rebinding | Passes | Blocked* |
* When using get_sync() or the returned Validated.ip directly.
url_jail validates after DNS resolution. Encoding tricks don't work.
Note: This library has not undergone a formal security audit. See SECURITY.md.
The Solution
Python (recommended):
= # Validates URL and all redirects
Python (with existing HTTP client):
=
= # SSRF-safe requests.Session
Rust:
use ;
use Client;
let v = validate.await?;
let client = builder
.resolve
.build?;
let response = client.get.send.await?;
Installation
# With HTTP client adapters
[]
= "0.2"
# Enable fetch() for redirect chain validation
= { = "0.2", = ["fetch"] }
Policies
| Policy | Allows | Blocks |
|---|---|---|
PublicOnly |
Public IPs only | Private, loopback, link-local, metadata |
AllowPrivate |
Private + public | Loopback, metadata (for internal services) |
HTTP Client Adapters (Python)
# requests
=
=
# httpx (sync)
=
=
# httpx (async)
= await
# aiohttp
= await
# urllib3
=
=
Advanced: Custom Blocklist
use ;
let policy = new
.block_cidr
.block_host
.build;
Error Handling
use ;
match validate_sync
| Method | Returns true for |
|---|---|
is_blocked() |
SsrfBlocked, HostnameBlocked, RedirectBlocked |
is_retriable() |
DnsError, Timeout, HttpError |
url() |
Extracts the URL that caused the error |
What's Blocked
- Cloud metadata endpoints (AWS, GCP, Azure, Alibaba)
- Private IPs (10.x, 172.16.x, 192.168.x) with
PublicOnly - Loopback (127.x, ::1)
- Link-local (169.254.x, fe80::)
- IP encoding tricks: octal (
0177.0.0.1), decimal (2130706433), hex (0x7f000001), short-form (127.1) - IPv4-mapped IPv6 (
::ffff:127.0.0.1)
Features
| Feature | Description |
|---|---|
fetch |
fetch() / get_sync() with redirect validation |
tracing |
Logging for validation decisions |
License
MIT OR Apache-2.0