Files
pi-extensions/docs/adr/0002-env-defers-to-ai-judge.md
SikongJueluo 1afcbd3118 refactor(permission): consume structured bash payload
- require @gotgenes/pi-permission-system >=25.3.0 and read the complete local bash command from PromptPermissionDetails.payload instead of session-walking recovery
- remove the @sikongjueluo/pi-permission-shared package
- pass the triggering command unit to handlers via HandlerContext.unit in place of details.command
- add shadow-only AI judge modules for evidence projection, structured verdict requests, and prompt building, with vitest coverage
- record ADR 0004 and mark the ADR 0001 recovery mechanism superseded
- exclude pi-permission-system 25.3.0 from the pnpm minimumReleaseAge guard
2026-08-16 23:08:45 +08:00

62 lines
2.9 KiB
Markdown

---
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 its `main()`;
- `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_PRELOAD` and the interpreter-injection variables defeat it without
touching `PATH`.
- **Unwrap only bare `env <cmd>` (zero modifiers)** — sound, but bare `env` is
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 complete command from permission-system's
structured prompt payload (ADR 0004) — it needs the complete input, modifiers
included, to judge non-transparent wrappers. See `CONTEXT.md` for the transparent /
non-transparent wrapper distinction.