cli-guard examples/egress

NAME

egress-demo - show the CONNECT-proxy allowlist gate

SYNOPSIS

egress-demo

DESCRIPTION

egress-demo exercises the per-invocation HTTP CONNECT proxy that cli-guard stands up for the duration of a wrapped subprocess. The child inherits HTTPS_PROXY / HTTP_PROXY pointing at a local proxy on 127.0.0.1:. The proxy logs every CONNECT, joins the rows back to the parent invocation's audit row, and either enforces a pinned allowlist or observes silently.

Two modes:

- ModeEnforce: per-binary allowlist pinned in code. Denied
  CONNECTs return HTTP 403 from the proxy. The row is marked
  decision=deny. Used for package-manager wrappers (brew, npm,
  pip, cargo, ...) where the upstream registry set is small,
  stable, and high-value.
- ModeObserve: no allowlist, every CONNECT is forwarded and
  logged. Used for aws, gh, kubectl, docker, tailscale - tools
  whose upstream surface is too broad to enumerate but whose
  every network reach is still worth auditing.

What this gate is and is not:

- CONNECT-only. No TLS interception, no CA install, no payload
  inspection. The proxy reads the host:port from the CONNECT
  request line and decides accept/deny on hostname alone.
- Plaintext HTTP through the proxy is rejected with 405. Any
  tool that depends on plaintext fetching is already a bug, and
  we do not want to be the layer that silently downgrades it.
- Not a sandbox. A determined hostile process can resolve a host
  directly, bypass the env proxy hint, or use a non-HTTP
  transport. The gate stops the dumb cases (npm post-install
  curls a remote shell), records the rest, and pairs with the
  lockdown deny rules and audit log to make the rest expensive
  to hide.

Operating model for an agent calling these commands:

- On a 403, the child saw a normal HTTP failure. Do not retry.
  The upstream is unreachable by design; the right escalation is
  "the allowlist does not cover <host>, here is the egress row,
  operator decide". An allowlist edit is a code change, not a
  runtime flag.
- Egress rows are emitted to stderr at proxy.Stop() time in
  these examples for inspection. In production they fold into
  the parent audit row.

Usage:

egress-demo [GLOBAL OPTIONS] [command [COMMAND OPTIONS]] [ARGUMENTS...]

COMMANDS

allowed

dial a host that is on the allowlist

denied

dial a host that is not on the allowlist

observe

log every host without enforcing