EQTY LabDocs

OPA/Rego Policies

Write your own allow and deny rules in Rego and run them on every agent request through the Agent Gateway.

View as Markdown

The OPA/Rego plugin lets you write your own rules in Rego, the policy language of Open Policy Agent, and have the Agent Gateway evaluate them on every request before it reaches the model provider. The gateway runs the policy engine for you. You supply the policy and any static data, and the guardian-plugin CLI packages them into a configuration.

When to use OPA/Rego

Use OPA/Rego for deterministic rules the built-in plugins do not cover: allowing only approved models or providers, admitting a request only when an earlier plugin wrote a specific result, or requiring values in the agent’s verified state record. If a rule needs outside calls or changing state, write a custom plugin instead.

Policy project layout

A policy lives in its own folder, usually in a git repository:

text
model-allowlist/
  guardian.plugin.toml    manifest: scope, mode, and which files to load
  policy/
    main.rego             one or more Rego files
    data.json             optional static data, exposed as data.*

When you validate or apply, the CLI reads the files the manifest lists and embeds them in the configuration, so the gateway never reads files at request time.

A minimal manifest

toml
[package]
plugin_id = "builtin.opa_rego"
name = "model-allowlist"
version = "v0.1.0"
abi_version = "v1"

[control_plane]
base_url = "https://control-plane.example.com/api"   # your Control Plane URL

[definition]
runtime = "premade"
supported_hooks = ["proxy.pre"]
default_mode = "enforcement"
required_capabilities = []

[configuration]
scope = "global"          # or "project" with project = "Your Project Name"

[[deployment.entries]]
plugin_id = "builtin.opa_rego"
hook = "proxy.pre"
order_index = 0
plugin_version = "v0.1.0"
mode = "observability"    # start here, switch to "enforcement" later
timeout_ms = 50

[deployment.entries.granted_capabilities."request.body.read"]
max_bytes = 65536

[deployment.entries.config]
entrypoint = "data.guardian.model_allowlist.decision"

[deployment.entries.config.input_options]
include_body = true

[deployment.entries.opa]
module_files = ["policy/main.rego"]
data_files = ["policy/data.json"]

If you keep configurations in a separate guardian.config.toml, put the [configuration] table and the deployment entry there instead. See Policy as Code.

A minimal policy

This policy denies every request unless the requested model is on the list.

rego
package guardian.model_allowlist

import rego.v1

default decision := {
  "allow": false,
  "status_code": 403,
  "code": "model_not_allowed",
  "message": "this model is not approved for agents",
}

decision := {"allow": true} if {
  input.body.json.model in data.guardian.allowed_models
}

policy/data.json:

json
{ "guardian": { "allowed_models": ["gpt-4.1", "claude-sonnet-4-5"] } }

Always define a default decision in the policy itself. If your entrypoint produces no result, the gateway falls back to the entry’s default_decision setting, and if that is not set either, it allows the request. For a fail-safe setup, set default_decision to deny in the manifest as well:

toml
[deployment.entries.config.default_decision]
allow = false
status_code = 403
code = "policy_no_result"
message = "policy produced no decision"

The input your policy sees

Your policy reads the request through input:

  • input.hook: the stage name, the request’s correlation_id, and timestamp_unix_ms.
  • input.request: method, provider, operation, gateway_path, upstream_path, gateway_uri, upstream_url, query, headers, stream_requested, and stream_observed. Header names are lower-cased, and each header holds a list of values, so read one as input.request.headers["content-type"][0]. Headers arrive unredacted, including Authorization and X-Api-Key, so never copy header values into tags, context_writes, events, or deny messages, all of which end up in the audit trail.
  • input.identity: the verified agent_id and the signer_key_id that signed the request.
  • input.context: string values written by plugins that ran earlier in the chain, such as project membership results.
  • input.body: the request body, with present, content_type, size_bytes, truncated, and either json (when the body parses as JSON) or text. Present only when you set include_body = true and grant request.body.read.
  • input.resolved_cids: the agent state record or credentials the request references, fetched and verified by the gateway. Each item has header_name, cid, found, verified_by_host, size_bytes, truncated, and json or text. Present only when you set include_resolved_cid = true, list the headers to resolve in input_options.cid_header_names, and grant cid.resolve; without cid_header_names the list is empty. The supported headers are x-viper-state-cid, x-viper-credential-cid, and x-viper-verifiable-credential-cid.
json
{
  "request": { "method": "POST", "provider": "openai", "operation": "chat_completions" },
  "identity": { "agent_id": "urn:uuid:3f6c1a2e-8b4d-4e7a-9c11-2d5b7e9f0a44" },
  "context": {},
  "body": { "present": true, "truncated": false, "json": { "model": "gpt-4.1" } }
}

The decision your policy returns

Your entrypoint returns an object with a required allow (true or false). On a deny, status_code, code, and message set what the agent receives and what the audit trail shows; any you leave out default to 403, plugin_denied, and request denied by plugin policy. A policy can also add tags to the exchange, write context_writes values that later plugins read, and emit events with emitted_events. tags and context_writes map names to string values. Each event needs a name and can carry string tags and an object body. Any other field makes the evaluation fail. terminate_stream is accepted but has no effect, because the policy runs before the provider responds.

Authoring loop

You need the OPA CLI v1.15.2 or later installed, because guardian-plugin uses it to compile your policy against the v1.15.2 capabilities the gateway runs. Set OPA_BIN if opa is not on your PATH.

  1. Write or change the Rego and data files.

  2. Test locally with a sample input file:

    json
    { "body": { "json": { "model": "gpt-4.1" } } }
    bash
    opa eval --data policy/ --input sample-input.json 'data.guardian.model_allowlist.decision'
  3. From the policy folder, validate with guardian-plugin config validate, preview the change with guardian-plugin config plan, and apply it with guardian-plugin config apply. The first time, a platform admin runs guardian-plugin publish in the folder before applying, to register the plugin. Policy as Code covers authentication, scopes, and CI use.

Roll out observe-first

Apply the policy with mode = "observability" first. Requests keep flowing, and each would-be deny is recorded in the Audit Trail. When the results match your intent, change the entry to mode = "enforcement" and run config plan and config apply again. In enforce mode a deny blocks the request, and if the policy fails to evaluate, the request is blocked too.

Limits

  • OPA/Rego runs only at Before Proxy (proxy.pre). It cannot inspect the provider’s response.
  • The request body is not visible until you opt in with include_body and the request.body.read grant. Keep max_bytes as small as your rule allows; a body over the limit arrives with truncated: true.
  • Policies cannot make network calls, read files, or change the request. Built-ins that reach the network or give different answers on each run, such as http.send and time.now_ns, are rejected at validation.
  • Keep policies small and deterministic. They run on every request, and the entry’s timeout_ms bounds how long each evaluation may take: guardian-plugin accepts 1 to 5000 ms, and uses 50 ms when you leave it out. An evaluation that runs over counts as a plugin failure, so it blocks the request in enforce mode.
  • Policy as Code — validate, plan, and apply the policy, including from CI
  • Gateway Policies — built-in plugins, hook stages, and when a custom plugin fits better
  • Audit Trail — review would-be denies during an observe-first rollout
  • Agent Configuration — the agent state and validation findings a policy can act on