mirror of
https://github.com/SikongJueluo/pi-extensions.git
synced 2026-10-05 11:52:55 +08:00
refactor(pi-permission-inner-cmd): dispatch commands through a handler registry
- 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
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
---
|
||||
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 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.
|
||||
Reference in New Issue
Block a user