Priority Model: Explicit Deny Wins
When multiple rules match a single command, runok must decide which action to take. runok uses the Explicit Deny Wins model, inspired by AWS IAM policy evaluation: a deny rule always overrides allow and ask, regardless of rule order.
Priority order
Section titled “Priority order”Each action has a fixed restriction level. When multiple rules match, the most restrictive action wins:
| Priority | Action | Meaning |
|---|---|---|
| 2 (highest) | deny | Block the command |
| 1 | ask | Prompt the user for confirmation |
| 0 | allow | Permit the command |
This priority is defined in action_priority() in the rule engine (src/rules/rule_engine.rs).
How it works
Section titled “How it works”- runok iterates over all rules and collects every rule whose pattern matches the input command (and whose
whenclause, if any, evaluates totrue). - Among all matching rules, the one with the highest restriction level determines the final action.
- Rule order in the config file does not affect priority. A
denyrule always wins, even if anallowrule appears later.
rules: - allow: 'git *' # priority 0 - deny: 'git push -f|--force *' # priority 2 — always winsIn this example, git push --force main matches both rules. The deny (priority 2) overrides the allow (priority 0), so the command is blocked.
Comparison with AWS IAM
Section titled “Comparison with AWS IAM”| Aspect | AWS IAM | runok |
|---|---|---|
| Explicit deny | Always wins | Always wins |
| Allow | Permits unless denied | Permits unless denied or asked |
| Default | Implicit deny (block) | Configurable via defaults.action |
| Ask (confirmation) | Not applicable | Middle tier between allow and deny |
The key difference is that runok adds an ask tier between allow and deny, and the default action is configurable rather than fixed to deny.
The default action
Section titled “The default action”When no rule matches a command, the action is resolved immediately to the configured defaults.action:
defaults: action: ask # "allow", "deny", or "ask"If defaults.action is not set, it defaults to ask.
Because unmatched commands are resolved at evaluation time, they participate directly in the Explicit Deny Wins comparison at their effective restriction level. For example, during compound command evaluation, an unmatched sub-command resolved to ask (priority 1) will correctly outrank an allow (priority 0) sub-command.
Wrapped command interactions
Section titled “Wrapped command interactions”When a command matches a definitions.wrappers pattern (e.g., sudo <cmd>), the wrapped command is extracted and evaluated recursively. The result from the wrapped command evaluation is then merged with any direct rule matches using the same Explicit Deny Wins logic.
For example:
definitions: wrappers: - 'sudo <cmd>'
rules: - allow: 'sudo *' - deny: 'rm -rf /'When evaluating sudo rm -rf /:
allow: "sudo *"matches directly (priority 0).sudo <cmd>extracts the wrapped commandrm -rf /and evaluates it recursively.deny: "rm -rf /"matches the wrapped command (priority 2).- The results are merged:
deny(priority 2) wins overallow(priority 0).
The command is denied.
Sandbox preset selection
Section titled “Sandbox preset selection”For a single command, the sandbox preset is taken from whichever rule wins the Explicit Deny Wins comparison. The sandbox preset does not affect the priority comparison itself — it is carried along with the winning action.
rules: - allow: 'npm install *' sandbox: restricted-networkFor compound commands, sandbox presets from all sub-commands are merged using a strictest intersection strategy.
Related
Section titled “Related”- Design Decisions: Explicit Deny Wins — Rationale behind this priority model.