OPA/Rego Operator Workflow
This guide explains how to author, validate, and deploy builtin.opa_rego
policies for the Guardian hook runtime.
For design background, see:
Runtime Model
Section titled “Runtime Model”builtin.opa_rego is a first-party premade plugin. Operators provide Rego
policy and static JSON data; the gateway provides the policy engine.
V1 behavior:
- Plugin ID:
builtin.opa_rego - Runtime:
premade - Supported hook:
proxy.pre - Runtime source of truth: the active plugin snapshot
- Local Rego and JSON files: authoring inputs only
The gateway does not read policy files from disk at request time and does not
fetch policy from a remote OPA service. guardian-plugin materializes local
policy files into inline snapshot configuration before deploy.
Configuration Shape
Section titled “Configuration Shape”Snapshot config for builtin.opa_rego is strict JSON with this shape:
{ "entrypoint": "data.guardian.authz.decision", "modules": [ { "name": "policy/main.rego", "source": "package guardian.authz\n\nimport rego.v1\n\n..." } ], "data": { "guardian": { "allowed_models": ["gpt-4.1", "gpt-4.1-mini"] } }, "default_decision": { "allow": true }, "input_options": { "include_body": false, "include_resolved_cid": false, "cid_header_names": ["x-viper-state-cid"] }}Required fields:
entrypoint: OPA data reference evaluated for the Guardian decision.modules: non-empty list of Rego modules with uniquenamevalues.
Optional fields:
data: static JSON object exposed to Rego asdata.default_decision: Guardian decision used when the entrypoint is undefined.input_options.include_body: include request body input whenrequest.body.readis granted.input_options.include_resolved_cid: include host-mediated blob-store CID resolution input whencid.resolveis granted.input_options.cid_header_names: CID-bearing request headers to resolve. Supported names arex-viper-state-cidandx-viper-credential-cid(or the deprecatedx-viper-verifiable-credential-cidduring migration).
If default_decision is omitted, the runtime default is {"allow": true}.
Local Authoring Workflow
Section titled “Local Authoring Workflow”Install or run the CLI from the repository root:
cargo install --path crates/guardian-plugin --force# orcargo run -p guardian-plugin -- --helpguardian-plugin validate requires a local OPA CLI. It uses OPA_BIN when set,
otherwise opa on PATH.
opa version# orOPA_BIN=/path/to/opa cargo run -p guardian-plugin -- validateCreate a starter layout:
cargo run -p guardian-plugin -- init --template opa-rego guardian.plugin.tomlThe OPA authoring section in guardian.plugin.toml points at local files:
[deployment.entries.opa]module_files = ["policy/main.rego"]module_globs = ["policy/*.rego"]data_files = ["policy/data.json"]These paths are resolved relative to the manifest directory and must stay inside
that directory. During materialize, validate, and deploy, the CLI reads
those files and writes inline modules and data into the snapshot config.
Local file paths are not persisted as runtime dependencies.
Inspect the final deployment entries before deploy:
cargo run -p guardian-plugin -- \ --manifest docs/examples/plugins/premade/opa-rego/model-allowlist/guardian.plugin.toml \ materializeValidate locally:
cargo run -p guardian-plugin -- \ --manifest docs/examples/plugins/premade/opa-rego/model-allowlist/guardian.plugin.toml \ validateDeploy and activate:
cargo run -p guardian-plugin -- \ --manifest docs/examples/plugins/premade/opa-rego/model-allowlist/guardian.plugin.toml \ deploy --activateNew policies should usually start in observability mode. Review audit and
plugin decision summaries, then change the deployment entry to enforcement
when the policy is ready to block traffic.
Input Model
Section titled “Input Model”OPA evaluates a host-normalized input document. Common fields include:
{ "hook": { "name": "proxy.pre", "correlation_id": "req_123", "timestamp_unix_ms": 1776123456789 }, "request": { "method": "POST", "provider": "openai", "operation": "chat_completions", "gateway_path": "/v1/openai_api/v1/chat/completions", "upstream_path": "/v1/chat/completions", "gateway_uri": "/v1/openai_api/v1/chat/completions", "upstream_url": "https://api.openai.com/v1/chat/completions", "query": "", "headers": { "content-type": ["application/json"], "x-viper-state-cid": ["bafybei-example"] }, "stream_requested": false, "stream_observed": false }, "identity": { "agent_id": "agent:demo", "signer_key_id": "did:key:z6Mk..." }, "context": { "scanner.result": "clean" }}When request body input is enabled and granted, input.body may contain:
{ "present": true, "truncated": false, "size_bytes": 42, "content_type": "application/json", "json": { "model": "gpt-4.1" }}When CID resolution is enabled and granted, input.resolved_cids may contain:
[ { "header_name": "x-viper-state-cid", "cid": "bafybei-example", "found": true, "request_referenced": true, "verified_by_host": true, "truncated": false, "size_bytes": 35812, "json": { "agent_version": "1.2.3" } }]Header names are lower-cased. CID resolution is grant-gated by cid.resolve
and reads from the gateway blob store; it is not direct plugin network access.
request_referenced reports whether the CID was part of the request context,
but it is metadata rather than a resolution allowlist. Body and CID payloads
expose parsed json when possible, text when valid UTF-8, and explicit
truncated and size_bytes metadata.
Decision Contract
Section titled “Decision Contract”The configured entrypoint must evaluate to either a Guardian decision object or
undefined. Undefined results use default_decision.
Decision object fields:
allow: required boolean when a decision object is returned.status_code: optional non-negative integer.code: optional machine-readable string.message: optional operator/user-readable string.tags: optional object of string values.context_writes: optional object of string values for later plugins.emitted_events: optional event list with requiredname.terminate_stream: optional boolean.
Example deny decision:
{ "allow": false, "status_code": 403, "code": "policy_denied", "message": "request denied by OPA/Rego policy", "tags": { "policy": "model_allowlist" }}Capabilities
Section titled “Capabilities”The OPA/Rego plugin can use host-mediated capabilities only when both config and snapshot grants allow them.
Supported V1 capability inputs:
request.body.read: required forinput.body.cid.resolve: required forinput.resolved_cids.
Not supported by builtin.opa_rego in V1:
http.request- Raw network access
- Runtime file reads
- Request or response mutation
Use an earlier plugin for remote enrichment, write a compact result into hook
context, and let OPA/Rego make the final admission decision from
input.context.
Failure Semantics
Section titled “Failure Semantics”builtin.opa_rego participates in the standard hook runtime.
- Explicit deny in
enforcementmode blocks the request. - Runtime, capability, or invalid decision errors in
enforcementmode fail closed with the standard plugin execution error response. - Runtime, capability, or invalid decision errors in
observabilitymode fail open and record the plugin error. - Invalid snapshot configuration or invalid Rego should be rejected during validation or snapshot activation, before traffic is affected.
Examples
Section titled “Examples”Checked-in starter examples:
- Model allowlist: reads the request body and allows only configured model names.
- Verified state CID gate: resolves
x-viper-state-cidand requires host verification. - Context admission: admits only when
a previous plugin has written
scanner.result = clean.
Validate each example with:
cargo run -p guardian-plugin -- \ --manifest docs/examples/plugins/premade/opa-rego/model-allowlist/guardian.plugin.toml \ validateThen inspect the materialized payload:
cargo run -p guardian-plugin -- \ --manifest docs/examples/plugins/premade/opa-rego/model-allowlist/guardian.plugin.toml \ materializeV1 Limitations
Section titled “V1 Limitations”V1 intentionally does not support:
- Remote OPA or OPA sidecar execution.
- Remote policy bundles or per-request policy fetches.
- Runtime reads from local policy files.
- Rego outbound network calls such as
http.send. - Non-deterministic Rego builtins.
- Request or response mutation.
- Hooks other than
proxy.pre. - Full response-body policy evaluation.
Future versions may add signed bundle artifacts, more hooks, richer debugging, or additional normalized inputs after the snapshot-managed V1 workflow is proven.