Skip to main content

Module canon

Module canon 

Source
Expand description

P5-1 (COMPOSABLE-HARNESS-DESIGN.md §5.3 risk 1, D-3): the command canonicalizer — the security-critical parser rule-matching depends on.

Why tree-sitter, not a hand-rolled splitter (risk-1 mitigation, §5.3: “tree-sitter-based parsing (oc’s proven approach, oc§1) rather than a hand-rolled splitter”): a regex/split-on-; approach cannot distinguish a real separator from one that’s quoted (echo "a; b" is ONE command), and cannot recurse into $(...)/backtick command substitution without reimplementing a chunk of shell grammar by hand — exactly the class of bug this design calls “a silent privilege escalation” (§5.3 risk 1). This module instead asks tree-sitter-bash (the same grammar opencode’s own parser uses, oc§1) to build a real parse tree and walks it.

Fail-closed contract (the load-bearing invariant): canonicalize NEVER returns a silently-empty or silently-optimistic result for input it can’t fully make sense of. Any parse error (an ERROR/MISSING node anywhere in the tree) yields CanonResult::Unparseable — callers MUST treat that as “requires approval”, never “allow” (see crate::permissions::rules::evaluate_command, which does exactly this).

Structs§

CanonSubcommand
One extracted sub-command: its canonicalized argv, and whether it is “opaque” — a wrapper whose real effect can’t be statically resolved, so crate::permissions::rules::evaluate_command must never let it resolve to Allow purely by absence of a matching rule.

Enums§

CanonResult
The result of canonicalize: either a flat list of every sub-command found (top-level compounds, and every command nested inside a $(...)/backtick substitution anywhere in the tree — see the module doc for why a single recursive command-node walk is sufficient to find both), or a fail-closed reason a caller must treat as REQUIRING approval.

Functions§

canonicalize
Parse command (a bash command string, as a model would pass to the bash/shell tool) into its canonical sub-commands — the D-3 security core every rule-engine decision in crate::permissions::rules is built on. See the module doc comment for the fail-closed contract.