Skip to main content

SSRF- and DNS-rebinding-safe outbound HTTP client: an egress host allowlist plus a DNS-pinned httpx transport.

Project description

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.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

egressweave-0.1.0.tar.gz (15.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

egressweave-0.1.0-py3-none-any.whl (15.1 kB view details)

Uploaded Python 3

File details

Details for the file egressweave-0.1.0.tar.gz.

File metadata

  • Download URL: egressweave-0.1.0.tar.gz
  • Upload date:
  • Size: 15.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.13

File hashes

Hashes for egressweave-0.1.0.tar.gz
Algorithm Hash digest
SHA256 e1e3a6dbabd4084fb03f19a95931ab96e4beeef96bc8fc7cf0d8e5b91e266057
MD5 95822a4df2e7f26442f702c2be4029ab
BLAKE2b-256 23982302136a7f35d216b4e019d1f3b4b9688bed488a06c1cd9b2022e68aded7

See more details on using hashes here.

File details

Details for the file egressweave-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: egressweave-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 15.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.13

File hashes

Hashes for egressweave-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 6bcb07109bdee25a6d49e5516f4c99ecd172ffe8536455d2cac860c44a4492f6
MD5 4f43c7d4ef471559996e14cec9779483
BLAKE2b-256 6f5aec82b385d2997796fabaaa3f827b4dc0fe46b466d5a99003e3f69d0a0065

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page