Approval Desk: ordered shell rules
Nia now reviews a shell-policy list. She expects a deny anywhere in the list to win. The implementation instead chooses the first applicable rule. Whether a rule is applicable depends partly on its approval value.
This makes ordering part of the policy, not merely its formatting.
Inspect strings without running them
The following is a complete fictional classification input from bash-first-applicable-rule. It is not a shell exercise and is not a configuration recommendation.
{
"command": "printf seed-card",
"patterns": [
{ "match": "printf *", "approval": "allow" },
{ "match": "*", "approval": "deny" }
],
"mode": "always-ask"
}
Prediction: does the final catch-all deny block this string?
Recorded answer: no. The first rule applies. BashTool.approval() returns an explicit tool allow at write tier, and the resolver returns allow from tool policy. No shell command was executed by this scenario.
The existing approval.test.ts also covers a broad early * allow shadowing a later matching deny. Therefore “there is a deny later” is not sufficient evidence of an effective denial.
Understand what matching means
bash.patterns is an ordered array of objects with match and approval. In the supplied implementation:
- Only
*is a wildcard. Other regular-expression-looking punctuation is escaped. - Pattern and command whitespace is normalized before matching.
- Policy values are normalized; malformed rule entries are discarded by the rule reader.
- An allow must match the whole command and pass the shell-control heuristic.
- A deny or prompt may match the whole command or a tokenized segment of a compound command.
- The first rule that applies under those semantics is selected.
The matching is not a shell security parser. It does not certify executable identity, the behavior of scripts or binaries, environment-dependent behavior, or every form of shell interpretation.
The allow heuristic does distinguish some literal quoted metacharacters from active shell control. The existing tests cover a quoted literal pattern that remains allowable, as well as expansion, redirection, compound syntax, and arguments reinterpreted through command/eval options that do not ride a narrow allow. Those are useful classification contracts, not an exhaustive shell-language proof.
Change the command shape, not the rule order
Now read bash-compound-deny-segment:
{
"command": "printf seed-card && printf closed",
"patterns": [
{ "match": "printf *", "approval": "allow" },
{ "match": "printf closed", "approval": "deny" }
],
"mode": "yolo"
}
Prediction: does the first allow still apply because the text begins with printf?
Recorded answer: no. The compound line cannot use that allow. The second rule matches a segment, so the decision is tool-owned deny with reason Blocked by bash pattern: printf closed. Yolo does not remove the selected explicit denial.
This explains why “first matching” must include the rule’s semantics. It does not mean “first textual prefix.”
Critical heuristics have another ordering boundary
After selecting a rule, BashTool.approval() does the following:
- If the selected rule says deny, return explicit deny immediately.
- Otherwise, check
CRITICAL_BASH_PATTERNS. - If a critical pattern matches, return exec with
override: trueand reasonCritical pattern detected, but no explicit prompt policy. - Only then return the selected allow or prompt, if any.
- With none of those decisions, return exec.
For the next three cases, the command field is the inert string chmod -R 700 /fictional-seed-library. It is included to classify a recursive absolute-path permission change. Never execute it.
| Case | Rule and mode | Recorded resolution |
|---|---|---|
bash-critical-before-allow | chmod * allow; write mode; user bash allow | prompt, exec, override true, source tool |
bash-critical-yolo | Same allow rule; yolo | allow, exec, override false, source mode |
bash-selected-deny-before-critical | chmod * deny; yolo | deny, exec, override true, source tool |
The critical result in the first two rows is an override-only decision. That is why yolo can bypass it. The third row is an explicit denial returned before the heuristic and remains denied.
There is a further consequence worth predicting from source: a selected prompt rule does not necessarily survive as an explicit prompt decision when the critical branch returns first. The selected rule is found first, but the critical branch is evaluated before selected allow/prompt is returned. Do not turn a catch-all prompt rule into a guarantee that every critical string will prompt in yolo.
For comparison, bash-explicit-prompt-yolo uses the noncritical printf seed-card string and a printf * prompt rule. Recorded: it remains a tool-sourced prompt in yolo, even with user bash allow. Here the explicit prompt branch was actually reached.
Repair an explanation before repairing a policy
When a result surprises you, write down the selected rule and the branch reached after selection. If you cannot identify the first applicable rule, rearranging or broadening the list is premature. Keep approval patterns separate from bashInterceptor rules: the latter are another execution-time mechanism for directing work toward dedicated tools, not the same ordered approval array.
Paper checkpoint: why does a selected deny survive yolo while the critical override-only result can be allowed? Worked answer: explicit denial is resolved before the yolo branch; an override flag without an explicit policy is ignored by that branch.
Failure boundary: the six private bash cases called the real declaration and resolver only. They did not call BashTool.execute(). Neither their passing classifications nor the existing pattern tests establish a shell sandbox.
Source anchors: packages/coding-agent/src/tools/bash.ts — getBashApprovalPatternRules, bashApprovalRuleMatches, findBashApprovalPatternRule, hasBashApprovalShellControl, CRITICAL_BASH_PATTERNS, BashTool.approval; packages/coding-agent/src/tools/approval.ts — resolveApproval; packages/coding-agent/test/tools/approval.test.ts — ordered-pattern, compound-command, and critical-pattern tests.
Tool permissions and approvals · Source chapter: permissions/approval-desk-ordered-shell-rules. Original evidence remains scoped to its recorded snapshot.