ADR-007: Verify command trust¶
Status¶
Accepted; amended by ADR-011 (D1) and ADR-033.
Decision¶
Verification commands may come only from the user. The precedence is:
- CLI
--verify "<cmd>": always allowed; the user typed it. - The user allowlist,
verify.allowin~/.config/obelize/config.yml(schema in CLI.md), outside any repository. - The repository
.obelize.yml: allowed interactively, after a one-time approval. - A pack's
verification.suggestions: displayed and never executed, listed as "suggested, not run".
A repository or a pack is remote text, and a checkout must not gain command execution by being read (threat TM-5). Trust is evaluated only when verification is going to run (ADR-011 D1.3).
Repository-sourced commands, interactively¶
Interactive means a TTY is present, CI is not set, and --non-interactive was
not passed. Approval is per command, never blanket: each command from
.obelize.yml is printed verbatim and approved once with a y, recorded in
~/.config/obelize/approved.json, keyed by repository path plus the command's
sha256, so changed text asks again.
Repository-sourced commands, non-interactively or in CI¶
A repository-sourced command runs only if it matches the user allowlist or
--trust-repo-config was passed on that invocation. Otherwise no command of
the phase runs, so a run never reports a pass over a subset: the result is
not_run with reason policy_refused, the refused command is printed and
recorded in the evidence, and the process exits 5.
Execution mechanics¶
shlex.splitandshell=False: no pipes, redirection, substitution or chaining. A compound command such aspytest -q && mypyis refused when the configuration is read; it is written as two entries.- The working directory is the repository root;
start_new_session=True,stdin=DEVNULL, stdout and stderr captured together. - On timeout (default 600 s) the process group gets
SIGTERM, thenSIGKILLafter 5 s, and the result isinconclusivewith reasontimeout. - The environment is inherited, plus
OBELIZE_RUN=1, so the command sees the user's credentials as it would if they ran it themselves. - Output is capped at 1 MiB (first 256 KiB plus last 768 KiB, flagged
output_truncated) and redacted before it reaches the logs or the report.
What is not covered by this decision¶
Two cheap checks always run and are not user-configurable, because they are ours,
not the project's: cst.parse_module before a file is written, and compile()
on every changed .py file after --apply. A failure there is a fail with
reason changed_file_does_not_compile, independent of any verification command.
Consequences¶
- A hostile repository cannot obtain execution by being scanned or fixed; in CI
the refusal is visible in the output and in
run.json. --trust-repo-configappears inrun.jsonunderargv, so its use is auditable.- Local execution is not isolated (v0 has no sandbox), and
docs/CLI.mdsays so.