Skip to content

When Clauses

A when clause adds a condition to a rule. The rule only takes effect if both the pattern matches and the when expression evaluates to true. This lets you write rules that depend on environment variables, specific flag values, positional arguments, or defined path lists.

rules:
- ask: 'terraform apply *'
when: "env.TF_WORKSPACE == 'production'"

In this example, terraform apply only triggers the ask prompt when the TF_WORKSPACE environment variable is set to production. In other workspaces, this rule is skipped.

when clauses use CEL (Common Expression Language), a lightweight expression language designed for policy evaluation. runok uses the cel-interpreter crate to evaluate these expressions.

CEL expressions must evaluate to a boolean (true or false). If the expression returns a non-boolean value, runok reports a type error.

The following context variables are available inside when expressions:

A map of the current process environment variables.

# Only ask when deploying to production
- ask: 'deploy *'
when: "env.DEPLOY_ENV == 'production'"
# Block curl when a proxy is configured
- deny: 'curl *'
when: "env.HTTP_PROXY != ''"

A map of flags extracted from the matched command. Flag names have their leading dashes stripped (e.g., --request becomes request, -X becomes X).

  • Flags with values: flags.request"POST" (string)
  • Boolean flags (no value): flags.forcenull
# Block POST/PUT/PATCH requests to production APIs
- deny: 'curl -X|--request * *'
when: "flags.request == 'POST' || flags.request == 'PUT'"

A list of positional arguments (non-flag tokens after the command name). Access by index with args[0], args[1], etc.

# Block terraform destroy on production
- deny: 'terraform destroy *'
when: "args[0] == 'production'"
# Ask when curl targets a production URL
- ask: 'curl *'
when: "args[0].startsWith('https://prod.')"

A map of named path lists from the definitions.paths section. Useful for checking whether a command operates on sensitive files.

definitions:
paths:
sensitive:
- '.env'
- '.envrc'
- '~/.ssh/**'
rules:
# Deny reading sensitive files when there are many defined sensitive paths
- deny: 'cat <path:sensitive>'
when: 'size(paths.sensitive) > 0'

The paths variable is most useful for checking properties of the defined path list itself (e.g., its size), since the <path:sensitive> pattern already handles matching individual files against the list.

A list of redirect operators attached to the command. Each element is an object with the following fields:

FieldTypeDescriptionExample
typestring"input", "output", or "dup""output"
operatorstringThe redirect operator">", ">>", "<", "<<<", ">&", "<&", "&>", "&>>", ">|"
targetstringThe redirect destination"/tmp/log.txt", "/dev/null"
descriptorint or nullFile descriptor number, if specified2 (for 2>)

Type classification:

  • "output": >, >>, >|, &>, &>>
  • "input": <, <<<, <<, <<-
  • "dup": >&, <&
# Require output redirect for renovate-dryrun
- deny: 'renovate-dryrun'
when: '!redirects.exists(r, r.type == "output")'
message: 'Please redirect output to a log file'
fix_suggestion: 'renovate-dryrun > /tmp/renovate-dryrun.log 2>&1'
# Only allow output redirect to /tmp/
- allow: 'renovate-dryrun'
when: 'redirects.exists(r, r.type == "output" && r.target.startsWith("/tmp/"))'

An object indicating whether the command receives piped input or sends piped output:

FieldTypeDescription
stdinbooltrue if the command receives input from a preceding pipe
stdoutbooltrue if the command’s output feeds into a following pipe

Both fields are false when the command is not part of a pipeline.

# Block piped execution of sh/bash (e.g., curl | sh)
- deny: 'sh'
when: 'pipe.stdin'
- deny: 'bash'
when: 'pipe.stdin'

A map of values captured by <var:name> placeholders in the matched pattern. When a pattern contains <var:name> and matches a command token, the matched token value is stored in vars under the variable name.

definitions:
vars:
instance-ids:
values:
- i-abc123
- i-prod-001
rules:
# Deny terminating production instances, allow others
- deny: 'aws ec2 terminate-instances --instance-ids <var:instance-ids>'
when: "vars['instance-ids'] == 'i-prod-001'"
- allow: 'aws ec2 terminate-instances --instance-ids <var:instance-ids>'

In this example, when the command matches <var:instance-ids>, the actual token value (e.g., i-prod-001) is captured into vars['instance-ids']. The when clause can then inspect this value to make conditional decisions.

definitions:
vars:
regions:
type: literal
values:
- us-east-1
- eu-west-1
- ap-southeast-1
rules:
# Deny AWS operations in US regions, allow others
- deny: 'aws --region <var:regions> *'
when: "has(vars.regions) && vars.regions.startsWith('us-')"
- allow: 'aws --region <var:regions> *'

CEL supports standard operators for building conditions:

OperatorDescription
==Equal
!=Not equal
<, >Less than, greater than
<=, >=Less than or equal, greater than or equal
OperatorDescription
&&Logical AND
||Logical OR
!Logical NOT
MethodDescription
.startsWith(prefix)Check if string starts with prefix
.endsWith(suffix)Check if string ends with suffix
.contains(substr)Check if string contains substring
ExpressionDescription
value in listCheck if value exists in a list
size(list)Get the length of a list or map
x.exists(e, p)Check if any element satisfies predicate
x.exists_one(e, p)Check if exactly one element satisfies predicate

The when clause is evaluated after the pattern matches. The evaluation flow is:

  1. Check if the rule’s pattern matches the input command.
  2. If the pattern matches and a when clause is present, evaluate the CEL expression.
  3. If the expression returns true, the rule takes effect.
  4. If the expression returns false, the rule is skipped (as if it never matched).

This means the when clause acts as an additional filter, not a replacement for pattern matching. You still need a pattern that matches the command structure.

Error typeCauseBehavior
Parse errorInvalid CEL syntax (e.g., @@@ invalid)Evaluation fails with error
Eval errorReferencing an undeclared variable (e.g., missing.var)Evaluation fails with error
Type errorExpression returns non-boolean (e.g., env.HOME)Evaluation fails with error

Errors in when clause evaluation cause the entire command evaluation to fail, rather than silently skipping the rule. This is intentional — a misconfigured when clause should be surfaced immediately.

rules:
# Allow terraform plan everywhere, but ask before apply in production
- allow: 'terraform plan *'
- allow: 'terraform apply *'
- ask: 'terraform apply *'
when: "env.TF_WORKSPACE == 'production'"
rules:
# Allow curl GET requests, but ask before POST to specific hosts
- allow: 'curl -X|--request * *'
- ask: 'curl -X|--request * *'
when: "flags.request == 'POST' && args[0].endsWith('.internal')"
rules:
# Deny destructive HTTP methods to production APIs
- deny: 'curl -X|--request * *'
when: "flags.request == 'POST' && args[0].startsWith('https://prod.')"
rules:
# Require output redirect for commands that produce large output
- deny: 'renovate-dryrun'
when: '!redirects.exists(r, r.type == "output")'
message: 'Please redirect output to a log file'
fix_suggestion: 'renovate-dryrun > /tmp/renovate-dryrun.log 2>&1'
# Allow with output redirect to /tmp/
- allow: 'renovate-dryrun'
when: 'redirects.exists(r, r.type == "output" && r.target.startsWith("/tmp/"))'
rules:
# Block curl-pipe-sh attacks
- deny: 'sh'
when: 'pipe.stdin'
- deny: 'bash'
when: 'pipe.stdin'