egress-guard
Deterministic, dependency-free detection of two things a task run inside a sandbox has no legitimate reason to do:
- reaching for the host — the container runtime socket, host memory, the cloud metadata endpoint, the orchestrator's credentials; and
- contacting a host it never declared — any external host outside the run's own egress allowlist.
It is defensive. It recognises these patterns in order to flag them and stop a run. It performs none of them, and it executes nothing it inspects — every rule is a regular expression over the text of an agent's tool calls and transcript.
from egress_guard import run_detectors
hits = run_detectors(tool_calls, egress_allowlist=["*.example.com"], messages=transcript)
for hit in hits:
print(hit.category, hit.rule, hit.evidence)
The one rule that matters
Internal is fine; external is not.
A run whose task is to probe or attack its own declared target inside the sandbox is working correctly and must never be flagged — otherwise the guard gets switched off, which is worse than no guard. The alarm is only for reaching outside the declared boundary. So the caller's egress allowlist is not just the enforcement boundary; it is the definition of "external" that the library tests against. With an empty allowlist, every external host is flagged — the correct default for a run that declared no egress.
Offensive-shaped tooling is judged by where it points, not by its name: the same scanner aimed at a declared internal target and at a public address are different events, and only the second is a hit.
Why deterministic, not a model
These behaviours are not subtle. They are enumerable, and a named rule for each has no false-negative rate from model weakness and needs no calibration. That is the opposite regime from catching subtle deception, where rules are useless and you need a model. Use the right instrument for each: this is the cheap, exhaustive one for the blatant cases.
Evasion is handled before matching
Anything that changes a command's spelling without changing its effect is normalised first, so a literal rule sees through it:
| written as | normalised to |
|---|---|
curl http://2852039166/ |
169.254.169.254 (integer-encoded IP) |
curl http://0xa9fea9fe/ |
hex-encoded IP |
n''m''a''p -p- … |
nmap … (shell-quote splitting) |
echo <base64> | base64 -d | sh |
the decoded payload, appended |
API
run_detectors(tool_calls, egress_allowlist=(), messages=())→list[Hit]— the main entry point; scans both what the agent did and what came back, and labels anything found only in the transcript as a mention rather than an action.detect_host_escape(tool_calls)→list[Hit]detect_undeclared_egress(tool_calls, egress_allowlist=())→list[Hit]is_internal(host)→boolnormalise(text)→str
Each Hit carries a detector_id, a category (sandbox_escape or external_attack),
a human-readable rule, the matched evidence, and the index of the tool call it was
found in.
The names run_containment_detectors, detect_sandbox_escape, and
detect_external_attack are kept as aliases for callers migrating from an in-tree copy.
Scope, honestly
This is a tripwire, not a sandbox. It tells you an agent reached for the exit; it does not contain anything, and a determined adversary who knows the exact rules can craft around them. Its value is catching the blatant, enumerable cases cheaply and without a false-negative rate — pair it with a real sandbox and, for subtle cases, a model-based monitor. Do not rely on it as your only boundary.
Licence
Apache-2.0. Built for AnyEval; useful anywhere agent traces need a cheap, deterministic boundary check.
Metadata
Release files for egress-guard 1.0.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 | Provenance | Actions |
|---|---|---|---|---|
| egress_guard-1.0.0.tar.gz | 15.4 kB | Without provenance | Details for egress_guard-1.0.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| egress_guard-1.0.0-py3-none-any.whl | Python 3 | none | any | Details for egress_guard-1.0.0-py3-none-any.whl |
Total release size: 27.7 kB
Release files / egress_guard-1.0.0.tar.gz
| Download URL | egress_guard-1.0.0.tar.gz |
|---|---|
| Size | 15.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4f2fd8937e7972e6a1c8d9e9af342f50458a9ef7d821e2de1035244ad6826f92
|
|
BLAKE2b-256 checksum How to use checksums |
0525d16fd1e38c1de608b710680f4e932222b5506598996a69641e021365bd6c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.3 {"installer":{"name":"uv","version":"0.12.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / egress_guard-1.0.0-py3-none-any.whl
| Download URL | egress_guard-1.0.0-py3-none-any.whl |
|---|---|
| Size | 12.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
cc486210bd233ac255f374daf644a9236e8bce5a542caa73b9357d6478085799
|
|
BLAKE2b-256 checksum How to use checksums |
823317a55950dd3a0cb3b0412a3f8090366ae5e384ce01e454c3bebbfe734978
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.3 {"installer":{"name":"uv","version":"0.12.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Security for egress-guard 1.0.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.
No provenance found
None of the files in this release were uploaded with provenance metadata.
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.