Skip to main content

pantheon-ssrf-guard

tests PyPI Python License

A small, dependency-free SSRF egress guard for Python's standard-library HTTP client — one that survives DNS rebinding.

The guarantee: an outbound request cannot reach a private, loopback, link-local, cloud-metadata, or CGN address — even if the hostname resolves public at check time and rebinds to an internal IP before the socket connects.

Extracted from PANTHEON, a multi-tenant AI substrate, where it is the single canonical guard on every outbound path (link imports, uploaded-URL fetches, external MCP-server transports).

Why a pre-check isn't enough

The common "block internal hosts" approach resolves the hostname and rejects private IPs before connecting. An attacker defeats it with DNS rebinding: the name resolves to a public IP when you check it, then to 127.0.0.1 / 169.254.169.254 (cloud metadata) / an internal service by the time the socket actually opens. A check after connect() is too late for the handshake: the TCP and TLS handshakes have already reached whatever address the second lookup returned.

pantheon-ssrf-guard does not look the name up twice:

  1. pin_public_address(host) — resolve once. If any address is non-public, raise before a socket exists.
  2. GuardedHTTP(S)Connection — open the socket to that IP literal, never to the hostname. TLS starts only after the connected peer is that IP. SNI and the certificate check stay on the original hostname.

The classifier's catch-all is not ip.is_global, so it also rejects everything the obvious flags miss — RFC 6598 shared/CGN space (100.64.0.0/10, used by Kubernetes/internal load balancers and the Alibaba/Tencent/Oracle metadata endpoint 100.100.100.200), plus benchmark, documentation, and NAT64 ranges.

Install

pip install pantheon-ssrf-guard   # or: copy the single ssrf_guard.py file into your project

Use

from ssrf_guard import guarded_opener, SsrfBlocked

opener = guarded_opener()          # https-capable; redirects refused by default (each hop is pinned if allowed)

try:
    with opener.open("https://example.com/data.json", timeout=10) as resp:
        body = resp.read()
except SsrfBlocked:
    ...   # the target resolved (or rebound) to a non-public address — refused before any data was sent

Lower-level pieces are exported too if you're building your own client:

from ssrf_guard import host_is_public, ip_is_public, peer_is_public
from ssrf_guard import GuardedHTTPHandler, GuardedHTTPSHandler

What it does not do

  • It doesn't proxy, cache, or rate-limit — it only refuses non-public destinations.
  • It guards urllib/http.client. For httpx/requests you'd apply the same idea at their transport layer (the classification functions here are reusable).
  • It is not a WAF; it's the one specific control that stops server-side request forgery to internal infrastructure.
  • A name is resolved once. If any address is non-public, nothing is attempted. The socket is opened to the IP from that resolution, not to the name, so a second lookup cannot rebind it. TLS starts only after the connected peer is that IP. The certificate is still checked against the original hostname. SNI is that hostname, not the IP.
  • This does not cover a proxy. A tunnel hides the destination, so a proxied connection is refused. trust_env_proxy=True does not override that.
  • This does not cover a network that delivers a public IP to a different machine. That is not DNS.

Changelog

  • 0.3.0 — connect to a pinned public IP. A DNS rebind no longer gets a TCP or TLS handshake. Proxied connections are refused, because the pin cannot see past a tunnel.

  • 0.2.0 — scheme allowlist at the opener boundary and environment proxies off by default, after an external review reproduced two bypasses: build_opener() also installs file:/ftp:/data: handlers, so opener.open("file:///etc/passwd") read a local file without the HTTP guard ever running; and a ProxyHandler inherited from HTTP(S)_PROXY moves the connection to the proxy, so the peer check validated the proxy while the real destination went unexamined. Now only http/https are reachable (enforced per-hop, so a redirect to ftp: is refused too), the dangerous handlers are removed from the opener entirely, and ProxyHandler({}) is installed unless the caller passes trust_env_proxy=True. The handshake residue documented in 0.2.x is closed in 0.3.0.

  • 0.1.1 — the pre-check is now wired into the opener (so "two-layer" is true, not aspirational): a GuardedHTTP(S)Connection rejects a non-public host by name BEFORE opening the socket, then re-checks the actual peer at connect time (the rebind guard). And IPv4-mapped (::ffff:…) + NAT64 (64:ff9b::/96) addresses are normalised to their embedded v4 before classifying, so an internal target smuggled inside a v6 address is blocked version-independently instead of trusting whichever is_* flags a given CPython sets.

  • 0.1.0 — initial release.

License

Apache-2.0. See LICENSE.

Metadata

Release files for pantheon-ssrf-guard 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pantheon-ssrf-guard 0.3.0
File Size Uploaded Provenance Actions
pantheon_ssrf_guard-0.3.0.tar.gz 15.4 kB With provenance Details for pantheon_ssrf_guard-0.3.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for pantheon-ssrf-guard 0.3.0
File Interpreter ABI Platform Actions
pantheon_ssrf_guard-0.3.0-py3-none-any.whl Python 3 none any Details for pantheon_ssrf_guard-0.3.0-py3-none-any.whl

Total release size: 27.5 kB

Release files / pantheon_ssrf_guard-0.3.0.tar.gz

Download URL pantheon_ssrf_guard-0.3.0.tar.gz
Size 15.4 kB
Tags Source
SHA-256 checksum
How to use checksums
1ef7dfa08b91fc5846f408b48cd919e56f392f6e9737fb5db6db0fb8fae95ed3
BLAKE2b-256 checksum
How to use checksums
7666e3ac3fd2e67efc606ee2aabc325e70af38f73e6197104ab10f58dfceb4ea
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 4, 2026.

Transparency log

Release files / pantheon_ssrf_guard-0.3.0-py3-none-any.whl

Download URL pantheon_ssrf_guard-0.3.0-py3-none-any.whl
Size 12.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
aa8b74c766d03299b944c69a47a349c470e491767f5cc06c375535559e7a3d18
BLAKE2b-256 checksum
How to use checksums
c828166d3a124623839d9d5c1f3255d8a72fcc61469042c309dc2a7025448627
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 4, 2026.

Transparency log

Security for pantheon-ssrf-guard 0.3.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for pantheon_ssrf_guard-0.3.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 4, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page