Egress Guard
Egress Guard is a forward HTTPS proxy that governs every outbound network request your AI agent makes. It ships as an SSG plugin.
SSG's tool-call hook intercepts tool calls before the agent runs them. Egress Guard sits one layer below: it governs the actual HTTP/S connection — including requests made by subprocesses, MCP servers, or prompt-injected payloads that the hook never sees. The allow / block / ask / force vocabulary is the same as SSG; the enforcement point is the wire.
How a request is decided
Each request passes through four steps in order:
- SSRF floor — if the target resolves to a private, loopback, or link-local address (including cloud metadata endpoints), it is blocked immediately in every mode. This check runs before any rule evaluation and again at dial time to close the TOCTOU window.
- Static rules — prefix, exact, or glob URL match with an optional HTTP method filter. Priority among matching rules:
denybeatsaskbeatsallow. - LLM judge (optional, off by default) — a natural-language policy decides requests that no static rule covered.
- Fallback — for anything still undecided:
ask(default),deny, orallow, configurable inconfig.json.
Monitor mode (the default on first run) observes and tracks all traffic but blocks nothing. Switch to enforce mode after you have built up your rule set.
Install the plugin
Egress Guard is distributed through the SSG plugin marketplace — it is not bundled into SSG.
ssg plugins install egress-guard
After install, open the SSG dashboard. Egress Guard appears under Tools in the sidebar. The plugin page shows a live per-domain traffic table and a quickstart panel.
The plugin page is a launcher and live-traffic view. The full UI (rules editor, policy, setup, approvals) is at http://127.0.0.1:8889 — the proxy's own dashboard, served same-origin with its admin API.
Install the proxy binary
The plugin gives you the dashboard view. The proxy engine is a separate binary — build it from source:
# From the egress-proxy project root:
bun run proxy:build # produces go/dist/egress-guard
Or install via your preferred method and put egress-guard on your $PATH.
Install as a systemd user service (Linux):
cp systemd/egress-guard.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now egress-guard
Quickstart
egress-guard up # start the proxy; generates its CA on first run
egress-guard protect claude-code # prints the HTTPS_PROXY + CA env to paste
egress-guard rules suggest # proposed allow-rules from observed traffic + harness preset
egress-guard mode enforce # turn on enforcement after reviewing your rule set
protect prints a copy-paste block for your shell:
export HTTPS_PROXY=http://127.0.0.1:8888
export HTTP_PROXY=http://127.0.0.1:8888
export NODE_EXTRA_CA_CERTS=~/.sigmashake/egress-guard/ca.crt
Paste this into the shell that launches your agent. Supported harness presets: claude-code, cursor, codex, gemini, node, python, git, curl, generic.
Rules
Static rules are stored in ~/.sigmashake/egress-guard/rules.json and evaluated by the rule engine before any LLM judge call.
Rule actions: allow, deny, ask
Match types: prefix (default), exact, glob
Priority when multiple rules match a request: deny > ask > allow
CLI
egress-guard rules allow https://api.github.com
egress-guard rules deny https://api.openai.com
egress-guard rules ask "*.slack.com/*" # glob: pause for approval
egress-guard rules list
Glob syntax
In glob match type, *. matches any number of subdomain labels (so *.example.com also matches example.com). A bare * matches any run of characters. Default port stripping means https://api.github.com:443/ and https://api.github.com/ match the same rule.
Suggested rules
Egress Guard proposes rules from two sources:
- Observed traffic — every domain that has appeared in live traffic with no matching rule, most-active first.
- Harness preset — a curated starter set for the connected harness (for example,
protect claude-codesuggestsapi.anthropic.com, the npm registry, GitHub, and similar). The preset is offered as soon as the harness connects, before any traffic flows.
egress-guard rules suggest # see proposals
egress-guard rules adopt api.github.com # adopt one as allow
egress-guard rules adopt --all # adopt all as allow
egress-guard rules adopt --all --ask # adopt all as ask (approve each on first use)
Adopting a rule only ever adds a rule — it never removes or modifies an existing one. You can also adopt rules from the dashboard's "Suggested rules" panel.
Auto-allowed domains and revocation
When you approve a pending request and tick don't ask again, the decision is persisted as a StaticRule tagged with its origin (approval), approver, and timestamp. Both dashboards show an Allowed domains review listing auto-allowed domains with a one-click Revoke button. Revoking removes the rule so that domain prompts again. The full rules editor in the proxy dashboard shows the Origin column across all allow / deny / ask / force rules.
Optional LLM judge
The judge is off by default and requires no credentials to skip. To enable it:
- Set
llm.enabled = truein~/.sigmashake/egress-guard/config.json. - Set
ANTHROPIC_API_KEYin the environment. - Write a natural-language policy:
egress-guard policy set "Only allow requests to official package registries and source control hosts.". - Restart the daemon (LLM config changes require a restart;
modeandfallbackapply live via the admin API).
Requests that no static rule covers are then sent to the judge.
What is tracked
Egress Guard does not write a per-request log. The durable record is a compact per-domain rollup (domains.json): domain, request count, allowed, blocked, asked, last seen. A bounded in-memory ring powers the live feed in the dashboard. No request headers or bodies are stored.
Data directory
All state lives under ~/.sigmashake/egress-guard/ (override with EGRESS_GUARD_DIR).
| File | Contents |
|---|---|
config.json | Proxy config: ports, mode, fallback, LLM settings, SSRF allowlist |
rules.json | Static allow / deny / ask / force rules |
policy.txt | Natural-language LLM judge policy |
domains.json | Per-domain traffic rollup (the only persisted traffic record) |
connected.json | Connected harnesses (drives rule suggestions) |
ca.crt / ca.key | MITM CA, generated on first run |
admin-token | Loopback admin API token (required for mutations) |
egress-guard.pid | Daemon PID file |
CA material, tokens, and state files are excluded from git by the project .gitignore.
Admin API
The loopback admin API runs at http://127.0.0.1:8889/api/.
Read endpoints (health, domains, recent traffic, setup snippets, CA cert, pending list, rules list) are open without a token — CORS only echoes loopback origins, so web pages outside localhost cannot read them. Mutation endpoints (mode change, rules, policy, approval decisions) require the admin-token.
The SSG dashboard plugin reaches the proxy over this API. The plugin route's CSP allows connect-src http://127.0.0.1:* http://localhost:*, so the plugin works with zero additional configuration.
Security model
- Pure userspace. No kernel module, no eBPF, no root required. The proxy binds loopback by default (
127.0.0.1). - SSRF is an absolute floor. Private, loopback, and link-local targets are blocked before the rule engine runs, and checked again at dial time. They are never forwarded and never parked in the ask queue.
- The proxy terminates TLS. It sees request content in cleartext in order to inspect and govern it. It does not persist bodies.
- Suggestions are additive only.
egress-guard rules adoptand "don't ask again" both useAddRule, which is idempotent onURLPatternand never modifies an existing rule. IP literals, loopback addresses, and private-range targets are filtered from suggestions.
Known limitations
- An
ask-parked request on a CONNECT tunnel waits up torequest_timeout_seceven if the client disconnects before approving, because a hijacked CONNECT tunnel does not propagate client cancellation to the request context. The timeout bounds the wait. - LLM judge configuration changes (
llm.enabled, model, API key) require a daemon restart; mode and fallback changes apply live.
Build from source
bun run proxy:build # Go binary → go/dist/egress-guard (zero external Go deps)
bun run build # plugin bundle → dist/
bun run test:go # Go unit tests: rule matcher, SSRF classifier
bun run test # plugin manifest tests
The proxy binary has zero external Go dependencies (go.mod has no require entries). go build works offline.