Skip to main content

Obligations

An obligation comes back with an allow and tells the enforcement point what else to do with the call. This page shows how to emit one from a custom control and what the gateway does with it.

Redact words in a response​

This control asks the gateway to replace the word confidential in every response from the orders resource that Register a managed resource sets up.

package pbac.controls.orders_redact

import rego.v1

rs_obligations contains {"filter_words": {"words": ["confidential"], "replacement": "[REDACTED]"}} if {
input.context.rs_context.resource_type == "urn:example:orders"
"orders:read" in input.context.rs_context.scopes
}

Save it as orders_redact.rego, check it, and deploy it. Run the block in the folder that holds the file.

opa check orders_redact.rego

jq -n --rawfile rego orders_redact.rego \
'{mode: "merge", rules: [{path: "pbac/controls/orders_redact.rego", content: $rego}]}' |
curl -fsS -X POST "$POLICYARC_URL/admin/api/policy-bundle/deploy" \
-H "X-Admin-API-Key: $POLICYARC_ADMIN_KEY" \
-H 'Content-Type: application/json' -d @-

Call the resource again.

sleep 5
resp=$(curl -fsS "$POLICYARC_URL/gateway/orders/orders/42" -H "Authorization: Bearer $TOKEN")
echo "$resp"
grep -qF '[REDACTED]' <<<"$resp" || { echo 'Expected [REDACTED] in the response' >&2; false; }

The block waits a few seconds for the policy engine to load the rule first. What we saw. The stub from Register a managed resource returned "note": "confidential", and the caller received "note": "[REDACTED]". The /introspect audit row for the call records the obligation under policyDecision.rs_obligations.

How an obligation reaches the gateway​

A control adds objects to a set named rs_obligations. Each object has one key, the obligation name, and its value is the obligation's settings. The product finds every package under pbac.controls by itself, as it does for deny.

rs_obligations is read only when a call is checked at /introspect with the call's context. The gateway always sends that context. Your rule reads it from input.context.rs_context.

FieldWhat it holds
resource_typeThe managed resource's type
resource_nameThe managed resource's name
scopesThe scope the matched route needs, as a one-item list
resource_idThe route's resource_id_param value, or the tool name on an MCP resource
tool_nameThe MCP tool name, on MCP calls only
tool_argumentsThe tool arguments. On a REST call, the gateway builds them from the JSON body, the query string and the path parameters.

A token request never carries this context, so an rs_obligations rule that tests input.context.rs_context, like the one below, does not fire at /token. A rule that tests only other input can fire there.

The /introspect response carries the obligations under extensions.obligations. A resource server of your own reads them from there, as described in Token introspection.

Obligations the gateway applies​

NameSettingsWhat the gateway does
filter_wordswords, a list, and replacement, which defaults to [REDACTED]Replaces each word in a text, JSON or XML response. The match is whole-word and ignores case. Other content types pass through.
filter_file_accessSet by the file access controlRemoves files the control does not allow from a list response.
require_file_checkSet by the file access controlLooks up a file's labels or location before the call and denies it if the control does not allow the file.
apply_labelSet by the file access controlAfter a create, labels the new file with its source's default label. If that fails, the file stays unlabelled and the create is kept.

The File access control page covers the last three.

The gateway also writes its own audit row for every call and adds the audit obligation itself, so a policy that emits audit changes nothing. It ignores an obligation name it does not know. If filter_words or another handler that changes the response fails, the gateway denies the call with HTTP 403 and the reason obligation_failed rather than return the unchanged body.

Subject obligations​

A control can also add to subject_obligations. Those go to the authorization server, not the gateway, and hold back a token until they are met. The CIBA approval control uses one, approval_required, to send the request to a human approver. Get a person's approval with CIBA covers that flow.