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.

Four 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.

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

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.')"