Policy & the tool catalogue
Every decision the gateway makes is the evaluation of a signed policy bundle: Cedar rules plus a tool catalogue, compiled and published by the control plane from a git checkout. Default deny throughout.
The signed catalogue is authoritative
Section titled “The signed catalogue is authoritative”A tool the bundle does not describe is refused, even if the MCP server advertises it. An updated server, or a compromised one, can publish new tools at any time; if they are not in the catalogue, nobody approved their use.
Discovery is filtered to match: tools/list, resources/list and
prompts/list only show what policy allows. An invisible tool is never
attempted, which means that many fewer refusals to handle and that much less
surface offered to a prompt injection.
Argument-level rules
Section titled “Argument-level rules”Tool-level rules cannot say “send_message, but only to #support”.
Argument rules can. The catalogue declares, per tool, which arguments the
policy may read (policy_args: a name, a JSON pointer, a type, an optional
default); the gateway extracts exactly those and Cedar sees them as
context.args:
@id("support_channel_only")forbid (principal, action == Action::"tool_call", resource == Tool::"send_message")when { context.args.channel != "#support" };What makes this safe:
- The declared allowlist is the privacy boundary. Values exist in memory
for the decision; the log keeps
args_hash; a field nobody declared is never even read. - Extraction is total. Every declared argument arrives extracted, defaulted, or the call is refused before Cedar runs. A crafted argument shape is a plain recorded denial, never an evaluation error. The fail mode stays reserved for failures of Obsign’s own machinery, out of the agent’s reach.
- Visibility and permission are different questions.
tools/listevaluates without arguments, so an argument-restricted tool stays visible; the call path is the enforcement point.
Argument declarations require bundle format obsign-policy/2; the control
plane emits /2 only when a tool actually declares arguments, so a fleet
that never uses the feature never forces a gateway upgrade.
Stable rule identifiers, enforced
Section titled “Stable rule identifiers, enforced”Cedar numbers its rules policy0, policy1… by file order. Since that
identifier is engraved in the log, inserting a rule at the top would silently
rename all the following ones and make every earlier record wrong. Every rule
must therefore carry an @id("...") annotation; a bundle missing one is
rejected at load time.
What else passes the gate
Section titled “What else passes the gate”- Read channels —
resources/read,resources/subscribe,prompts/getmove data exactly astools/calldoes, so they go through the same gate (Cedar actionsresource_read,prompt_get, default deny). Resource URIs are minted at runtime, so policies match exactly (resource == Resource::"docs://runbook") or by pattern (context.target like "docs://*"). - Completions —
completion/completereuses the capability of what it completes (ref/resource→resource_read,ref/prompt→prompt_get): complete only what you could read. Left unarbitrated it would walk around the listing filter. - Server-initiated channels —
sampling/createMessageborrows the agent’s model;elicitation/createputs a question to the human. Both are arbitrated under their own Cedar actions, granted per server, default deny. Refused, the gateway answers the server in the agent’s place and records the attempt. - Everything else — a fixed allowlist of protocol machinery
(
initialize,ping, the discoveries, defined notifications) is relayed as-is; any other method is refused with-32601and recorded, with nopolicy_idso a scope refusal reads differently from a rule’s.