Skip to content

Policy reference

Every name a policy may use is here, in both policy languages. This page is generated by asking the engines: the actions are the vocabulary the admin API refuses an unknown name against, and each condition scope’s variables are what that scope’s CEL environment declares. A name absent here is a name the gateway refuses.

There are two languages because there are two planes, and they are not alike. The admin plane decides who may change the deployment. That is infrequent, high-stakes, and a question about a principal, and it is written in Cedar, whose policies are analysable and whose default is deny. The data plane decides what a request may do. That happens on every request, at latency that shows up in a percentile, and it is a question about a payload, so it is written in CEL, the expression language Kubernetes admission policy already put in front of the same operators. Neither reaches the other. A Cedar policy cannot read a request body and a CEL condition cannot authorize an API call. See Restrict what an admin caller can do for the longer argument.

There is one policy file, named by admin.policy_file. Every request to the admin API is one authorization question (a principal, an action, a resource), and the answer is deny unless a policy permits it and no policy forbids it.

There are four, and they are few by design. A vocabulary an operator can learn in one sitting is one they will write correct policies in.

entity written is
Admin Admin::"<issuer>/<subject>" the caller. Its roles are its Cedar parents
Role Role::"platform" a role. Membership is the only thing about a principal that policies test
Action Action::"createKey" one operation, named by its OpenAPI operation ID
Resource Resource::"keys", Resource::"keys/team-a", Resource::"profiles/team-a" a resource class, one instance within it, or one configuration object

Every action is also a member of exactly one group, either Action::"read" or Action::"write", so a policy can say “may read everything” without an endpoint list that goes stale the next time one is added. And every action acts on exactly one resource class, so a policy scoped to Resource::"keys" covers every action on keys and every key.

The same vocabulary is published as a Cedar schema, admin.cedarschema, which is what the gateway validates a policy file against at load. Check a policy before it reaches a gateway with:

Terminal window
$ cedar validate --schema admin.cedarschema --policies admin.cedar

50 actions across 13 resource classes. The names are the OpenAPI operation IDs, so a policy, a generated client and the audit trail all spell an operation the same way.

action group
getA2AAgentCard read Project an agent’s card
listA2AAgents read List configured A2A agents
action group
listBackends read List configured backends
action group
listBudgets read List budgets and what has been spent against them
action group
getCatalogReport read Report every routable channel and the provenance of its capability claim
action group
listAuditHeads read List witnessed audit chain heads
listClusterMembers read List cluster members
removeClusterMember write Remove a cluster member
restoreClusterSnapshot write Restore the cluster state from a snapshot
saveClusterSnapshot write Save a snapshot of the cluster state
action group
checkCondition read Compile a CEL condition the way a write would, and say what is wrong where
getConfig read Summarize the active configuration
getConfigObject read Read one named object
getConfigSchema read Read the configuration document schema
getConfigSource read Read one configuration source
inspectGuardrails read Run the guardrails over text without a request behind it
listConfigObjects read List the stored objects of one kind
listConfigSourceRevisions read List a source’s retained revisions
listConfigSources read List configuration sources
listGuardrailDetectors read List the compiled guardrail detectors
listGuardrailEntityTypes read List the entity types the active guardrails can annotate
listGuardrailRules read List the compiled guardrail rules
deleteConfigField write Remove one block or scalar
deleteConfigObject write Remove one named object
deleteConfigSource write Remove one configuration source
putConfigField write Write one block or scalar
putConfigObject write Write one named object
putConfigSource write Write one configuration source
reloadConfig write Reload the configuration file
action group
listKeys read List virtual keys
listProfiles read List key profiles
preflightRequest read Resolve a request without sending it, on behalf of a named key
createKey write Mint a virtual key
revokeKey write Revoke a virtual key
action group
discoverMCPServer read Probe an MCP server
listMCPServers read List configured MCP servers
action group
listModels read List declared models
action group
getReconcileStatus read Reconciliation status
action group
getRegistryServerVersion read Read one registry server version
listRegistryServerVersions read List a registry server’s versions
listRegistryServers read List registry servers
publishRegistryServer write Publish a server to the registry
action group
getKeyring read Describe the keyring
listSecrets read List the secrets this node can resolve
deleteSecret write Remove a secret
putSecret write Store a secret
rewrapKeyring write Change the keyring
action group
listSuspensions read List what is out of service
suspend write Suspend a key, provider or rule
unsuspend write Put something back into service early

A write to a configuration source is decided once per object it adds, removes or changes, and each object is a resource of its kind’s class: Resource::"profiles/team-a" is the profile, whichever source holds it. An object has two parents, the kind container and the source container Resource::"config/<source>", so a policy may scope by either, and resource.source names the source. Creating or deleting a source itself is decided on the source container, which == names and in covers with everything inside it.

kind instances is
Resource::"credentials" Resource::"credentials/<name>" one element of the credentials list
Resource::"providers" Resource::"providers/<name>" one element of the providers list
Resource::"budgets" Resource::"budgets/<name>" one element of the budgets list
Resource::"profiles" Resource::"profiles/<name>" one element of the profiles list
Resource::"mcp" Resource::"mcp/<name>" one element of the mcp list
Resource::"access_rules" Resource::"access_rules/<name>" one element of the access_rules list
Resource::"a2a" Resource::"a2a/<name>" one element of the a2a list
Resource::"guardrail_detectors" Resource::"guardrail_detectors/<name>" one element of the guardrail_detectors list
Resource::"guardrail_rules" Resource::"guardrail_rules/<name>" one element of the guardrail_rules list
Resource::"auth" none: the block is one object the auth block or scalar, one owner
Resource::"a2a_cards" none: the block is one object the a2a_cards block or scalar, one owner
Resource::"reconcile" none: the block is one object the reconcile block or scalar, one owner
Resource::"guardrails" none: the block is one object the guardrails block or scalar, one owner
Resource::"models" none: the block is one object the models block or scalar, one owner
Resource::"catalog_overlays" none: the list is one object the catalog_overlays list, one per source that carries it
Resource::"max_body_bytes" none: the block is one object the max_body_bytes block or scalar, one owner
Resource::"default_provider" none: the block is one object the default_provider block or scalar, one owner
Resource::"on_fidelity_loss" none: the block is one object the on_fidelity_loss block or scalar, one owner
Resource::"public_url" none: the block is one object the public_url block or scalar, one owner

Where a kind shares a name with an action class, they are one container on purpose: Resource::"mcp/issues" is the server both to probe and to edit.

These are the only attributes the gateway sets, and a policy reading anything else is refused when the file is compiled rather than left to fail at evaluation. That matters more than it looks. Cedar skips a policy that errors, so an unguarded attribute read inside a forbid means that forbid silently stops forbidding.

variable attributes
principal issuer, kind, name, subject
resource class, id, source

There is no context, no entity carries tags, and an action carries no attributes: its group and its class are membership, action in Action::"read" and resource in Resource::"keys". The engine passes none of these, so reads of them are refused outright.

resource.source is the configuration source an object or a source container sits in, and empty on every other resource. resource.id is the instance name and empty on a class or a block.

principal.kind is how the caller was identified: oidc for a token, local on the Unix socket, loopback on an issuerless loopback listener. A deployment can hold one to a different standard than another. On the local socket principal.subject is the caller’s pid, recorded for the trail and not a thing to decide on, because a pid is reused.

There are three kinds of condition, each compiled against its own vocabulary. An access rule’s is compiled against the hops its at names. A condition must evaluate to bool, is checked when it is written rather than when it first matters, and carries a cost budget, so an expression over arguments the client sized cannot become the request that stalls the gateway.

POST /admin/v1/config/check-condition answers all of this for one expression, in the same compile the write performs: the errors with their positions, the type it evaluates to, and the vocabulary it was checked against. The console’s editors complete from it, and this page is generated from it.

Admits or refuses a request at every hop, deployment-wide or inside a profile, and so sees only who is calling. Its scope name, for check-condition, is access_rules.

variable type
key map(string, dyn)
agent map(string, dyn)
user map(string, dyn)

Admits or refuses a model request before it is routed. Its scope name, for check-condition, is access_rules with at: [llm]; a rule at several hops may read only what they all declare.

variable type
key map(string, dyn)
agent map(string, dyn)
user map(string, dyn)
model string
provider map(string, dyn)
route string

Makes the same decision one hop lower, at the MCP tool call. Its scope name, for check-condition, is access_rules with at: [mcp]; a rule at several hops may read only what they all declare.

variable type
key map(string, dyn)
agent map(string, dyn)
user map(string, dyn)
server string
method string
name string
args map(string, dyn)

Makes the same decision at the peer-agent hop, over an A2A request. Its scope name, for check-condition, is access_rules with at: [a2a]; a rule at several hops may read only what they all declare.

variable type
key map(string, dyn)
agent map(string, dyn)
user map(string, dyn)
peer string
method string
role string
task_id string
context_id string
parts list(map(string, dyn))

Decides what to do about what the detectors found. Its scope name, for check-condition, is guardrails.

variable type
annotations list(map(string, dyn))
types list(string)
entities list(string)
key map(string, dyn)
agent map(string, dyn)
user map(string, dyn)
model string
provider map(string, dyn)
route string
side string
path string
server string
tool string

Narrows a redact rule to some of the annotations it matched, one at a time. Its scope name, for check-condition, is guardrails_select.

variable type
a map(string, dyn)
key map(string, dyn)
agent map(string, dyn)
user map(string, dyn)
model string
provider map(string, dyn)
route string
side string
path string
server string
tool string

map(string, dyn) says a variable is a map and nothing about what is in it. Two of them are fixed:

key is the virtual key the request presented.

field
name the key’s name
profile the profile it was minted into, empty when it names none
metadata map(string, string), whatever the key was minted with

Metadata is the one a rule normally reads, and it is also where a condition most often fails. key.metadata.team on a key that has no team is an error, not a false, so guard it with has(), or leave on_error at its default and have the rule refuse.

provider is the provider the request is routed to, at the LLM hop and in every guardrail condition.

field
name the provider’s name in the configuration
metadata map(string, string), the provider’s metadata block, verbatim; empty when it declared none

A rule about where data may go reads the destination’s facts rather than its name: !(has(provider.metadata.residency) && provider.metadata.residency == "sa") holds when a provider is added without the field, where a list of names would not.

annotations has one entry per finding, in guardrails.rules[].when. a in a select predicate is one of these.

field
category the annotation namespace: pii, credential, injection, topic, lang
context_supported one of the recognizer’s context words was near the match and raised its score. With validated this is how a rule asks for a corroborated finding without naming a threshold: `a.validated
detector the name of the detector that produced it, as configured
end byte offset into the segment’s text, exclusive
entity_type Presidio’s value verbatim for PII (PERSON, CREDIT_CARD); for other categories, that category’s label (JAILBREAK, AWS_ACCESS_KEY)
kind what the segmenter asserted the segment is: system, user, assistant, tool_args, tool_result, output, header, query
recognizer the recognizer within that detector, where there is one
ref the segment these offsets belong to, as a path into the request body
score the detector’s confidence, in [0,1]. See PII coverage for what each built-in recognizer can produce, and note that nothing is dropped by default
start byte offset into the segment’s text, inclusive
text the matched text itself, which never leaves the process. An allow-list rule (“ignore our own support number”) is the common case
type category and entity joined, pii/CREDIT_CARD, and usually what a rule should match on
validated a checksum confirmed the match. false also covers a recognizer with no validator and one that abstained

types and entities are the same findings flattened to lists of strings, for the common rule that only asks whether something was found at all.

Every scope has the same 49 functions and 7 macros: the CEL standard library plus the strings, lists and sets extensions, which are the ones Kubernetes admission policy also enables.

Macros:

all exists exists_one filter has map sortBy

Functions and methods:

!_ -_ bool bytes charAt contains distinct double duration dyn
endsWith flatten format getDate getDayOfMonth getDayOfWeek
getDayOfYear getFullYear getHours getMilliseconds getMinutes
getMonth getSeconds in indexOf int join lastIndexOf lists.range
lowerAscii matches replace reverse sets.contains sets.equivalent
sets.intersects size slice sort split startsWith string
strings.quote substring timestamp trim type uint upperAscii