← Docs

Enforce mode

Crawdad 1.6 adds a runtime-governance axis, enforcement_mode, on top of detection. In Monitor (the default) Crawdad inspects and blocks on detection but changes nothing about your operating system. In Enforce it installs a persistent OS default-deny egress lock so a governed agent can reach only the Crawdad proxy, everything else is denied by construction. This guide covers what each control does, how to turn it on safely, and how to recover.

Two different "modes". enforcement_mode (Monitor vs Enforce, this page) is a separate axis from the detection-strictness modes shown in the dashboard header (Maximum / Standard / Reduced / Paused). The strictness "Maximum" is the most aggressive blocking level; the Enforce "Maximum tier" below is the sealed-VM Workspace. They are unrelated settings that unfortunately share the word.

Monitor vs Enforce

 Monitor (default)Enforce
Detection & auditOnOn
OS network changesNone, stock device unchangedPersistent default-deny egress lock
How the agent reaches the proxyBase-URL env var (a door it may decline to use)The only path off the machine (all else denied)
Credential brokerOptionalMandatory
Per-tool policyEvaluatedEnforced at the tool-call boundary
Survives a sidecar killn/a (nothing installed)Yes, the OS lock persists

Monitor is exactly Crawdad's historical behavior, so upgrading to 1.6 changes nothing until you opt a device (or a fleet) into Enforce.

Platform support

PlatformEnforce egress lockMechanism
LinuxYes, proven against a live kerneliptables default-deny (incl. UDP/QUIC), scoped to the interception cgroup
macOSYespf default-deny, scoped to a dedicated agent UID
WindowsNo OS lock todayThe mandatory credential broker still applies; the lock is honestly reported absent

Under Enforce the lock passes only the local Crawdad proxy ports and denies everything else, including the management (7749) and dashboard (7750) ports, so a governed agent cannot reach the endpoint that could turn the lock off. UDP/QUIC is denied along with TCP.

Turning on Enforce

The primary way to see and drive enforcement is the Enforcement view in the device dashboard (localhost:7750) — a dual-mode control surface (Simple for the one-glance posture, Rich for every signal, the per-tool risk matrix, and the live audit stream). It reads the true state straight from the sidecar and carries the controls; the API and on-disk paths further down are the same thing under the hood.

From the dashboard's Enforcement view:

The steps below are the underlying admin API and on-disk equivalents — useful for scripting, headless setup, or fleet automation.

1. macOS only: set a dedicated agent UID

On macOS the pf lock is scoped to a dedicated account so it never strangles your login user. Set the account's numeric UID in config.json in the sidecar data directory. Without it, Enforce reports NotScoped (no lock installed) and applies only the mandatory broker.

{ "enforce": { "agent_uid": 802 } }

Linux scopes by the interception cgroup instead and needs no UID. Windows has no OS lock.

2. Flip the mode (live, via the admin API)

The mode API applies the OS posture immediately. It is administrator-gated, a token-less or operator-only caller is rejected with 403, so a compromised agent cannot flip it. Authenticate with the device administrator token (rotate/show it with crawdad rotate-token).

curl -X POST http://localhost:7750/api/v1/mode/enforcement \
  -H "Authorization: Bearer <admin-token>" \
  -H "Content-Type: application/json" \
  -d '{"mode":"enforce","tier":"standard"}'

The response reports what was actually applied:

{
  "ok": true,
  "enforcement_mode": "enforce",
  "tier_configured": "standard",
  "tier_in_force": "standard",
  "egress_lock": "egress LOCKED via iptables (default-deny; proxy path only)",
  "egress_locked": true,
  "credential_broker_mandatory": true
}

Pass "mode":"monitor" to tear the lock back down. Add "pin":true to also write the fleet pin (see below).

3. Or set it on disk (applied at startup)

The sidecar reads enforcement_mode and enforce_tier from protection_mode.json at startup and applies the posture then. Files that predate 1.6 have no such keys and load as Monitor, so the upgrade is backward-compatible.

{ "enforcement_mode": "enforce", "enforce_tier": "standard" }

4. Fleet pin (optional)

A fleet can pin Enforce through the console. The pin is a tamper-independent breadcrumb: if protection_mode.json is deleted or tampered so enforcement_mode falls back to Monitor, a pinned device coerces itself back up to Enforce (fail-closed) and logs a tamper event, and a local request to downgrade to Monitor is refused until the fleet pin is changed.

The egress lock

Under Enforce, the egress lock is a persistent OS firewall rule (pf on macOS, iptables on Linux) that denies all outbound traffic by default and allows only the governed agent's connections to the local Crawdad proxy ports. There is no path off the machine except through Crawdad, and that includes UDP/QUIC, so an agent cannot switch to an HTTP/3 endpoint to slip past a TCP-only rule.

The lock persists across a sidecar kill. On Linux this has been verified against a live kernel, including kill -9 persistence and clean removal. That is the point: the boundary must not disappear the moment the process protecting it dies. Removing the lock is a deliberate, authenticated break-glass operation, never something the agent (or a compromised local process) can do.

Maximum tier, the sealed-VM Workspace

Maximum is an opt-in tier under Enforce. Instead of locking host egress with firewall rules while the agent runs on the host (Standard tier), Maximum runs the agent inside a sealed VM whose only network interface routes through Crawdad's host network stack. Containment is then by construction, an attempted escape is refused at the packet level rather than by a deny rule. It is available on macOS/arm64 today (Apple's Virtualization.framework).

Enabling Maximum

  1. Install the Workspace VM components next to the sidecar binary (launcher + host network stack + seatbelt profile). These ship with the macOS build.
  2. Provision the guest assets: crawdad ws fetch (downloads the guest kernel and initramfs).
  3. Select the tier: POST /api/v1/mode/enforcement with {"mode":"enforce","tier":"maximum"}, or set "enforce_tier":"maximum" in protection_mode.json.
Liveness-gated and fail-closed. At startup and on every mode read, Crawdad runs a read-only probe (it binds no ports and boots no VM) to confirm the launcher, host stack, seatbelt profile, and guest assets are all present and runnable on a supported platform. Maximum reports tier_in_force = maximum only when the probe passes. If any component is missing, Enforce falls back to the Standard egress-lock floor and reports tier_in_force = standard with a reason, the configured tier is not silently dropped, so a device requesting Maximum is never less locked than Standard. Check GET /api/v1/mode for tier_configured vs tier_in_force.

fail_mode, what happens when Crawdad can't inspect

fail_mode governs one narrow case: the sidecar is alive but cannot inspect a particular request (for example a message body it cannot parse). It does not govern a dead sidecar, that case is covered by the egress lock under Enforce.

ValueBehavior when a request can't be inspected
fail_closed (default)Block with 422 and an attributable error, rather than forward uninspected.
fail_openLegacy behavior, forward the request anyway.

Set it in config.json, or override with the CRAWDAD_FAIL_MODE environment variable. It is fail-closed by construction: a missing, corrupt, or unrecognized value all resolve to fail_closed, a broken config never silently opens the fail path.

{ "fail_mode": "fail_closed" }

Break-glass, recovering from Enforce

Because the egress lock survives a sidecar kill, there is a dedicated recovery path, crawdad enforce. It is deliberately hard to reach: every operation requires root (sudo) and the device administrator token, the agent can never invoke it, and every use is written to the tamper-evident audit chain (a linear hash chain, reconciled from an offline spool if the sidecar was down when you ran it).

CommandWhat it does
enforce recoverPreferred. Restart the sidecar into a time-boxed bounded-Monitor posture, detection and audit stay on, the egress lock comes off. Enforce auto-reverts at the window's expiry (or on the next boot past it).
enforce releaseLast resort, dead-sidecar only. Drop the egress lock now with the sidecar down. Refused while the sidecar is alive, use the mode API or recover instead.
enforce reenforceEnd a recovery window early, clear the override and restart into Enforce.
enforce statusShow the break-glass window and egress-lock state.
sudo crawdad enforce recover --window 30 --reason "debugging egress issue" \
  --admin-token-file /path/to/admin.token
The recovery window. Default 60 minutes; the hard maximum is 240 minutes and cannot be exceeded regardless of policy. A fleet/tenant can set a lower ceiling via config.json ({"breakglass":{"max_window_secs":N}}); the effective maximum is the lower of the two. Recovery never silently downgrades protection, detection and audit keep running, and Enforce re-asserts automatically at expiry.
enforce release leaves the agent unprotected. A direct offline release removes the OS egress lock while the sidecar is down, so the agent runs with no enforcer, no egress lock, and no detection or audit until the sidecar restarts (a restart re-installs the lock, because enforcement_mode is still Enforce). Prefer enforce recover, which keeps protection on.

Verifying state

sudo crawdad enforce status
curl -s http://localhost:7750/api/v1/mode -H "Authorization: Bearer <admin-token>"

The enforcement block of GET /api/v1/mode reports the live posture, including tier_configured vs tier_in_force, the egress-lock status string, and whether the credential broker is mandatory. At startup the sidecar also logs a one-line honest posture summary. You can also read it at a glance in the dashboard's Enforcement view, which shows the same posture under a colour law where green is reserved for Enforce with the egress lock engaged — so a Windows device under Enforce reads amber (“Broker-only”) by design, since Windows has no OS egress lock and the mandatory credential broker is the ceiling there. Fleet-wide, the fleet console's Fleet posture view rolls this up across every device (read-only in 1.6).

Before you uninstall

Under Enforce, the OS egress lock is designed to persist, so removing the sidecar without releasing it first can leave a default-deny rule in place. Return the device to Monitor first, either POST /api/v1/mode/enforcement with {"mode":"monitor"} while the sidecar is alive, or sudo crawdad enforce release if it is already stopped, and then run the uninstaller.