Skip to content
MagicFrog

Network Egress

How agent runs reach the network — direct, frp reverse tunnel, and client-proxy modes with fail-closed session scoping.

Every agent run's network traffic leaves through an egress mode chosen for that session. The three modes are direct, frp, and client-proxy (implemented in packages/egress and governed by the EgressState state machine).

Egress is scoped to a session: the mode binds to the session, switching modes is an explicit lifecycle change, and failure never degrades silently.

Egress modes

Mode Transport When to use
direct The executing machine's existing route (no tunnel). Machines with a public IP or otherwise reachable network.
frp An frp v0.71.1 reverse tunnel (TCP proxy). Enrolled devices behind NAT that need inbound reachability.
client-proxy SOCKS5 through a selected client as the network exit. Tools like browsers that should use a specific client's network.

How a mode is selected

selectBestMode(hasPublicIp, hasFrpServer, hasRelayNode):
  hasPublicIp → direct
  hasFrpServer → frp
  hasRelayNode → client-proxy
  otherwise → direct (outbound always works; inbound via the enrollment channel)

The mode can also be pinned by configuration:

MAGICFROG_EGRESS_MODE=client_proxy   # or "direct" (default when unset)
MAGICFROG_CLIENT_ID=<client id>      # defaults to the computer id
MAGICFROG_FRPS_ADDR=127.0.0.1
MAGICFROG_FRPS_PORT=7000
MAGICFROG_FRPS_TOKEN=<token>

The tunnel endpoint is published as socks5://<frps host>:<remotePort> once healthy.

Fail-closed session scoping

The EgressState state machine has four states — direct, switching, client_proxy, failed — with these transitions:

From To When
direct switching An egress switch is requested, guarded by the target client-proxy being online and authorized.
switching client_proxy The tunnel is established and the health check passed; browser session/context binds the new egress (explicit lifecycle change).
switching failed Tunnel failure, client offline, or policy denial — no fallback to direct, the failure is explicit.
client_proxy switching An explicit mode or client change; the old tunnel is destroyed before a new one is built, the old egress is never reused.
client_proxy failed Tunnel down, grant revoked, or token expired — existing connections through the proxy are terminated.

The failed state is absorbing: there is no in-place recovery edge. Re-establishing on a failed session is an explicit error — the only way back to direct is destroying and rebuilding the egress session (a new session number), never a silent in-place transition.

Browser session binding

Browser sessions bind the mode at creation. A client_proxy session launches the browser with --proxy-server=socks5://host:port (or http://), while an explicit direct session launches without a proxy argument — that is a configured mode, not a fallback. While switching or failed, the browser refuses to bind without a stable egress: bindings carry the tunnel epoch, and a stale binding is refused (isCurrent guard).

If the tunnel dies mid-session, the browser keeps its now-dead proxy address: Chromium fails connections, it never bypasses the proxy to direct on its own.

Switching egress

Switching is an explicit lifecycle change driven through PATCH /v1/runs/{runId}/egress:

  • Request: { "mode": "direct" | "client_proxy", "clientId": "..." } — clientId is required when mode is client_proxy (400 otherwise).
  • Errors are typed and explicitly surfaced: 400 missing clientId, 403 unauthorized, 410 client offline (fail-closed, no fallback), 504 tunnel establish deadline.

Guards are validated before leaving the current state: a rejected request leaves the healthy session untouched. Once a switch is committed, any failure lands in failed — never back in the old egress.

Revocation timing

Revoking a client's egress grant takes effect within 5 seconds: frpc is killed with SIGTERM first, SIGKILL after a 2-second grace, with a hard 5s bound — the egress machine enters failed, and every connection routed through that proxy is terminated. Existing connections are proactively terminated, not left to time out on their own.