Rego is the declarative policy language of Open Policy Agent (OPA). You write rules that evaluate structured data, such as a container spec or an API request, and return a decision like allow. Start with one rule, set a default, then write tests with opa test before the policy guards anything real.
What does Rego actually do?
OPA's documentation describes Rego as built to reason about structured data such as API requests, infrastructure-as-code files and configuration. It was inspired by Datalog and extended to handle JSON documents. Because it is declarative, you describe what a correct result looks like rather than how to compute it.
A rule has a name, a value and a body. The docs put it this way: rule-name IS value IF body. The body is a list of expressions that all have to be true, joined by AND. If you omit the value, it defaults to true.
Why do undefined results trip up beginners?
In Rego, a rule whose body never succeeds is undefined, not false. Any expression that refers to an undefined value is also undefined, including a not-equal comparison. If your service treats undefined as "no decision", you can accidentally allow things.
The fix from the docs is the default keyword, which supplies a fallback value when nothing else matches. For an authorization rule, the safe default is false.
Another rule from the docs: if two complete rules produce different values for the same document, OPA returns an error rather than picking one. Conflicts are loud by design.
What does a first policy look like?
Take a constructed case. A team wants to allow a deployment only if every container image comes from a registry it controls. The input is JSON with a list of containers.
package images
default allow := false
trusted := ["registry.example.com/", "ghcr.io/our-org/"]
trusted_image(image) if {
some prefix in trusted
startswith(image, prefix)
}
untrusted contains name if {
some c in input.containers
not trusted_image(c.image)
name := c.name
}
allow if count(untrusted) == 0
Three ideas are in play. The some keyword searches for any element that makes the body true. The untrusted rule builds a set, one name per offending container. And allow is true only when that set is empty. Listing the offenders, not just returning false, means a pipeline can tell the developer which container broke the rule.
How do you test it?
OPA has a built-in test framework. Test files live beside the policy, use a package ending in test, and name each test with a test prefix. The with keyword swaps in mock input.
package images_test
import data.images
test_trusted_image_allowed if {
images.allow with input as {"containers": [{"name": "web", "image": "ghcr.io/our-org/web:1.4"}]}
}
test_public_image_denied if {
not images.allow with input as {"containers": [{"name": "cache", "image": "redis:7"}]}
}
Run opa test . -v in that directory. We ran this exact policy and tests with OPA 1.21.2 and all passed. A denial test matters as much as an allow test, because a policy that allows everything passes every allow test.
Common mistakes
- Writing a negation with some and an inequality. The docs warn that checking "some app is not X" is true whenever any single app differs, which is not the same as "no app is X". Use the every keyword or a negated helper rule.
- Forgetting the default, so a missing field produces an undefined decision.
- Testing only the happy path.
How to check or apply this
- Install OPA and use the online playground to try small examples.
- Write the default first. For access decisions, make it deny.
- Put the input shape in a sample JSON file and keep it with the policy.
- Write at least one allow test and one deny test per rule.
- Run opa test in CI so a policy change cannot merge with failing tests.
- Review policy changes as you would code, since they decide who gets access.
Safeguard has its own policies and gates for dependencies and pull requests, separate from OPA. If you use Rego for infrastructure rules, the testing habit above carries over to any policy engine.
Quick check
What does Rego return when a rule body is never true?
Undefined, not false. Add a default value if you need a definite answer, such as default allow := false.
How do I make sure a policy denies what it should?
Write tests with the not keyword against mock input that should be rejected, and run them with opa test on every change.
Sources
Last reviewed: 18 October 2026.