Constraints narrow a Passport below a service grant. Where a grant allows Slack message posting, a constraint can limit it to channels C0123 or C0456. Constraints are evaluated on every proxy call and a failing constraint denies the request the same way an out-of-scope service does.
Constraints ride on a passport's services[].constraintsarray. A grant can carry baseline constraints that every passport inherits; passports issued from that grant can narrow further but never loosen.
A constraint is a three-tuple of path,op, and value:
{
"path": "body.channel",
"op": "in",
"value": ["C0123", "C0456"]
}matches uses RE2 and caps the pattern at 256 characters. RE2 rejects backtracking constructs that would enable catastrophic backtracking.
Paths resolve against a normalized view of the outbound request. Case-insensitive header lookup, stable URL parsing, and dotted traversal into JSON bodies are handled for you.
A service scope can also use a closed operation policy. Each allowed operation declares its HTTP methods, path match, permitted request fields, and optional constraints. A request is denied when it does not match a declared operation or carries an undeclared field. A scope without this policy keeps the open behavior for compatibility.
Child constraints must be a superset of parent constraints - every parent rule rides through, and the child may add more. STACK rejects delegation with SCOPE_ESCALATION if the child drops a parent rule.
curl -s -X POST https://api.getstack.run/v1/services/grant \
-H "Authorization: Bearer $STACK_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "agt_support_bot",
"service_connection_id": "scon_slack_prod",
"scopes": ["chat:write"],
"constraints": [
{ "path": "url.pathname", "op": "eq", "value": "/api/chat.postMessage" },
{ "path": "body.channel", "op": "in", "value": ["C0123", "C0456"] }
]
}'Any passport issued against this grant can only post messages, and only to those two channels. An attempt to post to a third channel is denied at the proxy withFORBIDDEN and records acredential_outside_scope security event.
Raw constraints are the primitive. Named intents are a higher-level shorthand that expand to constraints server-side - the name encodes the provider convention so you don't have to reinvent it each time. For a catalog of intents and how they expand, see/docs/concepts/intents.