OPA policies reference

Complete reference for OPA policy configuration in the Agent Gateway: the Policy canvas element, policy scopes, and the full input object available to Rego rules.

This page is the lookup reference for writing OPA (Rego) policies on the Agent Gateway. It covers:

If you are new to OPA policies on the Agent Gateway, read the OPA policies concept overview first.

The Policy element

The Policy element runs an OPA (Rego) policy against the request or response and denies it when the policy returns false. Policies run in-process. No external OPA daemon is required. Multiple Policy elements can coexist on a surface, one per slot.

The slot the element fills is determined by the edge you drop it on.

Drop positionSlotWhen it evaluates
Caller → Access Point (request)Inbound policyBefore the request reaches the Managed Agent.
Managed Agent → Target (request)Target policyJust before the upstream call.
Target → Managed Agent (response)Response policyOn the response before it is forwarded back to the caller.
On a Transit PointTransit Point policyPer-hop, before dispatch to that destination.

Fields

FieldTypeRequiredDefaultDescription
Policy DefinitionselectYesID of the OPA policy definition in the policy store. Evaluated against the PolicyInput struct at runtime.
Enrich policy input with agent contextboolNofalseWhen on, the pipeline fetches the target’s agent card and queries the trust registry before evaluation, populating input.agent in the OPA input. Adds latency. Enable only when the policy uses input.agent. Not available for response policies.

Policy definitions: versions, dry-run testing, and deletion

Every policy definition is stored as an append-only sequence of versions, not a single mutable record. Saving a change to the Rego body appends a new version and keeps every earlier one. Saving only the name, description, or enabled state updates the current version in place without creating a new one.

Content hash and version fields

The policy editor and list both show read-only fields that identify the exact Rego enforced, so a decision or an audit entry can be traced back to specific bytes even after the policy has since changed.

FieldWhat it doesDefault
SHAThe content hash (sha256:<hex>) of the Rego currently enforced. Shown on the policy editor, in the policy list, and against every row of the version history. Binds the policy’s scope to its body, so identical Rego under Gateway and Agent Surface scope hashes differently.
Version historyAn append-only, newest-first list of every revision the policy has had: version number, content hash, creation time, author, and the name/description as they stood at the time. Selecting an older version compares it line-by-line against the current one.
AuthorThe username of whoever saved the revision, resolved from the authenticated account. Falls back to the raw account identifier when the account can no longer be resolved, for example a deleted user.

Dry-run testing (Test panel)

The Test (dry-run) panel appears below the Rego editor once the policy definition has been saved at least once.

FieldWhat it doesDefault
Sample input JSONThe input document the dry run evaluates the policy against. Saved with the policy definition, so the last input you tested with is still there next time you open it.A minimal example with a JWT subject, an HTTP method and path, and an inbound direction.
Run against the draft aboveEvaluates the Rego currently in the editor, including unsaved changes, against the sample input. Disabled while the policy does not compile or the sample input is not valid JSON. Nothing here reaches the live policy engine or in-flight traffic.
Auto-runRe-runs the dry run automatically, a short pause after every change to the policy or the sample input.Off

The result shows an ALLOW or DENY badge, a reason when the policy defines one, and the query, version, and content hash the dry run evaluated (shown as draft instead of a version number when testing unsaved Rego).

The draft Rego and sample input are each size-capped, and a run is time-bounded, so an oversized or pathological input is rejected rather than left to run indefinitely: an oversized draft returns “Draft policy is too large to simulate”, and an oversized sample input returns “Sample input is too large to simulate”.

Delete confirmation and blast radius

Selecting the delete icon on a policy row opens a confirmation instead of deleting immediately.

  • The confirmation states how many gateways (for a Gateway policy) or surfaces (for an Agent Surface policy) currently reference the policy, so you can judge the impact before committing to a delete that cannot be undone.
  • This count only covers direct references within the policy’s own scope: gateways with it selected on their Global Policy tab, or surfaces with a Policy element bound to it. It does not include appliance-wide (global) enforcement. Check the policy’s Global column before deleting; a globally-enforced policy’s removal fails its entire plane closed, not only the objects the count includes. See Appliance-wide (global) policy enforcement.
  • A referencing gateway or surface does not fall back to allowing traffic once its policy disappears. It fails closed: every request that policy used to evaluate is denied until a replacement is attached or the reference is removed.

Appliance-wide (global) policy enforcement

Any Gateway-type or Agent Surface-type policy definition can also be enforced across every gateway or every surface on the appliance, from the Policies list, in addition to or instead of being attached to individual objects. This is a reach setting on an existing definition, not a separate Rego scope: an appliance-wide Gateway policy still uses package gateway.policy and query path data.gateway.policy.allow, and an appliance-wide Agent Surface policy still uses package surface.policy and query path data.surface.policy.allow, exactly as described in Policy scopes and input structs.

FieldWhat it doesDefault
Global (toggle, per policy row on the Gateway or Agent Surfaces tab)Enforces this policy definition on every gateway or every surface on the appliance, on top of that object’s own policy. Switching it on shows a confirmation stating how many gateways or surfaces will enforce it.Off
Monitor (checkbox, shown once Global is on)Evaluates and records the decision without blocking a request the policy would have denied.Off (enforcing)
  • Appliance-wide policies evaluate deny-overrides, ahead of that plane’s own per-object policy. On an inbound direct request the order is: appliance-wide gateway policy, the gateway’s own Global Policy tab policy, appliance-wide surface policy, then the surface’s own Policy element. A deny at any stage is final.
  • A Monitor assignment is evaluated and its would-be decision recorded, but it never blocks a request, so it is a way to observe a new appliance-wide rule before enforcing it.

Policy input reference

The gateway serialises request context into a structured input object before evaluating any Rego policy. Policy rules read fields from input to make access decisions. Use this reference when writing Rego rules to understand which fields are available, how authentication data appears in input.source_auth, and which fields are populated at each evaluation point.

Policy scopes and input structs

Two distinct input shapes exist. Use the correct package declaration and field paths for the scope you are writing.

ScopePackage declarationQuery pathInput struct
Gatewaypackage gateway.policydata.gateway.policy.allowPolicyInput
Surface (inbound, outbound, response)package surface.policydata.surface.policy.allowPolicyInput
MCP toolpackage surface.policydata.surface.policy.allowMcpPolicyContext

Gateway and surface policies share the same PolicyInput struct and field namespace. MCP tool policies use a different struct (McpPolicyContext) with a different field layout; see MCP tool policy input.

Gateway and surface policy input (PolicyInput)

Full example

A fully-populated PolicyInput for an inbound MCP request with JWT authentication and trust registry data:

{
  "http": {
    "method": "POST",
    "path": "/v1/surfaces/my-surface/mcp",
    "headers": {
      "content-type": "application/json",
      "x-request-id": "req-abc123"
    }
  },
  "gateway": {
    "direction": "inbound"
  },
  "channel": {
    "config_id": "surface-abc123",
    "name": "my-surface"
  },
  "source_auth": {
    "method": "jwt_bearer",
    "subject": "user@example.com",
    "claims": {
      "role": "admin",
      "org": "acme-corp",
      "aud": "https://gateway.example.com"
    }
  },
  "mcp": {
    "method": "tools/call",
    "tool_name": "search",
    "params": { "query": "hello" }
  },
  "agent": {
    "did": "did:web:example.com:agent-1",
    "trust_verification": true,
    "source_trust_verification": true,
    "agent_dna": {
      "uai": "urn:uai:example.com:agent-1:v1",
      "birthEvent": { "scid": "...", "timestamp": "..." },
      "genesis": { "codeHash": "...", "genesisHash": "..." },
      "behavioral": { "behavioralHash": "..." },
      "operational": {},
      "attestations": {}
    }
  }
}

Not all fields are present on every request. See Input availability by evaluation path for when each field is populated.

input.http

Always present. Sensitive headers are stripped before policy evaluation, on inbound and outbound requests alike. Policies cannot read credential values from headers.

FieldTypeDescription
methodstringHTTP method: "POST", "GET", etc.
pathstringRequest path, for example /v1/surfaces/my-surface/mcp.
headersobjectNon-sensitive request headers as a flat string-to-string map. On an outbound Transit Point request, these are the headers from the original inbound request.

input.gateway

Always present. Describes the traffic direction and the identity participants.

FieldTypeDescription
directionstring"inbound" when traffic arrives at this gateway. "outbound" when the managed agent sends a request through a transit point.
source_idstring or nullOn outbound requests, the resolved DID of the managed agent acting as the protected caller. null on inbound requests.
target_idstring or nullOn outbound requests: the target endpoint URL or remote gateway DID. null on inbound requests.

input.channel

Always present. Identifies the surface handling the request.

FieldTypeDescription
config_idstring or nullInternal surface ID. Stable identifier across renames.
namestring or nullHuman-readable surface name as configured in the dashboard.
variant_aliasstring or nullVariant alias from the request URL (/route$alias/...). null when the request targets the default variant.

input.source_auth

Present whenever the surface has a Caller Context element, whether the caller authenticated successfully or not. Absent only when the surface has no Caller Context element at all.

The method field indicates the authentication method and determines the shape of the object:

JWT bearer

Callers authenticated with a validated JWT.

{
  "method": "jwt_bearer",
  "subject": "user@example.com",
  "claims": {
    "role": "admin",
    "org": "acme-corp"
  }
}
FieldDescription
subjectThe sub claim from the validated token.
claimsAll claims from the token payload as a JSON object. Access individual claims as input.source_auth.claims.<claim_name>.
API key

Callers authenticated with a surface-scoped API key.

{
  "method": "api_key",
  "key_name": "my-client-id"
}
FieldDescription
key_nameThe Client ID associated with the API key.
DID auth

Callers authenticated with a DID-based credential.

{
  "method": "did_auth",
  "did": "did:web:example.com:caller"
}
FieldDescription
didThe caller’s DID.
mTLS

Callers authenticated with a mutual TLS client certificate.

{
  "method": "mtls",
  "principal": "CN=my-service,O=Example Corp",
  "fingerprint": "sha256:abc123...",
  "subject_dn": "CN=my-service,O=Example Corp",
  "issuer_dn": "CN=Example CA,O=Example Corp",
  "sans": {
    "dns": ["my-service.example.com"],
    "uri": ["spiffe://example.com/my-service"]
  }
}
FieldDescription
principalThe matched principal string (fingerprint, subject CN, DNS SAN, URI SAN, or RDN), depending on the configured identity binding.
fingerprintSHA-256 fingerprint (hash) of the client certificate in hex.
subject_dnFull subject Distinguished Name.
issuer_dnFull issuer Distinguished Name.
sans.dnsDNS Subject Alternative Names. Absent when none.
sans.uriURI Subject Alternative Names (for example SPIFFE IDs). Absent when none.
sans.emailEmail Subject Alternative Names. Absent when none.
sans.ipIP address Subject Alternative Names. Absent when none.
Failed

Callers whose credential was missing or invalid. A failed authentication does not stop the request from reaching this evaluation point; the surface forwards it with no asserted caller identity unless a policy denies it based on this field.

{
  "method": "failed",
  "attempted_method": "jwt_bearer",
  "reason": "Token has expired"
}
FieldDescription
attempted_methodThe authentication method configured on the Caller Context element: jwt_bearer, api_key, did_auth, or mtls.
reasonA human-readable description of why authentication failed.

input.mcp

Present on inbound MCP protocol requests only. Absent on outbound requests and non-MCP protocols.

FieldTypeDescription
methodstringThe MCP JSON-RPC method, for example "tools/call", "tools/list", "resources/read".
tool_namestring or nullTool name from a tools/call request. null for other MCP methods.
resource_uristring or nullResource URI from a resources/read request. null for other methods.
prompt_namestring or nullPrompt name from a prompts/get request. null for other methods.
paramsobject or nullFull parsed params from the JSON-RPC request body.

input.a2a

Present on A2A protocol requests. It is built from the original request body before identity injection and reflects what the caller actually sent.

FieldTypeDescription
methodstring or nullThe A2A JSON-RPC method, for example "message/send".
messageobject or nullThe A2A message object (params.message or top-level message). Contains role, parts, metadata, and messageId when present.

input.agent

Present when Extract Trust Registry Data is enabled on the surface and data was retrieved. Also present with did populated after the caller DID is resolved. Absent when neither condition applies.

FieldTypeDescription
didstring or nullThe agent’s effective DID.
trust_verificationboolean or nulltrue when all configured trust registry recognition queries pass. false when any query returns untrusted. null when no queries ran.
source_trust_verificationboolean or nullCaller-leg trust verification result when the surface is configured in Both mode.
target_trust_verificationboolean or nullTarget-leg trust verification result when the surface is configured in Both mode.
agent_dnaobject or nullAgent DNA fingerprint from the agent card. Contains uai and nested fingerprint objects. Absent when the agent card does not include an agentDNA field.
trust_registry_didstring or nullThe trust registry DID from the agent’s trust-registry extension.
provider_didstring or nullThe department or provider DID from the agent’s trust-registry extension.
authority_didstring or nullThe authority DID from the trust-registry extension.

input.extension_identity

Present when a verified Verifiable Presentation (VP) was included in the request body. Absent otherwise.

FieldTypeDescription
didstring or nullThe agent DID from the VP holder/subject.
identity_hashstring or nullA hash of the identity payload for correlation.

input.payment

Present on inbound requests where an x402 payment was verified. Absent otherwise.

FieldTypeDescription
verifiedbooleantrue when the payment was cryptographically verified.
response_headerstring or nullThe raw payment receipt header value.

input.identity_binding

Present when a verified VP from an upstream gateway was processed. Absent otherwise.

FieldTypeDescription
verifiedbooleanWhether the VP signature was cryptographically verified.
agent.didstringThe managed agent’s DID on the sending gateway.
agent.identity_fieldsobjectAgent identity fields from the VP credential subject.
caller.fieldsobjectAllowlisted caller claims, keyed by field name. Absent when the sending gateway bound no caller context.
caller.user_hashstringOne-way hash of the caller’s identity on the sending gateway.
caller.assurancestring"gateway_attested" or "caller_credential_chained".
caller.identity_sourcestringHow the sending gateway sourced caller context: "transit_token" or "jwt_bearer".
delegatedbooleantrue when the sending gateway was acting for a caller.
issuer_gatewaystringThe DID of the gateway that issued the VP.
targetstringThe endpoint the request was forwarded to. Absent when not recorded.
intentobjectStructured intent object from the VP. Absent when not present.

input.trust_check_results

Present when Trust Check elements are configured on the surface and at least one ran. Absent when no Trust Check elements are configured for the leg.

{
  "trust_check_results": {
    "caller": [
      {
        "id": "tc-caller-1",
        "trust_registry_id": "tr-main",
        "query_type": "recognition",
        "ok": true,
        "error": null,
        "name": "Verify caller department",
        "authority_id": "did:web:authority.example.com",
        "entity_id": "did:web:caller.example.com",
        "action": "is",
        "resource": "ownedAgent",
        "query_resolved": true
      }
    ],
    "target": []
  }
}
FieldTypeDescription
callerarrayResults for the caller leg. Empty array [] when no caller-leg checks ran. Never null.
targetarrayResults for the target leg. Empty array [] when no target-leg checks ran. Never null.

Each result in the array shares the same shape as the Trust Check result fields. For the complete list of error.code values and their categories, see Trust Check error codes.

input.metadata

Present when Metadata Injection rules have added metadata to the request. A flat key-value map with string keys and arbitrary JSON values.

Input availability by evaluation path

Use this table when a policy condition behaves unexpectedly. The field you are checking may simply not be populated on that evaluation path.

The four columns correspond to these evaluation paths:

  • Inbound direct: a caller connects to an Access Point on this gateway.
  • Inbound G2G (fabric send): the call arrives through a gateway-to-gateway Fabric tunnel, with this gateway acting as the receiving end.
  • Inbound GW2 (connection point): this gateway acts as a connection point receiving a call forwarded from an upstream gateway.
  • Outbound: the Managed Agent sends a request through a Transit Point to an upstream target.

Condition shorthand used in this table:

  • If Caller Context configured: the surface has a Caller Context element, whether or not the request’s credentials validated.
  • If TR enabled: Extract Trust Registry Data is enabled on the surface.
  • If TC elements configured: at least one Trust Check element is configured for this leg.
  • If VP present: a verified Verifiable Presentation was included in the request body.
  • If binding VP present: a verified VP from an upstream gateway was processed at the connection point.
  • If x402 verified: an x402 payment was cryptographically verified on the request.

= present when the stated condition is met. = never present on this path.

FieldInbound directInbound G2G (fabric send)Inbound GW2 (connection point)Outbound
input.http
input.http.headersFilteredFilteredFilteredFiltered, from the original inbound request
input.gateway
input.gateway.direction"inbound""outbound""inbound""outbound"
input.gateway.source_idnullnullCaller DIDManaged agent DID
input.gateway.target_idnullRemote gateway DIDnullTarget endpoint URL
input.channel
input.source_authIf Caller Context configuredIf Caller Context configuredIf Caller Context configured
input.mcpIf MCP protocol
input.a2aIf A2AIf A2AIf A2A
input.agentIf TR enabledIf TR enabledIf TR enabledIf TR enabled
input.extension_identityIf VP presentIf VP present
input.paymentIf x402 verifiedIf x402 verified
input.identity_bindingIf binding VP present
input.trust_check_resultsIf TC elements configuredIf TC elements configuredIf TC elements configuredIf TC elements configured

MCP tool policy input

MCP tool policies share the same package declaration (package surface.policy) and query path (data.surface.policy.allow) as surface policies, but use a different input struct (McpPolicyContext) with different field paths.

Full example

{
  "mcp": {
    "method": "search",
    "params": { "query": "hello" },
    "protocol": "json-rpc-2.0"
  },
  "jwt": {
    "role": "admin",
    "org": "acme-corp",
    "sub": "user@example.com"
  },
  "channel": {
    "name": "my-mcp-surface",
    "id": "surface-abc123",
    "protocol": "mcp"
  },
  "request": {
    "source_ip": "203.0.113.42",
    "method": "POST",
    "path": "/v1/surfaces/my-mcp-surface/mcp"
  }
}

Fields

FieldTypeDescription
input.mcp.methodstringThe tool name from the tools/call request: the value the policy should gate on.
input.mcp.paramsobject or nullFull parsed params from the tools/call body.
input.mcp.protocolstringAlways "json-rpc-2.0".
input.jwtobjectFlat JWT claims map when the caller used JWT bearer auth. Absent from the input entirely when the caller used a different authentication method. Referencing input.jwt.* without JWT auth evaluates to undefined and silently denies. Access claims directly: input.jwt.role, input.jwt.sub.
input.channel.namestringSurface name.
input.channel.idstring or nullSurface ID.
input.channel.protocolstringAlways "mcp" for MCP tool policies.
input.request.methodstringHTTP method of the request.
input.request.pathstringHTTP request path.
input.request.source_ipstringSource IP address of the caller.

Field name mapping from PolicyInput to McpPolicyContext

In gateway / surface policyIn MCP tool policyNotes
input.source_auth.claims.*input.jwt.*JWT claims are a flat map in McpPolicyContext.
input.mcp.tool_nameinput.mcp.methodTool name is in method in McpPolicyContext.
input.http.methodinput.request.method
input.http.pathinput.request.path
input.channel.config_idinput.channel.id
Not availableinput.request.source_ipSource IP only available in MCP tool policies.

Absent fields and Rego behaviour

Fields that are not populated are omitted entirely from the JSON input. They are not set to null unless explicitly nullable in the schema above. In Rego, referencing an absent field evaluates to undefined.

A rule block where any condition is undefined does not contribute to the allow decision. When default allow = false, a rule that silently becomes undefined causes a deny.

# This rule silently denies when input.mcp is absent (non-MCP request)
allow if {
  input.mcp.tool_name == "search"
}

To write rules that handle both present and absent fields safely, use object.get or check for field existence first:

allow if {
  input.source_auth.method == "jwt_bearer"
  object.get(input.source_auth, "claims", {}).role == "admin"
}

Stripped headers

input.http.headers never contains a header named exactly authorization, proxy-authorization, cookie, or set-cookie, or any header whose name contains token, secret, credential, apikey, or api-key, matched case-insensitively. These are stripped at policy input construction time, on both inbound and outbound requests. Policies cannot read bearer tokens, API key header values, session cookies, or similarly named credential headers from input.http.headers. Use input.source_auth for authenticated identity information instead.

On a Transit Point’s outbound leg, input.http.headers carries the non-sensitive headers from the original inbound request, so a policy on that leg can check a custom header the caller sent (for example a threat-intelligence or classification header) without needing to read it from input.source_auth.