Skip to main content

Documentation Index

Fetch the complete documentation index at: https://kosli-logo-update.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

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 includes a built-in evaluator with 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.Always define allow with a fail-safe default and drive it through a positive assertion, not through the absence of violations. See Safe policy design.
default allow := false

allow if trail_is_compliant(input.trail)
violations
set of strings
Optional but recommended. A set of human-readable strings explaining why the policy denied. Kosli displays these when allow is false. Each message should identify the offending resource and the reason.Violations are diagnostics only. They must not drive the allow decision. See Safe policy design.
violations contains msg if {
    # ... rule body ...
    msg := sprintf("descriptive message about %v", [resource])
}

Safe policy design

Three rules prevent a policy from incorrectly reporting a non-compliant trail as compliant.

Rule 1: use a fail-safe default

Always start with default allow := false. A trail must be explicitly approved rather than allowed by the absence of evidence against it. Use parameter aliases at the top of the policy file rather than hardcoding threshold values. If a required param is absent from the params file, any rule that references its alias will fail to evaluate, and allow will correctly remain false.
max_days_by_severity   := data.params.max_days_by_severity
max_ignore_expiry_days := data.params.max_ignore_expiry_days
See Evaluate trails with OPA policies for a detailed walkthrough.

Rule 2: drive allow through positive assertions

Drive the allow decision through a condition that must be true for the trail to be compliant. Do not write:
# Unsafe: allow depends on the absence of violations
allow if {
    count(violations) == 0
}
When a violations rule body encounters an undefined reference, such as a missing param or an absent attestation field, OPA silently skips that rule body and adds no message to the set. The set is then empty, count(violations) == 0 evaluates to true, and allow fires even though the policy never verified compliance. This produces a false-positive compliant result. The safe pattern makes compliance explicit:
# Safe: allow fires only when trail_is_compliant is positively true
allow if trail_is_compliant(input.trail)
If any field referenced inside trail_is_compliant is undefined, the rule body fails to evaluate and allow remains false. See Evaluate trails with OPA policies for a detailed walkthrough.

Rule 3: violations are diagnostics only

In a violations rule, an undefined reference causes the rule body to fail silently: no message is added. This is the safe failure mode for diagnostics. Violations explain a denial determined by the allow rule and must not determine it themselves. See Evaluate trails with OPA policies for a detailed walkthrough.

Params

Policies can read external configuration via the --params flag. Params are available in the policy as data.params.*. This separates policy logic from the thresholds it enforces, so one .rego file can cover multiple environments with different params files.
# Inline JSON
kosli evaluate trail "$TRAIL_NAME" \
  --policy my-policy.rego \
  --params '{"max_high": 0}' \
  --org "$ORG" \
  --flow "$FLOW"

# JSON file
kosli evaluate trail "$TRAIL_NAME" \
  --policy my-policy.rego \
  --params @rego.params.prod.json \
  --org "$ORG" \
  --flow "$FLOW"
Alias params at the top of the policy file so that missing values cause rules to fail rather than silently proceeding:
max_high := data.params.max_high
If max_high is absent, max_high is undefined and any rule that references it fails to evaluate, leaving allow at its false default.

Input data

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

Single trail (kosli evaluate trail)

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

Multiple trails (kosli evaluate 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'

Local testing

Use kosli evaluate input to test a policy against captured trail data without making live Kosli API calls:
# Capture trail data once
kosli evaluate trail "$TRAIL_NAME" \
  --policy allow-all.rego \
  --show-input --output json | jq '.input' > trail-data.json

# Iterate on the policy locally
kosli evaluate input \
  --input-file trail-data.json \
  --policy my-policy.rego \
  --params '{"max_high": 0}'

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

Allows only when every trail in input.trails has at least one pull request with at least one approver. The attestation name is read from params so the same policy works across orgs that use different naming conventions.
package policy

import rego.v1

pr_attestation_name := data.params.pr_attestation_name

default allow := false

trail_has_approved_pr(trail) if {
    some pr in trail.compliance_status.attestations_statuses[pr_attestation_name].pull_requests
    count(pr.approvers) > 0
}

allow if {
    every trail in input.trails {
        trail_has_approved_pr(trail)
    }
}

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

Check Snyk scan results on a single trail

Allows only when every artifact in the trail has a Snyk scan where the high-severity vulnerability count does not exceed max_high. Both the attestation name and the threshold are read from params.
package policy

import rego.v1

snyk_attestation_name := data.params.snyk_attestation_name
max_high              := data.params.max_high

default allow := false

artifact_within_threshold(artifact) if {
    snyk := artifact.attestations_statuses[snyk_attestation_name]
    every result in snyk.processed_snyk_results.results {
        result.high_count <= max_high
    }
}

trail_is_compliant(trail) if {
    every name, artifact in trail.compliance_status.artifacts_statuses {
        artifact_within_threshold(artifact)
    }
}

allow if trail_is_compliant(input.trail)

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

Further reading

Last modified on May 21, 2026