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
httpto 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 a302cannot 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)
| File | Size | Uploaded | |
|---|---|---|---|
| egressweave-0.1.0.tar.gz | 15.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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
|