Skip to main content

egressweave

SSRF- and DNS-rebinding-safe outbound HTTP for Python.

egressweave validates an outbound URL against an explicit host allowlist, refuses any target that resolves to a non-globally-routable address, and hands back an httpx.AsyncClient whose every connection is pinned to the validated addresses — rejecting any host or port that changes after validation.

It exists because the naive pattern — resolve, check the IP, then httpx.get(url) — is unsafe: the attacker-controlled DNS answer can change between the check and the connect (a TOCTOU / DNS-rebinding attack, CWE-350), and a permissive URL parser can be tricked into reaching internal services (SSRF, CWE-918).

What it defends against

  • SSRF (CWE-918): rejects private, loopback, link-local, reserved, multicast, unspecified, and otherwise non-global addresses; rejects embedded credentials, query/fragment, plaintext http to remote hosts, IP-literal hosts, backslash smuggling, and ASCII control characters.
  • DNS rebinding / validate-then-connect TOCTOU (CWE-350): resolves all addresses up front, validates each, and pins them into a custom transport that re-validates on every connect and refuses any host/port drift.
  • Egress allowlist: only hostnames you explicitly list are reachable; wildcards are refused — the allowlist is exact.
  • Redirects are disabled and environment proxies ignored (trust_env=False), so a 302 cannot bounce a request to an unvalidated host, and Unix sockets are refused.

Install

pip install egressweave

Quickstart

from egressweave import EgressPolicy, build_egress_http_client

policy = EgressPolicy.from_hosts("api.openai.com, api.anthropic.com")

normalized_url, client = await build_egress_http_client(
    "https://api.openai.com/v1", policy=policy
)
async with client:
    resp = await client.get(f"{normalized_url}/models")

Validate without building a client:

from egressweave import EgressPolicy, validate_egress_url, EgressNotAllowedError

policy = EgressPolicy.from_hosts("api.openai.com")
try:
    url = validate_egress_url("https://api.openai.com/v1", policy=policy)
except EgressNotAllowedError:
    ...  # generic, non-leaking rejection

Local development (Ollama-style container name that resolves to a private IP):

policy = EgressPolicy.from_hosts("ollama", allow_local=True)

API

Symbol Purpose
EgressPolicy Injected allowlist config: from_hosts(...), allow_local, dns_timeout_seconds.
validate_egress_url / validate_egress_url_details (+ _async) Validate a URL and resolve pinnable addresses.
build_egress_http_client(url, *, policy) Validate + build a DNS-pinned httpx.AsyncClient.
build_pinned_https_async_client(validated, *, policy) Pin an already-validated URL.
ValidatedEgressURL, EgressNotAllowedError Result type and typed failure (a ValueError).

One source, multi use (OSMU)

egressweave is extracted, behaviour-preserving, from a production control plane (naruon), where it guards every LLM-provider call. It is usable both as a standalone dependency and as a git submodule. The only change on extraction was replacing the app-specific settings object with an injected EgressPolicy.

Version compatibility

The pinned transport uses a few httpx / httpcore internals, so those libraries are constrained to httpx>=0.28,<0.29 and httpcore>=1.0,<2.0 and exercised by the test-suite. Bumping either requires re-verifying the transport.

Research grounding

See docs/research: OWASP SSRF Prevention, CWE-918, CWE-350 (DNS rebinding / TOCTOU), and RFC 8305 (Happy Eyeballs — the concurrent connect used across pinned addresses).

License

Apache-2.0. See LICENSE.

Metadata

Release files for egressweave 0.1.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 egressweave 0.1.0
File Size Uploaded
egressweave-0.1.0.tar.gz 15.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for egressweave 0.1.0
File Interpreter ABI Platform
egressweave-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 30.8 kB

Release files / egressweave-0.1.0.tar.gz

Download URL egressweave-0.1.0.tar.gz
Size 15.7 kB
Tags Source
SHA-256 checksum
How to use checksums
e1e3a6dbabd4084fb03f19a95931ab96e4beeef96bc8fc7cf0d8e5b91e266057
BLAKE2b-256 checksum
How to use checksums
23982302136a7f35d216b4e019d1f3b4b9688bed488a06c1cd9b2022e68aded7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.13

Release files / egressweave-0.1.0-py3-none-any.whl

Download URL egressweave-0.1.0-py3-none-any.whl
Size 15.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
6bcb07109bdee25a6d49e5516f4c99ecd172ffe8536455d2cac860c44a4492f6
BLAKE2b-256 checksum
How to use checksums
6f5aec82b385d2997796fabaaa3f827b4dc0fe46b466d5a99003e3f69d0a0065
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.13

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

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