Policy Reference β CAST Security Gate
CAST uses conftest with OPA Rego policies to evaluate SARIF security findings. This approach replaces hard-coded shell logic with version-controlled, auditable policies.
Built-in Policies
Three policies ship with CAST, selectable via the CAST_POLICY variable:
| Policy | File | Blocks on | Use case |
|---|---|---|---|
default |
policy/default.rego |
CRITICAL only | Production pipelines |
strict |
policy/strict.rego |
CRITICAL + HIGH | High-security projects |
permissive |
policy/permissive.rego |
Never | Audit / onboarding |
Selecting a policy
GitHub Actions β set a repository variable:
Settings β Secrets and variables β Actions β Variables β New variable
Name: CAST_POLICY
Value: strict
GitLab CI β set a CI/CD variable:
Settings β CI/CD β Variables β Add variable
Key: CAST_POLICY
Value: strict
If CAST_POLICY is unset, the default policy is used.
How the Gate Works
- SARIF-generating jobs (Semgrep, Trivy) upload their output as artifacts.
- The
gate/cast-gatejob downloads allcast-sarif-*artifacts. - conftest evaluates each SARIF file against the active policy.
- If any
denyrule fires, the gate job exits 1, blocking the merge/MR.
SARIF severity β conftest level mapping:
| Tool severity | SARIF level |
default |
strict |
|---|---|---|---|
| CRITICAL | error |
β BLOCK | β BLOCK |
| HIGH | warning |
β PASS | β BLOCK |
| MEDIUM | warning |
β PASS | β BLOCK |
| LOW | note |
β PASS | β PASS |
Note: Semgrep maps ERRORβ
error, WARNINGβwarning. Trivy maps CRITICALβerror, HIGHβerror(when--severity CRITICAL,HIGHflag is used in the scan).
Writing a Custom Policy
To override the CAST policy, create a policy/ directory in your repository
with at least one .rego file. The gate job detects this directory and uses
it instead of fetching from the CAST repo.
your-repo/
βββ policy/
β βββ my-policy.rego β gates evaluate this
βββ .github/workflows/
β βββ devsecops.yml
Example: block only on specific rule IDs
package main
import future.keywords.if
import future.keywords.in
BLOCKED_RULES := {"sql-injection", "hardcoded-secret", "eval-injection"}
deny[msg] if {
run := input.runs[_]
result := run.results[_]
result.ruleId in BLOCKED_RULES
msg := sprintf("Blocked rule %s: %s", [result.ruleId, result.message.text])
}
Example: block only findings in specific paths
package main
import future.keywords.if
deny[msg] if {
run := input.runs[_]
result := run.results[_]
result.level == "error"
location := result.locations[_]
path := location.physicalLocation.artifactLocation.uri
not startswith(path, "test/") # ignore findings in test code
msg := sprintf("CRITICAL in %s: %s", [path, result.message.text])
}
Testing Policies Locally
Install conftest:
brew install conftest # macOS
# or
curl -Lo conftest.tar.gz \
https://github.com/open-policy-agent/conftest/releases/download/v0.50.0/conftest_0.50.0_Linux_x86_64.tar.gz
tar xzf conftest.tar.gz && sudo mv conftest /usr/local/bin/
Run against a SARIF file:
# Test with default policy (blocks CRITICAL)
conftest test path/to/semgrep.sarif --policy policy/default.rego
# Test with strict policy (blocks HIGH + CRITICAL)
conftest test path/to/trivy.sarif --policy policy/strict.rego
# Test with all policies in directory
conftest test path/to/*.sarif --policy policy/
A non-zero exit code means the policy blocked β the same result as in CI.