- replace the hardcoded timeout switch with an engine that iterates registered handlers - extract the timeout logic into handlers/timeout.ts and add handlers/env.ts that defers env as non-transparent - thread a partial-evidence bag so the engine exception log retains handler-derived values like innerCommand - add CONTEXT.md with the transparent vs non-transparent wrapper glossary - record ADR 0002: env always defers to the AI judge and is never unwrapped
2.9 KiB
status
| status |
|---|
| accepted |
Defer env to the AI judge; inner-cmd never unwraps it
env is the second command type considered for pi-permission-inner-cmd's
deterministic unwrapping. We decided inner-cmd always defers env — it
never strips modifiers or re-evaluates an inner command — and lets the AI judge
authority handle env commands together with their full environment context.
Why
env is a non-transparent wrapper: its modifier args (NAME=VALUE, -i,
-u, -C, -S) form an unbounded, implicit input channel that the
command-string policy cannot see. Stripping them and re-evaluating the inner
command is unsound, because the modifiers can change what actually runs without
changing the inner command string:
PATH=/evil— resolve the inner name to a different binary;LD_PRELOAD=/x.so,LD_LIBRARY_PATH— inject arbitrary native code into the inner binary before itsmain();BASH_ENV,PYTHONPATH,NODE_OPTIONS,PERL5OPT, … — source scripts or inject code into the inner command's runtime;-i,-u NAME,-C DIR— alter the execution context.
A "detect dangerous variables and defer" denylist is infeasible: the dangerous
set is open-ended (new runtimes keep introducing new *_OPT / *_PATH
injection variables), so any denylist is permanently incomplete, and one miss is
an RCE. Detecting "does any modifier exist" is trivial and complete, but the
only sound unwrap case is bare env <cmd> (zero modifiers), which is too rare in
practice to justify the shell parsing it would require (assignment/flag
detection, -S, --, /usr/bin/env basename, quoting).
Considered options
- Detect a
PATH=override, otherwise unwrap — rejected as false security:LD_PRELOADand the interpreter-injection variables defeat it without touchingPATH. - Unwrap only bare
env <cmd>(zero modifiers) — sound, but bareenvis rarely written and the boundary parsing adds real complexity for near-zero coverage. Not worth it. - Always defer (chosen) — zero parsing risk, zero soundness surface; non-transparent commands get the semantic judgement they require from the AI judge.
Consequences
This establishes a clean division between the two permission authorities:
- inner-cmd (deterministic) unwraps only transparent wrappers (
timeout) — sound, fast, narrow. Every uncertain or non-transparent command defers fail-closed. - The AI judge authority receives deferred commands (including
env) and reasons about the full command with its environment, where non-transparency can be understood semantically rather than stripped.
This is why the AI judge consumes the full recovered command from the shared
recovery module (ADR 0001) — it needs the complete input, modifiers included, to
judge non-transparent wrappers. See CONTEXT.md for the transparent /
non-transparent wrapper distinction.