Skip to content

feat(run): universal --enforce — OneCLI-owned OS sandbox wraps any agent - #105

Merged
johnnyfish merged 3 commits into
mainfrom
feat/enforce-universal-wrap
Jul 31, 2026
Merged

feat(run): universal --enforce — OneCLI-owned OS sandbox wraps any agent#105
johnnyfish merged 3 commits into
mainfrom
feat/enforce-universal-wrap

Conversation

@johnnyfish

Copy link
Copy Markdown
Contributor

What

onecli run --enforce now works for any agent, not just Claude Code.

The native path borrowed Claude Code's own sandbox, which can never generalize (most agents have no sandbox to borrow). This PR inverts ownership: onecli generates its own Seatbelt profile (deny network-outbound except loopback) and execs sandbox-exec around the entire agent process tree. Every child — bash commands, curl, python, MCP servers, and the agent's own model API calls — inherits the sandbox and has exactly one network path: the loopback auth forwarder fronting the gateway. Direct dials fail at the OS even with proxy env stripped.

Same primitive Anthropic's sandbox-runtime standardizes; ours is the network-governance subset, chained to the gateway where policy/vault/audit live.

Routing

Agent Path
Claude Code native (unchanged) — its own sandbox via --settings
Hermes (Docker tool sandbox) fail closed with explanation
Codex, Cursor, Gemini, anything else wrap (new)

Profile decisions (validated empirically)

  • Unix sockets allowed (local IPC: DNS via mDNSResponder, ssh-agent) except docker.sock — a container runs outside the sandbox, so it would be a one-line egress bypass.
  • All loopback allowed: dev servers keep working; loopback can't leave the machine.
  • lsopen + appleevent-send denied: open/LaunchServices launch apps outside the sandbox (URL-fetch escape).
  • Codex quirk: its internal Seatbelt gets EPERM inside ours → disabled via -c sandbox_mode=danger-full-access, with a user-facing notice (network governance moves to our sandbox, strictly stronger; FS safety falls back to Codex approvals).

Validation

  • Unit tests: profile invariants (incl. rule ordering), env rewrite, argv assembly, quirks.
  • Live darwin test: OS blocks direct egress (instantly, not by timeout), allows loopback, confines grandchildren.
  • Manual e2e against the prod gateway: enforce-wrapped bash — env-stripped direct dial to 1.1.1.1 denied at OS, curl https://example.com via gateway → 200. Codex wrapped: full session through gateway, shell commands working, bypass attempts (env -u, --noproxy) denied.
  • Claude native path regression: onecli run --enforce -- claude -p ... still works.

Not in this PR (phase 1b+)

Linux netns backend, Claude-under-wrap convergence, enroll shims.

@johnnyfish
johnnyfish force-pushed the feat/enforce-universal-wrap branch 2 times, most recently from 78215a0 to 0469372 Compare July 30, 2026 21:39
The native enforce path borrows Claude Code's sandbox and can never
generalize. This adds the wrap path: onecli generates its own Seatbelt
profile (deny network-outbound except loopback) and execs sandbox-exec
around the ENTIRE agent process tree, so any agent — Codex, Cursor,
Gemini, a bare shell — gets OS-enforced gateway egress, including its
own model API calls and every subprocess.

- run_enforce_wrap.go: profile generation, availability checks, proxy
  env rewrite to the loopback forwarder, sandbox-exec argv assembly,
  and a per-agent quirks table (Codex's internal Seatbelt gets EPERM
  inside ours; disabled via -c sandbox_mode=danger-full-access with a
  user-facing notice).
- run.go: --enforce now routes per agent — Claude Code keeps the
  native --settings integration, Docker-sandbox agents (Hermes) fail
  closed with an explanation, everything else gets the wrap. Proxy env
  is repointed before child env/config injection derives from it.
- Profile decisions validated empirically: unix sockets stay allowed
  (local IPC: DNS, ssh-agent) except docker.sock (out-of-sandbox
  egress bypass); all loopback allowed (dev servers; can't leave the
  machine); lsopen + appleevent-send denied (out-of-sandbox URL-fetch
  escapes).
- Live darwin test proves the OS blocks direct egress (instantly, not
  by timeout), allows loopback, and confines grandchildren.
@johnnyfish
johnnyfish force-pushed the feat/enforce-universal-wrap branch from a6593b4 to f095bce Compare July 30, 2026 23:20
golangci-lint (CI lint job) flagged appendAssign: 'probes := append(direct, ...)'
reused direct's backing array. Build the probe list into a fresh, capacity-hinted
slice so the base 'direct' matrix is never aliased or mutated.
TestEnforceWrapArgv asserted the macOS sandbox-exec argv shape, but the
test isn't build-tagged, so it ran on the Linux CI build job where the
sandbox fallback returns a bare argv — failing the build. The OS-launcher
shape is the sandbox package's concern (covered by its darwin tests); the
cmd-level contract is only that agent args are present and per-agent quirk
flags are appended last. Assert that instead, so the test holds on every
platform.
@johnnyfish
johnnyfish merged commit a27b6de into main Jul 31, 2026
2 checks passed
@johnnyfish
johnnyfish deleted the feat/enforce-universal-wrap branch July 31, 2026 01:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant