Security posture
evalctl is not a sandbox. Runners and command scorers are arbitrary local commands that evalctl executes on your machine with your privileges. Treat a suite the way you’d treat any script you’re about to run.
The acknowledgment gate
run and replay execute the suite’s runner and scorer commands as local code.
Before either runs a single command, the invoker must acknowledge this:
evalctl run demo --acknowledge-unsandboxed-runner --json
# or, for a whole session:
export EVALCTL_ACKNOWLEDGE_UNSANDBOXED_RUNNER=1
Without the flag or the env var, run and replay refuse with
E_UNSANDBOXED_RUNNER_UNACK and exit 2 (safety block) before any command
executes. The acknowledgment lives with the invoker, not in the suite file —
a field inside the suite would be controlled by whoever wrote the suite, so it
could not defend you against an untrusted one. Inspect the suite, then
acknowledge.
The unsandboxed-runner warning
Every run and replay envelope also carries W_UNSANDBOXED_RUNNER — including
completed-run reuse. It is not situational; it is a standing reminder that the
runner (and any command scorer) is unsandboxed local code. If you need
isolation, provide it around evalctl (containers, VMs, restricted users);
evalctl won’t provide it for you.
What evalctl does guard
- Portable suite paths.
case addonly writes paths under the suite tree. Absolute paths and..segments are rejected so generated suite files stay portable and can’t escape the suite directory. - Process-group cleanup. Runner process groups are killed on timeout, so
child and grandchild processes don’t survive a
E_RUNNER_TIMEOUT. - Atomic artifact writes. JSON artifacts are written to a same-directory temp
file and moved into place with
os.replace, so readers never see a half-written file. (This is atomicity, not full crash-durability — evalctl does not fsync.) - Output caps.
EVALCTL_OUTPUT_FILEraw bytes are capped, with a truthfulrunner.json.output_truncatedflag and aW_OUTPUT_TRUNCATEDwarning. - Robust serialization. Non-UTF-8 workspace paths are skipped with
W_PATH_UNREADABLErather than crashing the run.
No network, no account
evalctl has no gateway, dashboard, or SaaS account and no runtime dependencies beyond the Python standard library. It does not phone home. The optional spoolctl queue is local, and the deferred inferctl integration is not wired in.
Reporting
Security issues belong on the evalctl repository. Do not include secrets in eval fixtures or task files — the run directory is designed to be copied and shared, and anything in a fixture travels with it.