Skip to main content
Outcome: a fetch PR -> test in isolation -> model review -> APPROVE / REQUEST_CHANGES pipeline. Level: intermediate · Time: ~15 minutes · Pattern: the box is a tool the model calls.

When to use this

Every project that accepts outside contributions carries the same hole: running someone’s PR tests means executing their code on your CI runner. pytest imports conftest.py automatically, pip install executes setup.py, and a test body can call anything. A pull request that looks like a typo fix can read GITHUB_TOKEN, exfiltrate environment variables, or plant something in your build artifacts. The usual mitigations — minimal-privilege tokens, withholding secrets from fork branches — reduce the blast radius; they do not stop the execution. Running the PR in a microVM does: the untrusted code gets its own kernel and filesystem, your orchestration logic and credentials stay in the host process, and the box is destroyed afterwards.

Architecture

  • The execution boundary is the box. setup.py, conftest.py, and anything inside a test run on the sandbox’s kernel only.
  • Decisions and credentials stay outside. The model call, the review verdict, and your GitHub token never enter the sandbox.
  • Results come back structured. You read exit_code / stdout / stderr from ExecResult. A non-zero exit does not raise — which is exactly what you want for a CI decision.

Prerequisites

  • BoxLite installed and a working virtualization host — see Installation.
  • An OpenAI-compatible LLM endpoint (OPENAI_API_KEY, plus OPENAI_BASE_URL for other providers).
  • Give the box a bigger disk. The default python:slim root filesystem is around 224 MB, which cannot hold git’s dependency chain — apt-get install git fails with No space left on device. Use disk_size_gb=4.

Build it

To keep the example self-contained, it builds a baseline commit and a “PR” branch inside the box, where the PR introduces a real bug (amount + amount * rate becomes amount + rate). Swapping in git clone <your repo> changes nothing from step 4 onward — see Next steps.
Parameter tables for exec and ExecResult: Run any language or command.

Run it

Three things happened inside the microVM: git produced a real diff, pytest caught the injected bug with exit_code=1, and the model turned both into a verdict. The untrusted billing.py never ran on the host. End to end this takes about 19 seconds, most of it installing git.

Trust and limits

  • What the boundary covers. Every execution surface of the pull request — setup.py, conftest.py, test bodies, package install scripts — runs on the sandbox’s own kernel and filesystem. rm -rf /, reading environment variables, or writing files affects the box only. Your GitHub token and review logic never enter it.
  • Network is still open by default. This guide depends on that to install git and clone. It also means a malicious PR can make outbound requests from inside the box. For genuinely untrusted contributions, narrow egress to github.com and your package mirror and drop privileges with exec(..., user="nobody") — see Run untrusted tools safely.
  • exec never decides for you. A failing test is a non-zero exit_code, not an exception. Read it. Appending || true to a command silently discards that signal.
  • Startup failures are catchable. No hypervisor or a failed image pull raises RuntimeError — a controlled failure, not a crash.

Troubleshooting

Next steps

Review a real pull request. Replace steps 2 and 3 of review_pr() with a clone — steps 4 to 6 are unchanged:
Other directions:
  • Private repositories and pushing back. Keep GITHUB_TOKEN on the host with Secret and inject it through a request header — never in the clone URL. See GitHub operations.
  • Ship code in without network access. Prepare the working tree on the host and box.copy_in("./pr_workdir", "/work") instead of cloning.
  • Any language. exec is language-agnostic: with the right image, exec("bash", "-lc", "go test ./...") works the same way. See Run any language or command.