Skip to main content
A Rego policy defines the rules Kosli evaluates trail data against. You pass a .rego file to kosli evaluate trail or kosli evaluate trails via the --policy flag. Kosli has a built-in evaluator — no OPA installation required.

Policy contract

These rules are Kosli-specific conventions, not OPA built-ins. Kosli queries data.policy.* to find them.
package policy
required
Every policy must declare package policy. Kosli queries data.policy.allow and data.policy.violations to read the result.
allow
boolean
required
Must evaluate to a boolean. Kosli exits with code 0 when true, code 1 when false. Typically defined as:
default allow = false

allow if {
    count(violations) == 0
}
violations
set of strings
Optional but recommended. A set of human-readable strings describing why the policy failed. Kosli displays these when allow is false. Each message should identify the offending resource and the reason.
violations contains msg if {
    # ... rule body ...
    msg := sprintf("descriptive message about %v", [resource])
}

Input data

The data structure passed to the policy as input depends on which command you use.

kosli evaluate trail — single trail

The policy receives input.trail, a single trail object.
input.trail
object
The trail being evaluated.

kosli evaluate trails — multiple trails

The policy receives input.trails, an array of trail objects with the same structure as input.trail above.
input.trails
array
Array of trail objects. Each element has the same structure as input.trail described above.
Use --show-input with --output json to print the full input structure for a given trail. Pipe through jq to explore specific fields:
kosli evaluate trail "$TRAIL_NAME" \
  --policy my-policy.rego \
  --org "$ORG" \
  --flow "$FLOW" \
  --show-input \
  --output json 2>/dev/null | jq '.input'

Exit codes

CodeMeaning
0Policy allowed (allow = true)
1Policy denied (allow = false) or command error (network failure, invalid Rego, policy file not found)
Exit code 1 is used for both denial and failure. To distinguish between them in CI, use --output json and read the allow field directly from the output rather than relying on the exit code.

Examples

Check pull request approvals across multiple trails

package policy

import rego.v1

default allow = false

violations contains msg if {
    some trail in input.trails
    some pr in trail.compliance_status.attestations_statuses["pull-request"].pull_requests
    count(pr.approvers) == 0
    msg := sprintf("trail '%v': pull-request %v has no approvers", [trail.name, pr.url])
}

allow if {
    count(violations) == 0
}

Check Snyk scan results on a single trail

package policy

import rego.v1

default allow = false

violations contains msg if {
    some name, artifact in input.trail.compliance_status.artifacts_statuses
    snyk := artifact.attestations_statuses["snyk-container-scan"]
    some result in snyk.processed_snyk_results.results
    result.high_count > 0
    msg := sprintf("artifact '%v': snyk scan found %d high severity vulnerabilities", [name, result.high_count])
}

allow if {
    count(violations) == 0
}

Further reading

Last modified on March 26, 2026