Safeguard
Application Security•

How do you get started with Rego and write a policy that you can test?

Rego is the policy language of Open Policy Agent. Write an allow rule over JSON input, set a safe default and run opa test before it guards anything.

Safeguard Security Team
4 min read

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

  1. Install OPA and use the online playground to try small examples.
  2. Write the default first. For access decisions, make it deny.
  3. Put the input shape in a sample JSON file and keep it with the policy.
  4. Write at least one allow test and one deny test per rule.
  5. Run opa test in CI so a policy change cannot merge with failing tests.
  6. 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.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.