Backends and Isolation¶
TokenFuzz keeps the audit contract independent of the model CLI. Target config, work state, testcase execution, triage, and artifact layout stay the same whether an audit uses one hosted backend, rotates several, or runs a local model.
Choose the data path and execution boundary before choosing a model. Most runs fit one of these four routes:
| Need | Practical route |
|---|---|
| Reproducible run or benchmark | One explicit backend and model. |
| Source must remain on the audit host | oss against a local OpenAI-compatible endpoint. |
| Broad hosted-model coverage | --backend all, after checking which backends the selected security mode can launch. |
| Backend has no usable native sandbox | external-bypass inside a container or VM you administer. |
The backend changes who reasons about the source. It does not change the target config, work-state format, probe contract, triage gates, or artifact layout.
Choose a backend¶
# One hosted backend; best for a reproducible run.
bin/audit --target <target> --backend codex [--model <model>]
# Rotate every installed hosted backend allowed by the security mode.
bin/audit --target <target> --backend all
# Keep inference on a local OpenAI-compatible endpoint.
bin/audit --target <target> --backend oss --model <served-model-id>
Each launch runs under an execution boundary chosen by --agent-security; see
Agent security modes.
| Backend | CLI | Model behavior |
|---|---|---|
claude |
Claude Code (claude) |
Uses config/models.toml unless --model is passed. |
codex |
Codex CLI (codex) |
Uses config/models.toml unless --model is passed. |
gemini |
Antigravity CLI (agy) by default |
A config model slug is mapped to an agy models label. Set USE_GEMINI_CLI=1 to use Google Gemini CLI instead. |
grok |
Grok Build (grok) |
Uses config/models.toml unless --model is passed. |
oss |
OpenCode (opencode) |
--model is required. Use an opencode/<id> catalog entry or the exact id served by a local OpenAI-compatible endpoint. |
all |
Installed hosted CLIs | Cycles claude → codex → gemini → grok; excludes oss, and skips any backend the selected security mode cannot launch. |
Use an explicit --backend and --model in any experiment or reproducibility
record. Omitting --backend is the same as --backend all: convenient for
exploration, not for holding model choice constant.
Models and reasoning effort¶
config/models.toml is the checked-in source of truth for default model names
and backend-native reasoning effort. Model precedence is:
--model;- the backend's
*_MODEL_DEFAULTenvironment override; config/models.toml.
The [effort] table is applied in each backend's native form: Codex model
reasoning effort, Gemini thinking level, or the corresponding Claude and Grok
flags. Edit the config when changing project defaults so normal audits,
validation, and direct model decisions stay aligned.
For the default agy Gemini path, --model accepts either the config slug or
an exact label printed by agy models. Preflight rejects an unknown mapping
before an agent starts. Under USE_GEMINI_CLI=1, the value is passed directly
to Google Gemini CLI.
Preflight also checks that the model you asked for is the model that answers.
A CLI that quietly falls back to another model, or a model whose safeguards
refuse the audit workload, fails the run before any agent starts, and the log
names the substituted model or the refusing safeguard category. Usage rows
carry served_model whenever the transcript shows a provider billing a session
to a different model than the one requested.
Install and authenticate¶
Install the chosen CLI through its upstream instructions:
Run one direct, non-interactive check before an audit. A backend that is
waiting for login can otherwise look like a stalled agent. Credentials remain
owned by the CLI; do not put keys in target.toml or reports.
Agent security modes¶
Every agent launch runs under one of two modes. Hosted backends default to
sandboxed. OpenCode defaults to external-bypass because its permissions are
an approval policy, not an OS sandbox, so sandboxed refuses oss.
IS_SANDBOX=1 is how an outer container or VM announces itself. It is an
assertion TokenFuzz cannot measure, so its absence prints one warning naming
what is left unconfined rather than refusing the run: the boundary is yours to
administer and yours to skip. What is refused is a capability fact: a CLI
whose own sandbox provably cannot host an audit, which no flag can grant it.
| Mode | What enforces the boundary | When to use it |
|---|---|---|
sandboxed (hosted default) |
The backend CLI's own OS sandbox: Seatbelt on macOS, Landlock/seccomp or bubblewrap on Linux. Approval prompts are turned off, because a headless run cannot answer one, and an approval the model can request is not a boundary. | Normal runs on a machine you also use for other things. |
external-bypass (oss default) |
Nothing in the CLI. IS_SANDBOX=1 asserts that an outer container or VM enforces filesystem, process, credential, and egress policy; without it, TokenFuzz warns but cannot create that boundary for you. |
Inside a container or VM you administer, and for backends sandboxed refuses. |
A default is not a boundary
oss selects external-bypass even on a plain host because that is the
only mode OpenCode can run. The warning is deliberate: enter a hardened
container or VM before launching it if the target or generated testcases
must be contained.
A third, classifier-reviewed auto mode is deliberately absent. It would add
provider calls, latency, and variable decisions to the audit and benchmark
contract without creating a stronger boundary.
What the sandbox does and does not buy¶
A native sandbox gives integrity and process containment: the agent cannot write outside its workspace or reach the network. It is not a confidentiality boundary. Every one of these sandboxes still reads the whole filesystem, and whatever the model reads travels to its provider by design. If secrets on the machine are in scope, the boundary is a hardened outer container or VM with only the target mounted, entered before the audit starts.
Backend support¶
This table is TokenFuzz's tested support policy, not a comparison of what each vendor CLI can do elsewhere. A backend is listed as supported only where its sandbox was measured doing the two things an audit needs, reading the target tree and writing results, while still containing the agent. Where it is not, TokenFuzz refuses the launch rather than recording the run as contained.
| Backend | sandboxed |
What it enforces, or why it is refused |
|---|---|---|
| Claude Code | Supported | Writes confined to the workspace (cwd plus --add-dir, each granted by its resolved path, since the sandbox matches resolved paths and the benchmark facade reaches the target tree by symlink); outbound network and DNS blocked; loopback kept open so local client/server harnesses still probe; web tools denied; unsandboxed commands denied, and an unavailable sandbox is a hard error. |
| Codex | Supported | workspace-write with approval_policy="never": writes confined to the workspace roots the harness supplies, reads unrestricted, and all network blocked, including loopback, which it has no setting to re-open. |
Antigravity (agy) |
Refused | Its terminal sandbox runs commands in a scratch directory, refuses writes to the launch directory, denies reads outside it, and auto-denies its file-writing tool headless. An audit could neither read the target nor file a result. |
| Google Gemini CLI | Refused | Its container mounts only the launch directory (--include-directories adds workspace context, not a mount), so a cell runs blind to the target. Its macOS profile allows outbound network. |
| Grok Build | Refused | workspace reads the whole host, including $HOME (only writes to credential paths are blocked), and allows outbound network. Its one read-restricting profile sees nothing outside --cwd, which would leave a model-direct control blind to the target it is scored against. |
OpenCode (oss) |
Refused | Its permissions are an approval policy, not an OS sandbox. Read-only decision calls still run, with external directories and web tools denied. |
Refused backends stay fully available under external-bypass. On a plain host
the default therefore selects Claude Code or Codex; --backend all skips the
rest and says which and why.
Egress and socket-driving targets¶
A target whose harness drives a real socket will fail its probes under
sandboxed rather than report a finding: a silent recall loss, not an error
visible in the counts. Claude Code keeps loopback for exactly this reason;
Codex cannot. Audit such a target under external-bypass in a hardened
environment, and do not publish sandboxed benchmark rows for it.
One isolation policy for every launch¶
Web tools are denied on every launch the harness makes, agent sessions in both security modes and one-shot decisions alike, so nothing reading an untrusted tree has egress through the model's own tools:
| Backend | How web access is denied |
|---|---|
| Claude Code | --disallowedTools WebFetch,WebSearch. A read-only plan mode gates the filesystem, not the network. |
| Codex | web_search="disabled" (its default is on). |
| Google Gemini CLI | The admin policy in config/gemini-no-web.policy.toml. |
| Grok Build | --disable-web-search. |
| OpenCode | webfetch and websearch denied in every profile. |
Antigravity (agy) |
Exposes no web switch. |
The same launch flags serve both benchmark conditions, so the model-direct control is egress-free too. Cross-project research (S6 peer fixes, advisories) is done by the harness's own tooling, not by agents.
Two things stay uneven and are documented rather than fixed:
- Cross-run memory. Learned memory is off by default on every backend that
has a switch. Antigravity (
agy) exposes no memory or home isolation and keeps its memory store beside its OAuth token, so cross-run memory cannot be isolated for that dialect. PreferUSE_GEMINI_CLI=1for benchmark rows. - Delegation. Every backend keeps its CLI's default subagent delegation in both benchmark conditions, because a control that cannot delegate is not the product a user gets. Only the bounded validator reviews turn it off.
What a session actually did is recorded on its usage row as
delegation_events: Claude Agent calls, OpenCode task calls, Gemini CLI
invoke_agent calls, Grok subagent_start events, and Codex spawn_agent
calls read from its rollout. Claude and Gemini CLI run subagents inside the
session, so their usage covers them. Codex and OpenCode run them as separate
threads or sessions the parent's usage cannot see, so a delegating row on those
backends is a spend floor. Grok reports no usage at all, and its subagent
events are hook names whose presence in the output stream is unconfirmed, so
its fan-out is treated as unobservable. The
benchmark page says how the report marks a floor.
Using the modes¶
# Default: the backend's own sandbox, no flag needed.
bin/audit --target <target> --backend codex 1
# Compatibility path, only after entering an externally hardened shell.
bin/audit --target <target> --backend grok --agent-security external-bypass 1
The chosen mode is written to state/run-config.json and inherited by every
subprocess of the run, including source-reading validators. bin/benchmark
accepts the same flag, applies it to both model-direct and harness cells,
writes it to run.json, refuses to resume a run under a different mode, and
re-scores a --regenerate under the mode that run recorded. --backend all
skips a backend the mode cannot launch and says so; a backend named on the
command line is a hard error instead.
Grok Build¶
Grok needs its CLI credentials (commonly XAI_API_KEY) before launch:
grok -p "Reply exactly: tokenfuzz-grok-auth-ok"
# Grok is refused by the default mode; run it inside a boundary you
# administer (see Agent security modes).
bin/audit --target <target> --backend grok --agent-security external-bypass 1
TokenFuzz uses headless streaming JSON and applies the configured reasoning effort; every iteration is a fresh session. Nested Grok subagents stay at the CLI's default (see the delegation note above); only a bounded validator review disables them. Grok's stream may not expose measured token counts; when it does not, usage reports label the numbers as estimates rather than presenting them as measured.
Containerised backend shell¶
The supported container helper puts the hosted CLIs and the repository in a repeatable Linux environment:
It opens a shell at /root/work; it does not start an audit. Host credential
directories (~/.claude, ~/.codex, ~/.gemini, ~/.grok,
~/.local/share/opencode) are not mounted. Authenticate in the disposable
shell or pass --forward-credentials to forward supported API variables and
read-only Google ADC files explicitly. The container sets IS_SANDBOX=1
because it is the boundary that external-bypass relies on.
See Where to run the audit for the trust boundary and Container runtime for Docker and gVisor setup.
Ensemble mode¶
When --backend is omitted or set to all, each iteration selects the next
installed hosted backend:
Each backend has independent evidence and logs:
output/<target>/claude/results/
output/<target>/codex/results/
output/<target>/gemini/results/
output/<target>/grok/results/
Target-level cluster summaries combine accepted results:
When ensemble mode helps¶
- A provider is intermittently rate-limited or degraded.
- You want independent model behavior behind the same execution and triage rules.
- You want a target-level view while preserving backend-specific provenance.
When one backend is better¶
- You need a reproducible method section with one fixed model.
- You are controlling spend against a known price.
- Source-handling policy requires the local
osspath. - You are comparing harness changes and need to hold the backend constant.
Ensemble mode is rotation, not consensus voting. Each backend works its own state tree; the target-level summaries cluster results after the fact.
OpenCode provider and local models¶
The oss backend can use a model from OpenCode's catalog provider. Refresh
the catalog and verify the exact provider-qualified id directly before the
audit:
opencode models opencode --refresh
opencode run --pure --model opencode/<model-id> "Reply exactly: tokenfuzz-opencode-auth-ok"
bin/audit --target <target> --backend oss \
--model opencode/<model-id> 1
TokenFuzz passes the opencode/ model reference through to the installed CLI,
which retains OpenCode's credential and provider handling. No security flag is
needed to select its default, but that default is external-bypass; read
Agent security modes before running it on a host.
Local OpenAI-compatible models¶
The oss backend runs OpenCode against an OpenAI-compatible server. TokenFuzz
defaults to http://127.0.0.1:8000/v1 and verifies the requested model against
the server's /v1/models response before launching agents.
Install OpenCode, then choose a server.
vLLM path¶
vLLM suits GPU hosts and larger models:
python3 -m venv .venv-vllm
. .venv-vllm/bin/activate
pip install -U vllm
vllm serve <model-or-path> --served-model-name audit-model
bin/audit --target <target> --backend oss --model audit-model 1
If the server is not on the default address:
export AUDIT_LOCAL_BASE_URL=http://127.0.0.1:9000/v1
bin/audit --target <target> --backend oss --model audit-model 1
Ollama path¶
Ollama is convenient for a desktop or a smaller local model:
ollama pull <model-tag>
ollama serve
export AUDIT_LOCAL_BASE_URL=http://127.0.0.1:11434/v1
bin/audit --target <target> --backend oss --model <model-tag> 1
Pass the exact tag reported by Ollama's OpenAI-compatible models endpoint. Set
AUDIT_LOCAL_API_KEY only when the local server requires authentication.
Local operation keeps model data flow on the selected machine only when the endpoint is actually local and OpenCode is not configured to call another provider. The model still receives the source excerpts, prompts, state, and reports the audit needs. Small models may need narrower target scopes and more human review; the environment reference lists the timeouts a slow local model tends to hit.
Inspect backend results¶
For one backend, start with:
output/<target>/<backend>/results/findings/FINDING-CLUSTERS.html
output/<target>/<backend>/results/crashes/CRASH-CLUSTERS.html
output/<target>/<backend>/results/crashes-rejected/REJECTED-CRASHES.html
output/<target>/<backend>/results/findings-rejected/REJECTED-FINDINGS.html
output/<target>/<backend>/logs/index.log
Record the target revision, target.toml, backend, model, and any non-default
reasoning effort with results. Token usage and tool counts are operational
signals; validated, deduplicated findings and crash bundles are the security
output.
Backend hygiene¶
- Authenticate outside the audit loop.
- Pin backend and model for reproducibility.
- Review provider data-handling and spend before continuous runs.
- Keep cross-run learned memory off unless cumulative learning is intentional;
it is off by default (
--enable-memoryturns it on). - Diagnose startup in
logs/index.log, then use the trimmed session log it names. - Expect quota pauses on long runs rather than treating them as failures; see The run paused, or the backend went unavailable.
- Evaluate results through the findings, crashes, and rejected indexes, not through the style or length of the backend transcript.
For hosted defensive research, the provider-access links are collected under Cyber access.