Skip to content

feat: process sandbox via firejail (closes #17) - #19

Merged
Protocol-zero-0 merged 2 commits into
mainfrom
feat/issue-17-firejail-sandbox
May 13, 2026
Merged

feat: process sandbox via firejail (closes #17)#19
Protocol-zero-0 merged 2 commits into
mainfrom
feat/issue-17-firejail-sandbox

Conversation

@Protocol-zero-0

Copy link
Copy Markdown
Owner

Summary

  • Adds an OS-level guarantee that the executor cannot write outside its assigned git worktree.
  • New evolution_kernel/sandbox.py (SandboxConfig + pure wrap_argv()); only the executor invocation is wrapped — planner and evaluator are read-mostly and unaffected.
  • New YAML block (sandbox.enabled / backend / extra_args), default disabled so v0.3 byte-compatibility is preserved.
  • Single-dependency rule preserved (still only PyYAML).
  • E2E demo at examples/sandbox_demo/ and 16 new tests (real firejail subprocess + sanity check that confirms the OFF path still leaks).

Closes #17.

Test plan

  • python3 -m pytest tests/ -q83 passed (was 67; +16 new).
  • End-to-end demo with sandbox ON:
    • bash examples/sandbox_demo/setup.sh
    • python3 -m evolution_kernel.cli --config examples/sandbox_demo/evolution.yml --repo /tmp/sandbox-demo-target --ledger /tmp/sandbox-demo-ledger
    • executor_output.json shows outside_write_ok: false with OSError: [Errno 30] Read-only file system.
    • /tmp/sandbox-leak-0001.txt does not exist on disk.
  • Sanity check with sandbox OFF on the same fixture: outside write does succeed and the leak file is created, proving the test is real.
  • All historic acceptance, scope, hard-stops, PR4, issue#10, and issue#14 tests still pass unchanged.

🤖 Generated with Claude Code

Adds an OS-level guarantee that the executor role cannot write outside
its assigned git worktree. The kernel still relies on the existing
worktree + scope check for protecting evolution/accepted, but it can now
also prevent a misbehaving executor from leaking writes to /tmp, ~/.ssh
or anywhere else during an experiment.

Design

- New `evolution_kernel/sandbox.py`: pure-stdlib `SandboxConfig` plus a
  single `wrap_argv()` function that prepends a firejail launcher
  (`--quiet --noprofile --read-only=/ --read-write=<worktree>
  --read-write=<run_dir>` and any user `extra_args`) before the role's
  argv. Disabled is bit-for-bit identical to v0.3.
- `evolution_kernel/config.py`: new `sandbox` YAML block (default off).
- `evolution_kernel/governor.py`: `Governor` accepts a `sandbox` kwarg
  and only the **executor** invocation is wrapped. Planner and
  evaluator are read-mostly and remain unsandboxed.
- `evolution_kernel/cli.py`: forwards `cfg.sandbox` into the governor.

Tests

- `tests/test_pr7a.py` (16 new tests, all green):
    - config parsing (defaults, validation errors)
    - `wrap_argv` unit tests (disabled, enabled, extra_writable dedup,
      extra_args placement, unsupported backend)
    - end-to-end with a real firejail subprocess: an executor fixture
      that tries to write both inside the worktree and outside it.
      Sandbox ON → outside write fails with OSError, escape file
      absent on disk. Sandbox OFF → outside write succeeds (sanity).
- Whole suite: 83 passed (was 67).

Example

`examples/sandbox_demo/` ships a runnable demo:
`bash examples/sandbox_demo/setup.sh` initializes a target repo, then
`evolution-kernel --config examples/sandbox_demo/evolution.yml ...`
runs a single round that writes EVOLUTION_MARKER.txt inside the
worktree and proves firejail blocks the planted /tmp escape attempt.

Single-dependency rule preserved (still only PyYAML).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 13, 2026 20:34
Without it, TestSandboxBlocksEscape skips via the @skipUnless guard and
CI gives a false sense of coverage. Installing firejail from the Ubuntu
universe is a one-line apt step.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@Protocol-zero-0
Protocol-zero-0 merged commit f41b55f into main May 13, 2026
4 checks passed
@Protocol-zero-0
Protocol-zero-0 deleted the feat/issue-17-firejail-sandbox branch May 13, 2026 20:36

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an OS-level process sandbox (via firejail) for the executor role so an experiment cannot write outside its assigned worktree, complementing the existing post-hoc scope check. The new wrapping is opt-in (sandbox.enabled: false by default) to preserve v0.3 byte-compatibility.

Changes:

  • New evolution_kernel/sandbox.py providing SandboxConfig + a pure wrap_argv() (currently only firejail backend).
  • Config parsing + Governor plumbing so the executor invocation (in both serial run_once and per-branch parallel path) is wrapped with firejail --read-only=/ plus read-write on the worktree and the run's ledger dir; planner/evaluator unchanged.
  • E2E demo under examples/sandbox_demo/ plus a new tests/test_pr7a.py covering config parsing, wrap_argv units, and a real-firejail end-to-end test (with sandbox-off sanity check).

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
evolution_kernel/sandbox.py New module; SandboxConfig dataclass and wrap_argv firejail wrapper.
evolution_kernel/config.py Adds sandbox field to EvolutionConfig and _parse_sandbox validation.
evolution_kernel/governor.py Stores sandbox config and applies it to the executor invocation in both run_once and _run_single_branch via _run_role(sandbox=...).
evolution_kernel/cli.py Wires cfg.sandbox into the constructed Governor.
tests/test_pr7a.py New unit + E2E tests for config parsing, wrap_argv, and firejail-backed escape blocking.
tests/fixtures/executor_escape_attempt.py Executor fixture that attempts both an in-worktree and an out-of-worktree write.
examples/sandbox_demo/{setup.sh,evolution.yml,target/bots/*.py} Runnable demo of the sandbox feature with planner/executor/evaluator role scripts.
README.md, README.zh.md Documents the new sandbox block, marks the sandbox roadmap item complete, and updates Qwen3-7B→8B references.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +597 to +606
if sandbox is not None and sandbox.enabled:
# Allow writes to the run's ledger directory so the role can persist
# its --output JSON, plus any role-declared stdout/stderr capture
# next to it. Everything else stays read-only under the sandbox.
argv = sandbox_wrap_argv(
argv,
worktree=worktree,
writable=[output_path.parent],
config=sandbox,
)
Comment on lines +21 to +25
sandbox:
enabled: true
backend: firejail
# extra_args is for advanced operators; defaults are fine for this demo.
extra_args: []
Comment on lines +359 to +363
if not isinstance(entry, str) or not entry.strip():
raise ConfigError(
f"`sandbox.extra_args[{index}]` must be a non-empty string"
)
extras.append(entry.strip())
Comment on lines +90 to +97
seen = {worktree_abs}
for w in writable:
abs_path = str(Path(w).resolve())
if abs_path in seen:
continue
seen.add(abs_path)
prefix.append(f"--read-write={abs_path}")
prefix.extend(extra_args)
Comment on lines +28 to +36
planner:
- "/usr/bin/python3"
- "bots/planner.py"
executor:
- "/usr/bin/python3"
- "bots/executor.py"
evaluator:
- "/usr/bin/python3"
- "bots/evaluator.py"
Comment thread README.md
| Goal evaluator — stops when mission is "won" | ✅ |
| k-branch parallel exploration (FunSearch / AlphaEvolve style) | ✅ |
| Process sandbox (firejail / bwrap) for production safety | 🔧 PR #7 |
| Process sandbox via firejail — executor cannot write outside its worktree | ✅ |
Comment thread tests/test_pr7a.py
Comment on lines +213 to +222
self.escape.parent.mkdir(parents=True, exist_ok=True)
self._old_env = os.environ.get("ESCAPE_TARGET")
os.environ["ESCAPE_TARGET"] = str(self.escape)

def tearDown(self):
if self._old_env is None:
os.environ.pop("ESCAPE_TARGET", None)
else:
os.environ["ESCAPE_TARGET"] = self._old_env
self._tmp.cleanup()
@Protocol-zero-0 Protocol-zero-0 mentioned this pull request May 14, 2026
3 tasks
Protocol-zero-0 added a commit that referenced this pull request May 14, 2026
Bumps version 0.3.0 → 1.0.0 and updates the status badge to v1.0
following the Phase 4 work (PR #19 firejail sandbox, PR #20 HTTP
evidence source). The kernel now has:

- A process-level sandbox that stops the executor from writing
  outside its assigned worktree (firejail backed by OS-level
  read-only mount, verified end-to-end on CI).
- An HTTP evidence source so the observer can pull live state
  from deployed services into observation.json alongside file and
  shell sources.

Together with the v0.2 multi-round LLM loop, k-branch parallel
exploration, goal evaluator, and full ledger audit chain, this
crosses the bar for "灵魂插件": a kernel you can point at any
git repository and trust to evolve it unattended.

99 tests · CI green on Python 3.10 + 3.12 · single dependency
(PyYAML) preserved.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
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.

Phase 4a: Process Sandbox via firejail (PR7a)

2 participants