OPA 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.
Policy scopes and input structs
Two distinct input shapes exist. Use the correct package declaration and field paths for the scope you are writing.
| Scope | Package declaration | Query path | Input struct |
|---|---|---|---|
| Gateway | package gateway.policy | data.gateway.policy.allow | PolicyInput |
| Surface (inbound, outbound, response) | package channel.policy | data.channel.policy.allow | PolicyInput |
| MCP tool | package channel.policy | data.channel.policy.allow | McpPolicyContext |
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 (authorization, cookie, any header containing token) are stripped before policy evaluation; policies cannot read credential values from headers.
| Field | Type | Description |
|---|---|---|
method | string | HTTP method: "POST", "GET", etc. |
path | string | Request path, for example /v1/surfaces/my-surface/mcp. |
headers | object | Non-sensitive request headers as a flat string-to-string map. Empty {} on outbound requests. |
input.gateway
Always present. Describes the traffic direction and the identity participants.
| Field | Type | Description |
|---|---|---|
direction | string | "inbound" for traffic arriving at this gateway; "outbound" for traffic the managed agent sends through a transit point. |
source_id | string or null | On outbound requests: the resolved DID of the managed agent (the protected caller). null on inbound requests. |
target_id | string or null | On outbound requests: the target endpoint URL or remote gateway DID. null on inbound requests. |
input.channel
Always present. Identifies the surface handling the request.
| Field | Type | Description |
|---|---|---|
config_id | string or null | Internal surface ID. Stable identifier across renames. |
name | string or null | Human-readable surface name as configured in the dashboard. |
variant_alias | string or null | Variant alias from the request URL (/route$alias/...). null when the request targets the default variant. |
input.source_auth
Present when the caller is authenticated. Absent when the surface has no Caller Context element or the request carries no credentials.
The shape depends on the authentication method, indicated by the method field:
JWT bearer: caller authenticated with a validated JWT:
{
"method": "jwt_bearer",
"subject": "user@example.com",
"claims": {
"role": "admin",
"org": "acme-corp"
}
}| Field | Description |
|---|---|
subject | The sub claim from the validated token. |
claims | All claims from the token payload as a JSON object. Access individual claims as input.source_auth.claims.<claim_name>. |
API key: caller authenticated with a surface-scoped API key:
{
"method": "api_key",
"key_name": "my-client-id"
}| Field | Description |
|---|---|
key_name | The Client ID associated with the API key. |
DID auth: caller authenticated with a DID-based credential:
{
"method": "did_auth",
"did": "did:web:example.com:caller"
}| Field | Description |
|---|---|
did | The caller’s DID. |
mTLS: caller 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"]
}
}| Field | Description |
|---|---|
principal | The matched principal string (fingerprint, subject CN, DNS SAN, URI SAN, or RDN), depending on the configured identity binding. |
fingerprint | SHA-256 fingerprint of the client certificate in hex. |
subject_dn | Full subject Distinguished Name. |
issuer_dn | Full issuer Distinguished Name. |
sans.dns | DNS Subject Alternative Names. Absent when none. |
sans.uri | URI Subject Alternative Names (for example SPIFFE IDs). Absent when none. |
sans.email | Email Subject Alternative Names. Absent when none. |
sans.ip | IP address Subject Alternative Names. Absent when none. |
input.mcp
Present on inbound MCP protocol requests only. Absent on outbound requests and non-MCP protocols.
| Field | Type | Description |
|---|---|---|
method | string | The MCP JSON-RPC method, for example "tools/call", "tools/list", "resources/read". |
tool_name | string or null | Tool name from a tools/call request. null for other MCP methods. |
resource_uri | string or null | Resource URI from a resources/read request. null for other methods. |
prompt_name | string or null | Prompt name from a prompts/get request. null for other methods. |
params | object or null | Full parsed params from the JSON-RPC request body. |
input.a2a
Present on A2A and AP2 protocol requests. Built from the original request body before identity injection, so it reflects what the caller actually sent.
| Field | Type | Description |
|---|---|---|
method | string or null | The A2A JSON-RPC method, for example "message/send". |
message | object or null | The 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 normalize_caller_did runs, which promotes the gateway-derived caller DID into input.agent.did when no trust-registry extension is in the payload. Absent when neither condition applies.
| Field | Type | Description |
|---|---|---|
did | string or null | The agent’s effective DID. Populated from the trust-registry extension in the payload, or promoted from input.extension_identity.did when the payload omits the extension. |
trust_verification | boolean or null | true when all configured trust registry recognition queries pass. false when any query returns untrusted. null when no queries ran. |
source_trust_verification | boolean or null | Caller-leg trust verification result when the surface is configured in Both mode. |
target_trust_verification | boolean or null | Target-leg trust verification result when the surface is configured in Both mode. |
agent_dna | object or null | Agent DNA fingerprint from the agent card. The top-level field is uai (Universal Agent Identifier string). Also contains nested camelCase fingerprint objects: birthEvent, genesis, behavioral, operational, attestations. Absent when the agent card does not include an agentDNA field. |
trust_registry_did | string or null | The trust registry DID from the agent’s trust-registry extension. Present when the extension is included in the request or agent card. |
provider_did | string or null | The department or provider DID from the trust-registry extension. This is the default authority_id template value ({{ input.agent.provider_did }}) used on the caller leg of every Trust Check element. Present when the extension is included. |
authority_did | string or null | The authority DID from the trust-registry extension. Present when the extension includes an authority DID. |
If input.agent.trust_verification is absent (field is None), a condition that checks input.agent.trust_verification == true evaluates to undefined in Rego and the rule denies. Ensure Trust Registry data extraction is enabled on the surface when writing policies that check input.agent.
input.extension_identity
Present when a verified Verifiable Presentation (VP) was included in the request body. Absent otherwise.
| Field | Type | Description |
|---|---|---|
did | string or null | The agent DID from the VP holder/subject. |
identity_hash | string or null | A hash of the identity payload for correlation. |
input.payment
Present on inbound requests where an x402 payment was verified. Absent otherwise.
| Field | Type | Description |
|---|---|---|
verified | boolean | true when the payment was cryptographically verified. |
response_header | string or null | The raw payment receipt header value. |
input.identity_binding
Present when a verified VP from an upstream gateway was processed. Absent otherwise. Used in gateway-to-gateway trust chains.
| Field | Type | Description |
|---|---|---|
verified | boolean | Whether the VP signature was cryptographically verified. |
agent_did | string | The agent DID (VP holder/subject). |
gateway_did | string | The DID of the upstream gateway that issued the VP. |
identity_fields | object | Identity fields extracted from the Verifiable Credential inside the VP. |
input.trust_check_results
Present when Trust Check elements are configured on the surface access point or target and at least one ran. Absent when no Trust Check elements are configured for the leg.
Each leg carries results in the order the elements were configured.
{
"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": []
}
}| Field | Type | Description |
|---|---|---|
caller | array | Results for the caller leg (AccessPoint.trust_check_list). Empty array [] when no caller-leg checks ran or the object is present. Never null. |
target | array | Results for the target leg (Target.trust_check_list). Empty array [] when no target-leg checks ran. Never null. |
Each result in the array:
| Field | Type | Description |
|---|---|---|
id | string | The element id from the surface configuration. Use this to address a specific result in Rego. Do not rely on array index order. |
trust_registry_id | string | The trust registry the query ran against. |
query_type | string | The TRQP query type: "recognition" or "authorization". |
ok | boolean | true when the trust registry allowed the request. false for both clean denials (NOT_RECOGNIZED, NOT_AUTHORIZED) and stage failures. |
error | object or null | null only when ok is true. Present for all other outcomes: clean denials and stage failures alike. Contains code and message. |
name | string or null | The operator display label from the element, when set. Absent from the wire when the element has no name. Use id for Rego matching. |
authority_id | string or null | The TRQP authority DID as actually sent. Post-template-substitution when query_resolved is true; raw template string on TEMPLATE_RESOLUTION_FAILED. Absent for AGENT_CARD_UNAVAILABLE, TRUST_REGISTRY_METADATA_UNAVAILABLE, IDENTITY_VP_VERIFICATION_FAILED, and TARGET_AGENT_IDENTITY_UNAVAILABLE. |
entity_id | string or null | The TRQP entity DID as actually sent. Absent on the same four codes as authority_id. |
action | string | The TRQP action as sent. Recognition queries with no operator-set value use the wire default "is". Always present. |
resource | string | The TRQP resource as sent. Recognition queries with no operator-set value use the wire default "ownedAgent". Always present. |
query_resolved | boolean | true when every template placeholder resolved. false on TEMPLATE_RESOLUTION_FAILED and on the four target-leg “unavailable” codes. Always present. |
For the complete list of error.code values, their categories, and field-population rules, see Trust Check element reference — Error codes.
input.metadata
Present when Custom 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
✓ = present when conditions met. — = never present on this path.
| Field | Inbound direct | Inbound G2G (fabric send) | Inbound GW2 (connection point) | Outbound |
|---|---|---|---|---|
input.http | ✓ | ✓ | ✓ | ✓ |
input.http.headers | Filtered | Filtered | Filtered | Empty {} |
input.gateway | ✓ | ✓ | ✓ | ✓ |
input.gateway.direction | "inbound" | "outbound" | "inbound" | "outbound" |
input.gateway.source_id | null | null | Caller DID | Managed agent DID |
input.gateway.target_id | null | Remote gateway DID | null | Target endpoint URL |
input.channel | ✓ | ✓ | ✓ | ✓ |
input.source_auth | If authenticated | If authenticated | If authenticated | — |
input.mcp | If MCP protocol | — | — | — |
input.a2a | If A2A / AP2 | If A2A / AP2 | If A2A / AP2 | — |
input.agent | If TR enabled | If TR enabled | If TR enabled | If TR enabled |
input.extension_identity | If VP present | — | If VP present | — |
input.payment | If x402 verified | — | If x402 verified | — |
input.identity_binding | — | — | If binding VP present | — |
input.trust_check_results | If TC elements configured for either leg. The leg that did not run carries [], never null. | If TC elements configured | If TC elements configured | If TC elements configured |
MCP tool policy input
MCP tool policies use a different input struct from gateway and surface policies. The package is still channel.policy, but the field paths are different.
Do not use input.source_auth.* in an MCP tool policy; that field is absent from this input. Use input.jwt for JWT claims. A rule referencing a field that does not exist evaluates to undefined and silently denies the request.
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
| Field | Type | Description |
|---|---|---|
input.mcp.method | string | The tool name from the tools/call request: the value the policy should gate on. |
input.mcp.params | object or null | Full parsed params from the tools/call body. |
input.mcp.protocol | string | Always "json-rpc-2.0". |
input.jwt | object or null | Flat JWT claims map when the caller used JWT bearer auth. null otherwise. Access claims directly: input.jwt.role, input.jwt.sub. |
input.channel.name | string | Surface name. |
input.channel.id | string or null | Surface ID. |
input.channel.protocol | string | Always "mcp" for MCP tool policies. |
input.request.method | string | HTTP method of the request. |
input.request.path | string | HTTP request path. |
input.request.source_ip | string | Source IP address of the caller. |
Field name mapping from PolicyInput to McpPolicyContext
| In gateway / surface policy | In MCP tool policy | Notes |
|---|---|---|
input.source_auth.claims.* | input.jwt.* | JWT claims are a flat map in McpPolicyContext |
input.mcp.tool_name | input.mcp.method | Tool name is in method in McpPolicyContext |
input.http.method | input.request.method | |
input.http.path | input.request.path | |
input.channel.config_id | input.channel.id | |
| Not available | input.request.source_ip | Source 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 headers whose names include authorization, cookie, or token (case-insensitive). These are stripped at policy input construction time. Policies cannot read bearer tokens, API key header values, or session cookies from input.http.headers. Use input.source_auth for authenticated identity information instead.
Related
- OPA policies: conceptual overview of how the gateway evaluates policies, evaluation order, and policy scopes.
- Apply OPA policies to your gateway and surfaces: create policy definitions and attach them to a gateway or surface.
- Control MCP tool access with per-tool policies: per-tool policy configuration using
McpPolicyContext. - Trust registries: how
input.agent.trust_verificationandinput.trust_check_resultsare populated.
Glad to hear it! Please tell us how we can improve more.
Sorry to hear that. Please tell us how we can improve.
Thank you for sharing your feedback so we can improve your experience.