Architecture Overview
This page describes the internal architecture of runok for contributors and advanced users who want to understand how commands are evaluated and executed.
Processing Pipeline
Section titled “Processing Pipeline”When runok receives a command, it flows through the following stages:
| Step | Stage | Description |
|---|---|---|
| 1 | Config Loading | 4-layer merge + preset resolution |
| 2 | Command Parsing | Tokenization + compound command splitting |
| 3 | Rule Evaluation | Pattern matching + when-clause evaluation |
| 4 | Action Decision | Allow / Deny / Ask / Default |
| 5 | Sandbox Resolution | Preset lookup + strictest-merge |
| 6 | Command Execution | Sandboxed or direct execution |
1. Config Loading
Section titled “1. Config Loading”runok merges configuration from four layers in ascending priority (global config, global local override, project config, project local override).
The extends field triggers recursive preset resolution (DFS with cycle detection, max depth 10). Presets can be loaded from local paths or remote GitHub repositories.
Source: src/config/loader.rs, src/config/preset.rs
2. Command Parsing
Section titled “2. Command Parsing”The command parser (src/rules/command_parser.rs) handles two tasks:
- Tokenization: Shell-aware splitting that respects single/double quotes and backslash escapes.
- Compound command splitting: Uses
tree-sitter-bashto decompose pipelines (|), logical operators (&&,||), semicolons (;), subshells, loops, conditionals, and command substitutions into individual commands.
Each individual command is then structurally parsed using a FlagSchema inferred from rule patterns (see Pattern Matching).
3. Rule Evaluation
Section titled “3. Rule Evaluation”The rule engine (src/rules/rule_engine.rs) evaluates each command against the configured rules:
- Single commands: Each rule’s pattern is tested against the command via the pattern matching pipeline, then any
whenclauses are evaluated using a CEL expression evaluator. - Compound commands: Each sub-command is evaluated individually, then results are aggregated using the Explicit Deny Wins principle.
- Wrapper commands: If a command matches a wrapper definition (e.g.,
bash -c <cmd>,sudo <cmd>), the inner command captured by the<cmd>placeholder is recursively evaluated (max depth 10).
4. Action Decision
Section titled “4. Action Decision”The rule engine returns one of four actions:
| Action | Meaning |
|---|---|
Allow | Command is permitted to execute |
Deny | Command is rejected (with optional reason and suggestion) |
Ask | User confirmation is required |
Default | No rule matched; falls back to configured default behavior |
For compound commands, actions are aggregated by priority: Deny > Ask > Allow > Default.
5. Sandbox Resolution
Section titled “5. Sandbox Resolution”If a matching rule specifies a sandbox preset name, the adapter resolves it to a concrete policy:
- Look up the preset in
definitions.sandbox - Resolve CWD-relative paths to absolute paths
- For compound commands, merge all sub-command policies using a strictest-wins strategy:
writablepaths: intersection (more restrictive)denypaths: union (all denied paths combined)network: AND (denied if either denies)
If no rule-level sandbox is specified, the global defaults.sandbox is applied as a fallback.
6. Command Execution
Section titled “6. Command Execution”The executor layer (src/exec/) runs the command in one of three modes:
| Mode | Description |
|---|---|
TransparentProxy | Replaces the current process via exec syscall |
SpawnAndWait | Spawns a child process and waits for it to complete |
ShellExec | Runs through sh -c for shell features |
When a sandbox policy is active, a platform-specific sandbox wraps the execution:
- macOS: Generates an SBPL (Seatbelt Profile Language) profile and runs the command through
sandbox-exec. - Linux: Uses Landlock LSM for filesystem access control.
Module Overview
Section titled “Module Overview”The source code (src/) is organized into four top-level modules:
| Module | Responsibility |
|---|---|
cli/ | CLI argument definitions and subcommand routing |
config/ | Config data model, 4-layer loading/merging, and preset resolution |
rules/ | Pattern matching pipeline (lexer, parser, matcher), rule engine, command parser, CEL expression evaluator |
exec/ | Command execution, platform-specific sandbox implementations, extension runner |
Adapter Layer
Section titled “Adapter Layer”runok supports three adapter types that share the same evaluation pipeline but differ in how they handle the result:
- Exec (
runok exec): Executes allowed commands directly (or via sandbox). Exits with code 3 for denied/ask actions. - Check (
runok check): Performs dry-run evaluation and outputs the result as JSON or text. Always exits with code 0. - Hook: Integrates with LLM agent hook systems (e.g., Claude Code’s
PreToolUsehook). Evaluates onlyBashtool invocations and wraps allowed commands withrunok exec --sandbox.