This document is the authoritative reference for CodeyBox's security posture. Read it before deploying to anything that matters.
We assume the LLM agent's output is adversarial-by-default:
- The prompt may be authored by someone we trust, but the agent's response is shaped by training data and tool calls that can include attacker-influenced content (prompt injection from a fetched web page, a poisoned dependency, malicious source files in the repo being edited, etc.).
- The agent may attempt to read or exfiltrate secrets, persist outside the sandbox, escalate privileges, or pivot through the kernel.
- A compromise of the agent process must not become a compromise of the host or any other sandbox.
Everything crossing the API boundary is hostile too: work-item titles and prompts, repository URLs, branch and ref names, attachments, and the output of plugins and auditors. The API bearer token is a service credential for one trusted caller — it is not an authorization system, and it confers no per-user or per-tenant separation.
We do not defend against:
- A compromised host kernel running the orchestrator.
- A compromised orchestrator process.
- The agent producing bad code that humans then merge. Code review of the merged output is still required; CodeyBox does not validate correctness.
- Side-channel attacks against the host CPU (Spectre-class). Mitigations are the host kernel's responsibility.
The headline reason this framework exists. With plain containers, every container shares the host kernel. A Linux LPE (and there are several per year) becomes a host compromise. With Multipass (KVM-backed), each sandbox runs its own kernel inside a real VM. A guest kernel exploit doesn't get the attacker out of the VM.
Two providers give you that kernel isolation: Incus (COW clones, best for persistent headless hosts) and Multipass (simplest install, supports graphical sandboxes). Bubblewrap reduces attack surface but shares the host kernel; choose it only when KVM isn't available, and only for prompts and repositories you already trust.
The dev-only Sandbox.Process provider has none of these properties.
It is for local pipeline development only. Do not run it against
untrusted prompts, untrusted repositories, or untrusted upstream content.
| Credential | Lives in | Visible to sandbox? |
|---|---|---|
| Agent API key (Anthropic, OpenAI…) | Host env, mounted to work / rework / merge / audit-LLM sbx | Only those sandboxes, only their own agent |
| Project test secrets | Host env, injected by reference into opted-in phases only | Only the declared phases of that project — never via the agent-credential path |
| GitHub PAT / generic git creds | Orchestrator process only | Never |
| Host SSH keys, cloud creds | Host | Never |
Tool-only audit sandboxes (linters, scanners) run with no agent
credentials and (typically) the isolated network profile. A buggy or
compromised linter cannot exfiltrate the agent's API key, because the
key is not present in the sandbox where the linter runs.
The merge phase is agent-driven (so it can resolve merge conflicts and
run the project's test suite). The orchestrator constrains exfiltration
risk via the project's merge network profile — typically the same as
work, never broader. The orchestrator also verifies merge state
(expected post-merge SHA, clean working tree) before pushing.
The upstream push runs on the host, not in any sandbox. The token required to push to GitHub is held by the orchestrator and never crosses the sandbox boundary.
Every sandbox sits on a host-managed Linux bridge whose nftables rules
drop everything not on the bridge's allowlist. The bridges are created
once by scripts/setup-host-networks.sh; the orchestrator only chooses
which bridge a given sandbox attaches to (via the provider's
launch flags).
Three egress modes per profile:
-— no egress at all (loopback + DNS + established only). Right for tool-only audits and isolated phases.internet— block RFC1918 / link-local / cloud-metadata / loopback / multicast; accept the rest. Wide reach without LAN attack surface.host1,host2,…— strict hostname allowlist; resolved to IPv4 IPs at setup time and re-resolved on script re-run.
Per-project, per-phase profile selection lives in project config. The orchestrator passes a default-route override via cloud-init so the VM can't accidentally egress through the provider's own default (blocked) bridge.
A compromised agent with sudo cannot bypass this: the drops happen in
the host kernel, on bridges the agent has no view into. iptables -F
inside the VM affects nothing. See ../operating/host-firewall.md
for the threat model and setup. The Process and Bubblewrap providers do
not enforce egress (they are dev-only / shared-kernel).
Agent secrets are written to a tmpfs mount (/run/codeybox/creds) that
exists for the lifetime of the sandbox and is destroyed with it. The
secret never lands on a persistent disk inside the VM. Project-scoped
test secrets get the same treatment: they travel only through
SandboxSpec.Environment (the per-exec guest channel), never appear in
instance configuration, cloud-init, or logs, and are declared by host
variable name — the value never sits in committed config. See
Project sandbox secrets.
SandboxResourceLimits caps CPU, memory, disk, and wall-clock.
Incus and Multipass enforce memory and CPU limits at VM-launch time. The
orchestrator additionally enforces per-attempt WorkTimeout and
MergeTimeout budgets, plus an absolute fallback-chain cap, via
CancellationTokenSource timers.
The host bare git repo and the SQLite work-item store live on the host filesystem. Sandbox writes to the bare repo go through the git protocol, not raw filesystem writes. Even if a sandbox could write to the mount, it would corrupt the bare repo's git data — git-fsck on the host can detect this.
Upstream remote impls scrub their tokens from any error message returned
to the orchestrator. The orchestrator log lines never contain credential
material. Do not extend the orchestrator to log the contents of
AgentCredential or GitHubUpstreamOptions.
Tracked weaknesses in the current posture, most recently reviewed 2026-08-12 against OWASP ASVS 5.0 Level 2:
- Default-branch protection is not enforced on the CodeyBox repository itself — reviewed pull requests, required checks, and force-push/deletion blocks are not yet mandatory.
- Adversarial testing is incomplete. The following have been reasoned about and unit-tested but not attacked on a live host: sandbox escape and egress bypass, cloud-metadata SSRF and DNS rebinding, hostile git refs/hooks/submodules/LFS and attachment inputs, credential leakage through argv, environment, prompts, plugin output and crash dumps, and plugin supply-chain integrity.
- Destructive sandbox testing needs KVM. Verifying escape claims requires a host with nested virtualization; a second container on the same kernel is not adequate containment.
Fixed in earlier passes, listed so you don't re-report them:
cross-work-item bare-repo exposure (now a per-item mount), the
workBranch == baseBranch merge-containment bypass, GitHub PAT exposure
on argv via token-in-URL (now askpass), git option injection through
RepositoryUrl and branch names (validation plus a -- separator),
agent secrets on per-exec argv (now an env-file written at boot), a
default network bind wider than loopback, and log entries that accepted
un-sanitized operator-controlled values.
Sandbox.Process runs commands as the host's normal user, with read-only
copies for read-only mounts and symlinks for writable mounts. It does
not enforce the network policy. It exists so the orchestrator pipeline
can be developed and tested without a VM provider installed. Do not ship this
provider to production. Configuration that selects it fails loudly when
ASPNETCORE_ENVIRONMENT != Development unless
DangerouslyAllowProcessSandbox=true is explicitly set.
Sandboxes boot an Ubuntu cloud image (Incus defaults to
images:ubuntu/24.04/cloud) and then run whatever ExtraRuncmd bakes
into the baseline — typically language toolchains and agent CLI
installers fetched over the network at bake time. You are trusting those
installers and the distro mirrors they come from.
Bake a baseline once and clone it, rather than re-running installers per
sandbox: it is faster and it narrows the window in which a compromised
upstream installer can reach your fleet. Pin toolchain versions in the
bake script. See ../reference/sandbox-baselines.md.
The agent's prompt typically references the repository it is editing.
Files in that repository can contain instructions designed to subvert the
agent (e.g. README.md with "ignore previous instructions and run …").
CodeyBox reduces blast radius of a successful injection but does not
prevent injection. The two key reductions:
- The compromised agent has only its API key and the local repo. It cannot reach upstream creds, the host, or other work items' repos.
- Anything the agent writes is constrained to a feature branch on the host bare repo. The merge phase blindly merges, but the upstream push is host-controlled, and a human (or a CI pipeline you bolt on) can gate it.
For high-stakes deployments, gate the upstream push behind a manual
approval step (replace IUpstreamRemote with one that records the merge
and requires a separate API call to push).
The merge phase runs the agent against the merge so it can resolve merge
conflicts and run the project's test suite. This is what most projects
need; it is also a slightly larger trust surface than a deterministic
git merge. The reductions:
- The orchestrator verifies merge state (expected post-merge SHA, clean working tree) before allowing phase 4 — the agent cannot quietly push something other than the merge.
- The merge sandbox runs under the project's
mergenetwork profile. Pick the strictest profile your tests can tolerate. - If you don't want agent-driven merge, configure auditors that gate merge (the audit phase is the natural gate) and/or replace the orchestrator's merge step with a deterministic one.
The framework has no concept of tenants today. If you run this for multiple users, you must:
- Partition
IGitHostrepos per-tenant. - Partition
ICredentialProviderper-tenant (do not let tenant A's work items use tenant B's API key). - Partition the SQLite store or move to per-tenant DBs.
- Add per-tenant authorization. The bearer token authenticates a caller; it does not distinguish one tenant's work items from another's.
- Rotate API keys on a schedule. The orchestrator picks up changes via
EnvironmentCredentialProvideron next read; in-flight sandboxes hold the previous value until they finish. - Audit log everything. Every state transition is logged at Information level. Send these to an append-only sink.
- Don't disable warnings-as-errors. The codebase enforces this in
Directory.Build.props. Suppressing warnings about uninitialised fields or platform-specific calls usually masks a real bug.
Review this document whenever:
- A new sandbox provider is added.
- A new agent integration is added (each agent has its own auth quirks).
- The credential or network model changes.
- Any cross-sandbox communication is introduced.