deny rule fires even when the blocked command is hidden inside a compound, pipe, subshell, or substitution.
deny rules still block unsafe operations.
Quick Start
1
Block destructive commands — even in compound form
A single Commands like
deny rule on bash:rm * now catches rm wherever it appears:cd /tmp && rm -rf x, ls; rm -rf x, and echo $(rm -rf x) are all blocked — not just rm -rf x directly.2
Protect files from redirect overwrites
Truncating redirections (
>, >>) emit a write: sub-target. A write: deny rule catches them:cat foo > /etc/hosts and echo x >> /etc/hosts are blocked even though the command starts with cat / echo.How It Works
What Gets Decomposed
The
external_dir: rows above only apply when PermissionManager is created with workspace_root=.... See Workspace Boundary.echo '$(rm -rf x)' is a literal string — no rm is extracted.
fd-to-fd redirects like 2>&1 are never treated as write targets.
Input redirects (<, <<, <<<) — the filename is never mistaken for the executable.
Shell expansion escalates to ASK
Shell commands containing an expansion that cannot be statically resolved are escalated toASK instead of being silently allowed by a broad rule.
Before the tokenizer runs, the manager checks for $IFS, ${VAR}, and bare $VAR. If any is present, an explicit deny still wins; otherwise the request is escalated to ASK with the reason “Command contains shell expansion that cannot be statically verified; requires approval”. Command substitution ($(...) and backticks) is excluded — it is already decomposed per-op, so its inner commands keep matching deny rules.
bash:* allow could shadow a specific bash:rm * deny for anything containing ${IFS} or $HOME — a real permission bypass.
Evasions Now Blocked
Adeny: bash:rm * rule now blocks all of these:
Aggregation Precedence
When a compound command produces multiple sub-operations, their results are aggregated as: deny wins → then ask → then allow.Workspace Boundary
WhenPermissionManager is created with workspace_root=..., an extra external_dir:<parent>/* sub-target is added for any path that escapes the root — whether it appears as a command argument, a redirect write-target, or an executable path. The same deny→ask→allow aggregation applies: external_dir:* → deny hard-blocks; external_dir:/data/* → allow pre-authorises; the default is ask.
See Workspace Boundary for full details.
Fallback Behaviour
For simple single commands (bash:ls -la) with no compound operators, the engine defers to the existing flat matcher — exact backward compatibility. On any parse failure, the whole command is treated as a single op using today’s behaviour, so no existing rule is silently weakened.
Common Patterns
Block all destructive shell ops:Best Practices
Patterns match the executable name, not the full path
Patterns match the executable name, not the full path
bash:rm * catches rm -rf /tmp but not bash:/usr/bin/rm -rf /tmp. Use a regex rule with is_regex: true for absolute-path coverage.Single-quoted substitutions are literals
Single-quoted substitutions are literals
echo '$(rm -rf x)' is correctly not treated as an rm call — the parser respects single-quote suppression. Double-quoted substitutions are still extracted.Protect filesystem locations with write: patterns, not bash: patterns
Protect filesystem locations with write: patterns, not bash: patterns
Truncating redirects produce a
write:<path> sub-target. Use write:/etc/* to block overwrites — bash:cat * alone won’t catch cat foo > /etc/hosts.Zero overhead when permissions are off
Zero overhead when permissions are off
The command parser is lazy-imported: no parsing cost when permissions are not in use or the target is a non-shell tool.
Command-aware permissions decompose a compound command; Reusable Approval Scopes generalise a single command’s approval to a whole family.
Related
Declarative Permissions
Pre-declare allow/deny rules in YAML, CLI, or Python
Permissions Module
Programmatic PermissionManager API
Permissions CLI
CLI rule management reference
Approval
Interactive approval backends
Workspace Boundary
Gate shell/file access outside a project root with
external_dir:*Reusable Approval Scopes
Generalise one approval to cover a whole command family
Command-aware permissions parse inside the command; the workspace boundary gates where on disk it can act.

