ExfilGuard is a Rust egress proxy that enforces per-client policies on outbound HTTP and HTTPS traffic. It accepts explicit proxy connections, evaluates each request against the client's policy, logs the decision, and either forwards or denies the request. When a rule enables inspection, ExfilGuard terminates TLS and mints leaf certificates on the fly using its built-in CA; otherwise it can tunnel the CONNECT stream without touching the payload. Configuration can be reloaded without restarting the process.
-
Start the proxy with the minimal config:
cargo run -- --config examples/quickstart/exfilguard.toml
This config trusts only
127.0.0.1, allows bumped HTTPS requests tohttps://www.searchkit.com/faq/**, and denies everything else. The implicit CONNECT tunnel is only opened for that host/port. All generated material lives under/tmp/exfilguard/quickstart. -
Send a test request through the proxy:
SSL_CERT_FILE=/tmp/exfilguard/quickstart/ca/root.crt \ curl --cacert /tmp/exfilguard/quickstart/ca/root.crt \ -x http://127.0.0.1:3128 https://www.searchkit.com/faq/ --head
The response should be
200 OK. Any other host, path, or client IP is denied.
ExfilGuard is designed as a modular pipeline to ensure security policies are enforced consistently across different protocols.
The entry point that accepts raw TCP connections. It hands off the stream to the Dispatcher.
Determines the protocol (HTTP/1.1, HTTP/2, or CONNECT). If a CONNECT
request is received and the policy requires inspect_payload = true, it
triggers the TLS Bumping flow.
A decoupled module that takes a "Request" (Scheme, Host, Port, Path, Method) and returns a Decision (Allow/Deny).
- Compilation: TOML is compiled into a CIDR Trie and Regex-based Matchers.
- Evaluation: Client resolution happens via Source IP before policy iteration.
An abstraction layer using the RequestHandler trait. This allows the same
policy evaluation logic to be shared by:
- Plain HTTP requests.
- CONNECT tunnels (Splicing).
- "Unwrapped" HTTPS requests inside a bumped tunnel.
Manages a connection pool (UpstreamPool) to reduce latency for outbound
requests, supporting both HTTP/1.1 and H2 multiplexing.
Use the richer sample layout when you need multiple clients, a mix of inspection settings, or hot reload demonstrations:
cp examples/full/exfilguard.toml exfilguard.toml
cargo run -- --config exfilguard.tomlEdit the copied files—or point --config at the originals—to try different
policies.
- Linux — primary production target.
- macOS — supported for development and demos.
- Windows — not supported.
ExfilGuard’s outbound TLS client configuration requires a populated
platform trust store. The process aborts startup when no native anchors are
present rather than silently continuing without any trust material. This is
intentional; if you’re operating in a stripped-down environment (e.g., minimal
containers), install the standard CA bundle (ca-certificates or equivalent) so
outbound TLS verification remains intact.
src/cli.rs— entry point and runtime flags.src/config/— config schema and loaders.src/policy/— policy compiler and matcher.src/proxy/— listeners, HTTP handlers, and upstream clients.src/tls/— CA lifecycle plus the on-disk leaf cache.examples/— ready-to-run configs (quickstart/,full/).
- The listener accepts TCP connections on the configured address.
- Protocol front-ends parse HTTP/1.1 requests or CONNECT tunnels (HTTP/2 support
lives in
proxy::http2). - The request pipeline normalizes the request and evaluates the relevant client's policies in order until one matches.
- Allowed requests are proxied upstream with pooled TCP/TLS clients; denied requests receive a 403 and a structured log entry.
ExfilGuard controls what outbound traffic is allowed and logs everything for review. It blocks requests to destinations not on your allow list and records any blocked attempts.
Does:
- Block outbound requests to hosts/paths you haven't allowed
- Log all traffic (allowed and blocked)
- Drop slow or stalled connections via timeouts
Doesn't:
- Inspect request or response bodies
- Stop data from leaving through allowed destinations
- Detect compromised clients misusing allowed routes
The proxy doesn't fully trust clients or upstreams. Malformed requests, slow connections, and oversized headers/bodies are rejected early. Parsing is strict and policy checks run before any upstream connection is made. The goal is that a misbehaving or malicious client can't bypass rules or degrade the proxy for others.
- Use the
.debpackage for production—it creates a dedicated user and sets sane defaults. - Run as a non-root user and only listen on the interfaces you need. Put a firewall in front.
- Lock down
ca_dirwith strict file permissions. Back up and rotate the CA files regularly. - Review timeout and size limits for your setup (see
docs/configuration.md). - Set
max_request_body_size,max_request_header_size, andmax_response_header_sizeto reasonable values. - Leave
allow_private_upstream = falseunless you need to reach private IPs. - Treat logs and metrics as sensitive—they contain internal hostnames and can reveal unusual traffic patterns.
- WebSocket and HTTP/1.1 Upgrade flows are not supported; upstream
101 Switching Protocolsresponses are rejected rather than tunneled. - HTTP/1.0 requests and upstream HTTP/1.0 responses are rejected; ExfilGuard only supports HTTP/1.1 framing.
Each policy rule declares whether ExfilGuard bumps TLS:
inspect_payload = true(default) terminates TLS so the proxy can enforce scheme, host, path, and method checks—and log bodies if needed. HTTPS rules implicitly authorize CONNECT bumping for the same host/port.inspect_payload = falseonly enforces scheme/host/port. These rules must usemethods = ["CONNECT"]and aurl_patternending in/**, making it clear that the intent is to tunnel the host untouched. Use this for pinned TLS or non-HTTP payloads that cannot tolerate MITM.
Example from examples/full/policies.toml:
[[policy]]
name = "trusted-analytics-egress"
[[policy.rule]]
action = "ALLOW"
methods = ["GET", "POST"]
url_pattern = "https://api.trusted-analytics.com/v1/exports/**"
[[policy]]
name = "pinned-payments-egress"
[[policy.rule]]
action = "ALLOW"
methods = ["CONNECT"]
url_pattern = "https://secure.partner.com/**"
inspect_payload = false
allow_private_upstream = trueThe loader aborts if the inspection settings are inconsistent (for example:
inspect_payload=false is not allowed for DENY action).
- ExfilGuard maps the request's downstream address to a client. Non-fallback selectors must not overlap, so the match is unambiguous.
- If no selector matches, the
fallbackclient is used. - It evaluates that client's policies in-order; the first matching rule wins.
- Rules that disable inspection still use the same logging path—they simply skip the TLS bump step and stream bytes once allowed.
- When
--configis omitted, ExfilGuard looks for/etc/exfilguard/exfilguard.tomlbefore falling back to./exfilguard.toml. - Relative paths in the config are resolved from the directory that contains the config file, which keeps packaged installs self-contained.
clients_dirandpolicies_dircan point at optional*.ddirectories. Every.tomlfile in those directories loads in alphabetical order after the base file. Names must be unique across all files.
Exactly one client must set fallback = true. That client handles requests that
do not match any specific IP or CIDR. Config loading fails if zero or multiple
fallback clients exist.
--ca-dir and cert_cache_dir hold the root, intermediate, and leaf
material. Files are written with 0o600, but you must also secure the
directories (for example chmod 700). Anyone who can read them can mint
certificates or impersonate the proxy, so run ExfilGuard as an unprivileged user
and store the CA on trusted disks.
-
Primary integration test (spins up the proxy on loopback):
cargo test --test bump_integration -
Full suite (integration + unit tests):
cargo test
ExfilGuard fuzzes critical untrusted input paths (HTTP/1 request/response
parsing, chunked bodies, HTTP/2 request sanitization, CONNECT targets). Targets
live under fuzz/fuzz_targets/.
Run a target:
cargo fuzz run http1_request_headParallel workers example:
cargo fuzz run http1_request_head -- -jobs=8 -workers=8-
Install the pre-commit hook to enforce formatting locally:
ln -sf ../../hooks/pre-commit .git/hooks/pre-commit
- Build a release binary:
cargo build --release. - Install the helper (once):
cargo install cargo-deb. - Produce the package:
cargo deb(outputs totarget/debian/exfilguard_<version>_<arch>.deb). - Install with
sudo dpkg -i exfilguard_<version>_<arch>.deb, edit/etc/exfilguard/*.toml, and enable the service withsudo systemctl enable --now exfilguard.
Need an isolated build environment? packaging/deb-container/ provides a
Docker-based workflow that installs Rust, cargo-deb, and all system
dependencies inside Ubuntu 22.04. See that directory's README for usage.
The package installs /usr/sbin/exfilguard, sample configs in /etc/exfilguard/,
a writable CA/cache directory under /var/lib/exfilguard/, and a
systemd unit (exfilguard.service). The binary reads
/etc/exfilguard/exfilguard.toml by default, and logs go to journald unless you
override the config or set EXFILGUARD__* environment variables.
Set metrics_listen = "127.0.0.1:9090" in your config to expose Prometheus
metrics at /metrics. Provide metrics_tls_cert and metrics_tls_key to serve
the endpoint over HTTPS. The listener is intended for internal use only—keep it
firewalled to your Prometheus (or front it with an authenticating reverse
proxy) because the metrics include internal hosts and policy decisions.
examples/full/shows both inspect and pass-through rules plus a multi-client layout.
ExfilGuard is available under the terms of the Apache License, Version 2.0. See
LICENSE for the full text.