Skip to content

Next (unreleased)

This page tracks changes that will be included in the next release. It is updated as pull requests are merged.

Add a definitions.aliases field that factors out repeated prefixes from rule patterns. Each alias name maps to one or more pattern strings. At rule-load time, every rule whose leading command token equals an alias name is expanded by substituting the alias pattern in for the alias name — so a single rule can cover every variant of a shared flag prefix without rewriting the command itself.

runok.yml
definitions:
aliases:
kubectl:
- 'kubectl [--namespace|-n *]'
rules:
- allow: 'kubectl get pods'

The rule kubectl get pods expands to kubectl [--namespace|-n *] get pods, so all of kubectl get pods, kubectl -n prod get pods, and kubectl --namespace prod get pods match it. An alias with N patterns produces N expanded rules. Aliases compose recursively with cycle detection and a depth limit (currently 5).

If an alias pattern ends with a value-taking flag like --context * and you want a rule to use that alias with no tail, declare the flag explicitly via definitions.flag_groups and reference it as <flag:name> in the alias pattern. This tells the command parser that the flag consumes the next token as its value.

The audit log records the alias chain referenced by the matched rule under command_evaluations[].alias_chain (in expansion order, outermost-rule reference first). The field is omitted from the JSON when no alias contributed to the match.

See Configuration schema -> definitions.aliases for details.

Optional / PathRef / VarRef are now allowed in wrapper patterns (#399)

Section titled “Optional / PathRef / VarRef are now allowed in wrapper patterns (#399)”

Wrapper patterns under definitions.wrappers can now contain [...] (Optional), <path:name> (PathRef), and <var:name> (VarRef) tokens. Previously these returned unsupported token in wrapper pattern. This unblocks the common case of wrapping commands that take optional flags without listing every combination.

runok.yml
definitions:
wrappers:
- 'sudo [-E] [-u *] <cmd>'
- 'docker exec [-it] [-u *] <container> <cmd>'
rules:
- deny: 'rm -rf *'
- allow: 'ls *'

With the above, sudo rm -rf /, sudo -E rm -rf /, and sudo -u root rm -rf / all extract the inner command and deny it via the rm -rf * rule. The wrapper engine evaluates both the present and absent interpretations of each optional group and picks the most restrictive result (Explicit Deny Wins), the same priority comparison already used for wrapper wildcards.

The RuleError::UnsupportedWrapperToken variant is removed as a side effect. This only affects callers that embed runok as a Rust library and exhaustively match on RuleError; the CLI does not surface it.

Reserved-word prefixes on bash compound statements no longer fan out into multiple ask decisions (#427)

Section titled “Reserved-word prefixes on bash compound statements no longer fan out into multiple ask decisions (#427)”

time for i in 1 2 3; do echo $i; done and similar inputs where a bash reserved-word prefix (time, time -p, !, …) precedes a compound statement (for, while, until, if, case, { ... }) were split across multiple top-level commands — e.g. time for ..., do ..., done — and each fragment was evaluated independently. With a time <cmd> (or similar) wrapper configured, the inner body could not be reached and every fragment fell through to defaults.action.

The parser now detects this misparse symptomatically (a non-leading program child that begins with do / done / then / fi / elif / else / esac / }) and strips the leading prefix word so the inner compound parses normally. The detection covers any prefix that triggers the misparse — not just literal time — and is robust against future bash reserved words. time for i in 1 2; do echo $i; done now evaluates the same way as for i in 1 2; do echo $i; done. Simple time ls and time (...) continue to flow through wrapper matching unchanged because tree-sitter already parses them as a single command node.

runok check no longer evaluates standalone # comment lines (#404)

Section titled “runok check no longer evaluates standalone # comment lines (#404)”

When plaintext input contains a line that is only a # ... comment, runok check previously emitted a separate evaluation result for that line and fell back to defaults.action (typically ask). Multi-line scripts piped through Claude Code’s Bash tool — for example a pipeline followed by an explanatory # ... comment and another pipeline — therefore surfaced an unexpected ask between the real commands.

Comment-only top-level lines are now skipped to match bash semantics, where # introduces a no-op comment that ends at the next newline. Pipelines, ;, &, &&, ||, and | continue to split top-level commands as before, and # after a command on the same line is also dropped at end-of-line.