Skip to main content

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:

  1. 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.
  2. Static rules — prefix, exact, or glob URL match with an optional HTTP method filter. Priority among matching rules: deny beats ask beats allow.
  3. LLM judge (optional, off by default) — a natural-language policy decides requests that no static rule covered.
  4. Fallback — for anything still undecided: ask (default), deny, or allow, configurable in config.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-code suggests api.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:

  1. Set llm.enabled = true in ~/.sigmashake/egress-guard/config.json.
  2. Set ANTHROPIC_API_KEY in the environment.
  3. Write a natural-language policy: egress-guard policy set "Only allow requests to official package registries and source control hosts.".
  4. Restart the daemon (LLM config changes require a restart; mode and fallback apply 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).

FileContents
config.jsonProxy config: ports, mode, fallback, LLM settings, SSRF allowlist
rules.jsonStatic allow / deny / ask / force rules
policy.txtNatural-language LLM judge policy
domains.jsonPer-domain traffic rollup (the only persisted traffic record)
connected.jsonConnected harnesses (drives rule suggestions)
ca.crt / ca.keyMITM CA, generated on first run
admin-tokenLoopback admin API token (required for mutations)
egress-guard.pidDaemon 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 adopt and "don't ask again" both use AddRule, which is idempotent on URLPattern and 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 to request_timeout_sec even 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.