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.
| Field | What it holds |
|---|---|
resource_type | The managed resource's type |
resource_name | The managed resource's name |
scopes | The scope the matched route needs, as a one-item list |
resource_id | The route's resource_id_param value, or the tool name on an MCP resource |
tool_name | The MCP tool name, on MCP calls only |
tool_arguments | The 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
| Name | Settings | What the gateway does |
|---|---|---|
filter_words | words, 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_access | Set by the file access control | Removes files the control does not allow from a list response. |
require_file_check | Set by the file access control | Looks up a file's labels or location before the call and denies it if the control does not allow the file. |
apply_label | Set by the file access control | After 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.
Send feedback about this page