Repository navigation
--runtime claude-code|codex|gemini bypasses the attested launch, and --sandbox-required passes anyway #363
Description
--runtime claude-code|codex|gemini bypasses the attested launch, and --sandbox-required does not stop it
Found in security review of the v0.6.0 release (#362). Documented in the release notes and in launch-attestation.ts's header rather than left implicit — this issue is the fix.
What happens
packages/cli/bin/tps.ts branches on --runtime before the attested-launch path:
} else if (action === "start") {
const runtimeArg = /* …--runtime… */;
if (runtimeArg === "claude-code" || runtimeArg === "codex" || runtimeArg === "gemini") {
// …spawns the runtime directly…That branch never calls runAgent({action:"start"}), and runAgent is the only path to launchAttested() — which has exactly one caller, src/commands/agent.ts:877. The three runtime modules each spawn directly:
src/utils/claude-code-runtime.ts—spawn("claude", …)src/utils/codex-runtime.ts— passes--sandbox <mode>, which is Codex's own workspace-write sandbox, not nonosrc/utils/gemini-runtime.ts—spawn("gemini", …)
The part that is worse than the gap itself
launchesAgent() in src/utils/nono.ts recognises the command by name:
if (command === "agent") return sub === "start";So tps agent start --id X --sandbox-required --runtime claude-code passes the launch gate — the gate sees agent start and is satisfied — and then takes the unconfined branch.
A flag that reports the sandbox is required, on a path that cannot provide one, is worse than no flag. It converts "unsandboxed" into "believed sandboxed". This is reachable from a generated unit or any wrapper that passes --runtime.
Scope, stated honestly
This is not a regression. v0.5.4 shipped no sandbox at all — neither nono.ts nor launch-attestation.ts existed. v0.6.0 confines the default path and leaves these three unconfined, which is a strict improvement over what is published today. The defect is that the claim was universal when the coverage is not.
Fix
Two parts, and the second is worth doing even if the first takes longer:
- Route the three runtimes through
launchAttested()like the default path, carrying--sandboxed/--sandbox-requiredthrough the branch. - Until then, make the branch refuse when
--sandbox-requiredis set — a few lines. It does not confine anything, but it stops the flag from asserting a guarantee it cannot honour, which is the part that turns a known gap into a false belief.
Acceptance
tps agent start --runtime <each of the three>either runs under nono, or exits non-zero when--sandbox-requiredis set, naming the runtime and the reason.- A test covers all three runtimes, and fails before the change.
- No comment or release note in the tree claims universal coverage while an exemption exists.
launch-attestation.ts's header was corrected in release: v0.6.0 — first publish since February, lockstep bump + changelog #362 as part of this.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsIn review
Activity