Skip to content

feat: isolated code execution (IsolatedCodeRunner + backends) — v0.24.0 - #51

Merged
chrishayuk merged 15 commits into
mainfrom
feat/isolated-code-execution
Jul 29, 2026
Merged

feat: isolated code execution (IsolatedCodeRunner + backends) — v0.24.0#51
chrishayuk merged 15 commits into
mainfrom
feat/isolated-code-execution

Conversation

@chrishayuk

Copy link
Copy Markdown
Collaborator

Adds IsolatedCodeRunner — the safe counterpart to CodeSandbox — for running untrusted / LLM-generated code behind a real OS/runtime boundary, with tool access brokered back to the host over a single audited channel (JSON, never pickle).

What's included

  • Core: IsolatedCodeRunner, IsolationLimits, IsolatedResult, the IsolationBackend protocol, and a host-side ToolBroker (per-run token, tool allowlist, call ceiling).
  • Backends: SeatbeltBackend (macOS sandbox-exec), DockerBackend (throwaway container), BubblewrapBackend (Linux namespaces), LocalProcessBackend (no isolation — dev/testing only; refused unless allow_no_isolation=True).
  • Docs: new docs/isolated_execution.md; security.md / programmatic_execution.md now point untrusted code at IsolatedCodeRunner; README/CORE_CONCEPTS/ADVANCED_TOPICS stop presenting the subprocess IsolatedStrategy as a security boundary.

Compatibility

Purely additive — no existing public API changes. from chuk_tool_processor.execution import CodeSandbox does not eagerly load the isolation package. Verified downstream (mcp-cli) imports unaffected.

Verification

  • tests/execution/isolation/ + CodeSandbox tests: 68 passed / 4 skipped (backend integration gated on runtime availability).
  • ruff + mypy clean on the isolation package.
  • On macOS the Seatbelt backend is exercised end-to-end (blocks network, fs-writes, secret reads).

Stacked on the v0.23.0 CodeSandbox fix. Experimental Windows (AppContainer) and WASM backends follow on separate branches.

🤖 Generated with Claude Code

Add an IsolatedCodeRunner that runs untrusted/LLM-generated code behind a real
boundary, with tool access brokered back to the trusted host over a single
audited unix-socket channel — the safe counterpart to the in-process,
trusted-only CodeSandbox.

Core (backend-agnostic):
- IsolationLimits, IsolatedResult, IsolationBackend protocol, GuestJob/Outcome
- ToolBroker: host-side RPC server; owns the registry, enforces a per-run token,
  tool allowlist, and max-tool-calls ceiling; JSON-only wire (never pickle)
- guest_bootstrap: dependency-free guest entrypoint that execs the code with
  async tool proxies and reports the result back over the socket
- IsolatedCodeRunner: ties broker + backend together; refuses non-isolating
  backends unless allow_no_isolation=True

Backends:
- SubprocessBackend base (staging, rlimits, wall-clock kill, output caps)
- LocalProcessBackend: no isolation, dev/testing/reference only
- SeatbeltBackend: macOS sandbox-exec; denies inet network and filesystem
  writes outside the work/tmp dirs, denies reads of well-known secret dirs

Tests cover the wire framing, limits validation, fail-closed behaviour, and the
full core via the local backend; the Seatbelt suite runs on macOS and asserts
the boundary actually blocks network and filesystem access.

Signed-off-by: chris hay <chris.hay@uk.ibm.com>
Build on the isolation core with two OS-level backends behind the same
IsolationBackend protocol:

- DockerBackend: one throwaway `docker run --rm` container per run — no network,
  read-only root, dropped caps, no-new-privileges, memory/pids limits; broker
  socket bind-mounted in. Force-removes the container by (token-derived) name on
  timeout. Works with any docker/podman-compatible CLI.
- BubblewrapBackend: Linux `bwrap` namespace sandbox — read-only system view,
  private tmpfs, fresh /proc + /dev, net unshared unless allowed, only the
  broker socket bound in.

Also make SeatbeltBackend's secret-read denylist configurable
(deny_read_paths / add_deny_read_paths; DEFAULT_DENY_READ_PATHS as the base set)
instead of a hard-coded list.

Backends shell out to docker/bwrap/sandbox-exec (no Python deps). Tests cover
argv construction (pure, run everywhere) and the configurable denylist; full
container/namespace integration is gated on the runtime being present.

Signed-off-by: chris hay <chris.hay@uk.ibm.com>
Add docs/isolated_execution.md: architecture (broker + guest + JSON RPC +
limits), backend comparison and selection, usage, the security/threat model,
the configurable Seatbelt denylist, and how to write a custom backend.

Point docs/security.md's "running untrusted code safely" section and the
programmatic_execution.md CodeSandbox warning at IsolatedCodeRunner as the
concrete answer for untrusted/LLM-generated code.

Signed-off-by: chris hay <chris.hay@uk.ibm.com>
README, CORE_CONCEPTS, and ADVANCED_TOPICS described the subprocess
IsolatedStrategy as the way to run "untrusted"/"LLM-generated code" "safely" and
called it a "security boundary". It is crash/fault isolation only — same OS user,
no seccomp/namespaces, results cross via pickle — and it never executes an
orchestration code string.

Reframe it as crash isolation for tool dispatch and route untrusted/LLM *code* to
IsolatedCodeRunner instead. Add docs/isolated_execution.md and docs/security.md
to the README docs index.

Signed-off-by: chris hay <chris.hay@uk.ibm.com>
…ends)

Bump version to 0.24.0 and add CHANGELOG entry for the isolated code execution
feature (IsolatedCodeRunner + Seatbelt/Docker/bubblewrap/local backends).
Additive — no changes to existing public APIs.

Signed-off-by: chris hay <chris.hay@uk.ibm.com>
Signed-off-by: chris hay <chris.hay@uk.ibm.com>
Signed-off-by: chris hay <chris.hay@uk.ibm.com>
@chrishayuk
chrishayuk force-pushed the feat/isolated-code-execution branch from d9e4c18 to c9c7fd6 Compare July 28, 2026 15:45
The pre-commit ruff was pinned to v0.7.1 while CI resolves ruff 0.15.x via
'uv run ruff', so the two disagreed on formatting and green local commits
could still fail CI's 'ruff format --check .'. Align them.

Signed-off-by: chris hay <chris.hay@uk.ibm.com>
- Add tests/execution/isolation/__init__.py and put repo root on pytest
  pythonpath so 'from tests.…' cross-test imports resolve under the pytest
  console script (CI), not only 'python -m pytest'.
- Set mypy platform=linux so POSIX-only APIs used behind runtime guards
  (os.killpg, resource, asyncio.start_unix_server) don't fail type-checking on
  the Windows runner; Windows-only modules keep their ignore_errors override.

Signed-off-by: chris hay <chris.hay@uk.ibm.com>
- Skip the isolation test modules on Windows: this release's broker uses unix
  domain sockets, so the feature is POSIX-only (Windows support is a later PR).
- Gate Docker/bubblewrap integration tests behind CTP_TEST_ISOLATION_INTEGRATION
  so they don't run in the default matrix (ubuntu runners have Docker; the
  backend integration isn't verified in the mandatory suite).
- Fix the cross-test import to a same-dir 'from test_runner import' (works under
  the pytest console script) and revert the global pythonpath change.
- Keep mypy platform=linux so POSIX APIs behind runtime guards pass on Windows.

Signed-off-by: chris hay <chris.hay@uk.ibm.com>
Signed-off-by: chris hay <chris.hay@uk.ibm.com>
Signed-off-by: chris hay <chris.hay@uk.ibm.com>
…op ALL)

Signed-off-by: chris hay <chris.hay@uk.ibm.com>
…tation

- Add isolation.yml: runs the Docker + bubblewrap integration tests on ubuntu
  (CTP_TEST_ISOLATION_INTEGRATION=1) so the container backend is CI-verified.
- Remove the temporary docker-debug workflow.
- docs: DockerBackend is CI-verified end-to-end on native Linux and runs as the
  host uid; note that Docker Desktop / podman-machine VM file sharing does not
  support the bind-mounted unix socket (ENOTSUP).

Signed-off-by: chris hay <chris.hay@uk.ibm.com>
…loopback)

Docker integration is CI-verified; bubblewrap needs a real Linux host with
unprivileged user namespaces (GitHub runners reject bwrap's loopback RTM_NEWADDR).

Signed-off-by: chris hay <chris.hay@uk.ibm.com>
@chrishayuk
chrishayuk merged commit cafd6ac into main Jul 29, 2026
12 checks passed
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