Metadata-Version: 2.4
Name: egress-guard
Version: 1.0.0
Summary: Deterministic, dependency-free detection of sandbox escape and undeclared external-host contact in agent traces. Defensive: it flags patterns, it never runs them.
License: Apache-2.0
Project-URL: Homepage, https://anyeval.com/standard
Project-URL: Source, https://github.com/AnyEvalOrg/egress-guard
Keywords: agent,sandbox,monitoring,safety,evaluation,guardrail
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# 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.

```python
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)` → `bool`
- `normalise(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](https://anyeval.com); useful anywhere agent traces need a
cheap, deterministic boundary check.
