Skip to content

Placeholders

Tokens wrapped in <...> are placeholders — special tokens that match dynamically rather than by exact string comparison.

PlaceholderDescription
<cmd>Captures the wrapped command for further rule evaluation
<opts>Absorbs zero or more flag-like tokens
<vars>Absorbs zero or more KEY=VALUE tokens
<path:name>Matches against a named list of paths
<var:name>Matches against a typed variable definition

The <cmd> placeholder captures the remaining tokens as the wrapped command. The wrapped command is then evaluated against the other rules in the configuration. See Wrapped Command Recursion for details.

# sudo echo hello -> wrapped command is "echo hello"
- allow: 'sudo <cmd>'

Wrapper patterns are defined in the definitions.wrappers block and referenced by rules:

definitions:
wrappers:
- 'sudo <cmd>'
- 'xargs <opts> <cmd>'
rules:
- allow: 'echo *'
# With the wrapper definition, this also allows:
# sudo echo hello
# xargs -I{} echo hello

The <opts> placeholder absorbs zero or more flag-like tokens (tokens starting with -):

# Matches: env FOO=bar command
# Matches: env -i FOO=bar command
# Matches: env -u HOME -i FOO=bar command
- allow: 'env <opts> <vars> <cmd>'

<opts> stops consuming tokens when it encounters:

  • A token that does not start with -
  • The -- end-of-options marker

For short flags consisting of exactly - plus one ASCII letter (e.g., -n, -S), if the next token does not start with -, it is consumed as the flag’s argument:

# env -S "FOO=bar" command -> <opts> consumes "-S" and "FOO=bar"
- allow: 'env <opts> <cmd>'

The <vars> placeholder absorbs zero or more KEY=VALUE tokens — tokens that contain =:

# Matches: env command
# Matches: env FOO=bar command
# Matches: env FOO=bar BAZ=qux command
- allow: 'env <vars> <cmd>'

<vars> stops consuming tokens when it encounters a token without =.

The <path:name> placeholder matches a command argument against a named list of paths defined in the definitions.paths block.

definitions:
paths:
sensitive:
- /etc/passwd
- /etc/shadow
- /etc/sudoers
config:
- /etc/nginx/nginx.conf
- /etc/hosts

Reference a path list with <path:name>:

rules:
- deny: 'cat <path:sensitive>'
- deny: 'rm <path:sensitive>'
- allow: 'cat <path:config>'
CommandRuleResult
cat /etc/passwddeny: "cat <path:sensitive>"Denied
cat /etc/hostsallow: "cat <path:config>"Allowed
rm /etc/shadowdeny: "rm <path:sensitive>"Denied

Paths are normalized before comparison. The following path components are resolved:

  • . (current directory) is removed
  • .. (parent directory) is resolved
definitions:
paths:
sensitive:
- /etc/passwd
CommandMatches <path:sensitive>
cat /etc/passwdYes
cat /etc/./passwdYes (. removed)
cat /tmp/../etc/passwdYes (.. resolved)

This prevents bypassing path rules through path manipulation.

If a pattern references a path name that is not defined in definitions.paths, the pattern never matches:

# If "sensitive" is not defined, this rule has no effect
- deny: 'cat <path:sensitive>'

The <var:name> placeholder matches a command argument against a typed variable definition in the definitions.vars block.

Each variable has an optional type (default: literal) and a list of values:

definitions:
vars:
instance-ids:
values:
- i-abc123
- i-def456
- i-ghi789
test-script:
type: path
values:
- ./tests/run
TypeMatching behavior
literalExact string match (default)
pathCanonicalize both sides before comparison, fallback to path normalization
rules:
- allow: aws ec2 terminate-instances --instance-ids <var:instance-ids>
- allow: bash <var:test-script>
CommandRuleResult
aws ec2 terminate-instances --instance-ids i-abc123allow: "... --instance-ids <var:instance-ids>"Allowed
aws ec2 terminate-instances --instance-ids i-UNKNOWNallow: "... --instance-ids <var:instance-ids>"No match
bash ./tests/runallow: "bash <var:test-script>"Allowed
bash tests/runallow: "bash <var:test-script>"Allowed (path normalization)

When type: path is set, both the command argument and the defined values are canonicalized (resolved to absolute paths via the filesystem). If the path does not exist on disk, logical normalization is used as a fallback (. removal and .. resolution).

This handles cases where the same file is referenced with different path forms:

definitions:
vars:
test-script:
type: path
values:
- ./tests/run
CommandMatches <var:test-script>
bash tests/runYes
bash ./tests/runYes
bash ./tests/../tests/runYes
bash ./scripts/deployNo

If a pattern references a variable name that is not defined in definitions.vars, the pattern never matches.

Placeholders can be combined to handle complex wrapper patterns:

definitions:
wrappers:
# Handles: env [-i] [-u NAME] [KEY=VALUE...] command [args...]
- 'env <opts> <vars> <cmd>'
# Handles: sudo [-u user] command [args...]
- 'sudo <opts> <cmd>'
# Handles: xargs [flags...] command [args...]
- 'xargs <opts> <cmd>'
# Handles: find [args...] -exec|-execdir|-ok|-okdir command [args...] \;|+
- "find * -exec|-execdir|-ok|-okdir <cmd> \\;|+"
  • <cmd> captures one or more tokens; it tries all possible split points to find a valid wrapped command
  • Optional groups, path references, and variable references are not supported inside wrapper patterns