Skip to main content

Policy Context

Reference

Every CEL expression evaluates against an object map pre-populated from the request. This page lists every available field, its type, and what it contains.

Generated field reference

This page's schema-backed sections below are generated directly from the policy engine's field registry and kept in sync automatically — a policy referencing an undeclared field, or misspelling one within a registered namespace, fails to save. A few namespaces further down (object.identity, object.user, object.context, object.http, object.tokenIssuance) are not yet registered there and are maintained by hand until they are.

Available context

Every CEL expression evaluates against an object map pre-populated from the request. The fields are grouped by namespace below. Use object.mcp.tool != "" to target MCP tool calls, object.llm.model != "" to target LLM calls, and object.http.host != "" to target network egress calls — each is empty for the other two call types.

object.mcp is an alias for object.tool — both names route to the same fields in the activation map. This page documents them under object.mcp for tool-call fields; object.tool.datasource, for example, is the same field as object.mcp.datasource.

These namespaces are registered in the policy engine's field registry — a policy referencing a field outside them, or misspelling one within them, fails to save. Grouped by the tag that owns each field (one tag per module).

object.agent

FieldTypeDescription
object.agent.agentIdstringUnique agent or developer identifier (email or agent ID)
object.agent.autonomyLevelintNumeric tier, 1–4 — use for range checks like autonomyLevel >= 3
object.agent.autonomyTierstringCSA NIST autonomy tier, "T1"–"T4". Unassigned agents resolve to "T2"
object.agent.capabilitieslist(string)Granted capability tags, e.g. "premium-models"
object.agent.labelsmap(string, dyn)Custom agent metadata, e.g. cost center or environment tag
object.agent.namespacestringKubernetes namespace of the agent's service account
object.agent.nodeNamestringKubernetes Node name the calling Pod is scheduled on
object.agent.podNamestringKubernetes Pod name, from the projected ServiceAccount token's kubernetes.io claim (K8s 1.20+)
object.agent.podUIDstringKubernetes Pod UID, same claim as podName
object.agent.quarantinedbooltrue if the agent is suspended
object.agent.tokenClaimsmap(string, dyn)All raw JWT claims — useful for custom IdP attributes

object.exceptions

FieldTypeDescription
object.exceptionslist(dyn)Escape-hatch read-only list of active granted exceptions applying to this call (§7)

object.llm

FieldTypeDescription
object.llm.currentPromptstringText of the last user message
object.llm.inputTokensintInput token count for this request (0 if not yet known)
object.llm.messageslist(dyn)Full conversation history — each item has role and content
object.llm.modelstringRequested model identifier, e.g. claude-sonnet-4-6. Empty for MCP calls.
object.llm.responsedynLLM response projection on the response pass, including usage/token counts (§3.6)
object.llm.tierstringCost tier: "premium", "standard", or "economy"
object.llm.upstream.jurisdictionstringData residency region, e.g. "EU", "US"
object.llm.upstream.zeroDataRetentionboolUpstream provider guarantees zero data retention

object.mcp

FieldTypeDescription
object.mcp.argumentsmap(string, dyn)Tool arguments
object.mcp.commandstringShell command text (shell tools only)
object.mcp.commandRiskstringPre-classified risk: "risky", "moderate", or "safe"
object.mcp.responsedynMCP tool response projection on the response pass (§3.6)
object.mcp.ssrf.hasPrivateIPbooltrue if a URL argument resolved to a private or internal address
object.mcp.ssrf.hostnameslist(string)Parsed hostname of every URL argument found
object.mcp.ssrf.resolvedIPslist(string)Every IP address resolved from URL arguments in this call
object.mcp.toolstringMCP tool name, e.g. bash, read_file. Empty for LLM calls.
object.mcp.trustTierstringMCP server trust: "trusted/internal", "trusted/verified", "untrusted/external"

object.request

FieldTypeDescription
object.request.credentialEntitieslist(string)Credential entity codes found (e.g. PRIVATE_KEY, AWS_KEY, CONNECTION_STRING), deduplicated
object.request.hasCredentialboolTrue when a credential or secret was detected
object.request.hasInjectionboolTrue when a prompt-injection pattern was detected
object.request.hasPIIboolTrue when any PII entity was detected in the scanned content
object.request.hasUnsafeContentboolTrue when an unsafe-content finding was reported
object.request.injectionScoredoubleHighest confidence score among injection findings (0.0-1.0)
object.request.piiConfidencedoubleHighest confidence score among PII findings (0.0-1.0)
object.request.unsafeEntitieslist(string)Unsafe-content entity labels, deduplicated

object.response

FieldTypeDescription
object.response.credentialEntitieslist(string)Credential entity codes found in the response, deduplicated
object.response.findingslist(dyn)Per-finding spans detected in the response: list of {class, entity, severity, score, start, end}. start/end are byte offsets targeted by patchType:Redaction. Empty for unbuffered streaming responses.
object.response.hasCredentialboolTrue when a credential or secret was detected in the response
object.response.hasInjectionboolTrue when a prompt-injection pattern was detected in the response
object.response.hasPIIboolTrue when any PII entity was detected in the response content
object.response.hasUnsafeContentboolTrue when an unsafe-content finding was reported in the response
object.response.injectionScoredoubleHighest confidence score among injection findings in the response (0.0-1.0)
object.response.piiConfidencedoubleHighest confidence score among PII findings in the response (0.0-1.0)
object.response.unsafeEntitieslist(string)Unsafe-content entity labels found in the response, deduplicated

object.session

FieldTypeDescription
object.session.budgetUSDdoubleAllocated cost budget for this session
object.session.deniedInSessionintPolicy denials already recorded in this session
object.session.developerIdstringEmail of the developer who started the session
object.session.intentstringDeclared session purpose
object.session.isPresentbooltrue if a session context is active
object.session.percentUseddoubleFraction of budget consumed (0.0-1.0)
object.session.spentUSDdoubleCost spent so far in this session
object.session.ticketIdstringAssociated Jira / GitHub issue ID
object.session.toolCallCountintTotal tool calls in the session
object.session.toolsCalledInSessionlist(string)Distinct tools used so far (capped at 200)

Resolved defaults — object.defaults.*

FieldTypeDescription
object.defaults.budget.limitslist(dyn)Effective budget limits for the requested model, one entry per matching scope tier, most specific first. Each entry has tier (token, agent, user, group, or global), scope, modelPattern, limitUSD, window (30d, 7d, 24h, 1h), mode (deny, approval, warn, audit), and warnAt (percent, 0-100). Empty/absent for calls without a model.
object.defaults.compression.matchedOnstringWhich rule selected the mode: trusted-mcp:<pattern>, never-compress:<pattern>, agent-rule:<pattern>, tool-rule:<pattern>, or default — debugging/audit context only.
object.defaults.compression.modestringThe effective compression mode for this MCP tool call: off, conservative, standard, aggressive, ml, semantic, or ast. This is the request-time base mode — it does not reflect content-type detection or context-pressure escalation, both computed only on the response pass. Absent for LLM calls and absent when compression is disabled.
object.defaults.egress.allowedDomainslist(string)The intersected network-egress allowlist across every scope tier that applies to the caller — exact domains and *.suffix patterns.
object.defaults.model.allowedModelslist(string)The caller's effective LLM model allowlist — the org-wide ceiling intersected with their teams' allowlists, unioned with any per-developer overrides, and folded with any granted llm.model-access exceptions. Absent for calls without a model, and absent when the resolved list is unrestricted.
object.defaults.routing.matchedOnstringWhy routing's static pick was selected: labels, models, groups, wildcard, model-family, or fallback — debugging/audit context only.
object.defaults.routing.upstreamNamestringThe effective upstream name for this call: routing's static pick, or the name a patchType: Defaults policy overrode it to. Absent for calls with no upstream configured (and for MCP tool calls, which have no upstream to route).

Overriding resolved defaults — patchType: Defaults

A MutatingPolicy with patchType: Defaults can narrow the object.defaults.* values above before validating policies read them — see Defaults mutations for how narrowing and the widening clamp work. routing and compression are the exceptions to "narrow only": there is no ceiling to narrow against, so a patchType: Defaults policy setting routing.upstreamName or compression.mode freely overrides the resolved value. For routing, the request is then forwarded to the named upstream. For compression, the response is then compressed using the overridden mode.

note

object.defaults.* is populated live and enforced. egress and budget are enforced on both LLM paths (Claude Code / Anthropic-format requests and OpenAI-compatible chat completions) and MCP tool calls. routing and model are LLM-only and absent for MCP tool calls (MCP calls have no model to route or scope). compression is MCP-only and absent for LLM calls (see below). The resolved (and exception-adjusted, clamp-protected) object.defaults.egress.allowedDomains list gates each call's destination (see Restrict Network Egress), object.defaults.budget.limits reflects the effective per-model spend caps enforced against live spend on every call, object.defaults.model.allowedModels gates the requested model (enforced by the built-in team-model-scope policy), and object.defaults.routing.upstreamName — if overridden by a patchType: Defaults policy — determines which upstream the call is actually forwarded to.

compression is the mirror image of routing: it is MCP-only and absent for LLM calls. The resolved (or overridden) object.defaults.compression.mode drives the actual compression of the tool response on the response pass — a patchType: Defaults policy setting compression.mode (for example, the skip-truncation-for-large-non-code-reads pattern) has a real, observable effect on the bytes returned to the caller, not just on what a later policy reads back. Content-type detection and context-pressure escalation still run on top of the resolved/overridden base mode on the response pass, exactly as they do for the unoverridden case — only agent/tool-rule/global-default selection is replaced by the override.

Identity verification — object.identity.*

FieldTypeDescription
object.identity.verifiedbooltrue only when the identity was cryptographically or credential-verified — a Kubernetes ServiceAccount/OIDC JWT that passed full signature, issuer, and audience validation, or a proxy-issued VirtualKey resolved by hash lookup. false for anonymous, none-mode, passthrough (unsigned claims), and other self-declared identities.
object.identity.verification_methodstringHow verified was established: "k8s-sa", "oidc-user", "virtualkey", "pat", "session", or "" when unverified.
object.identity.effective_allowed_modelslist<string>The developer's team-resolved model allowlist (global ceiling intersected with team grants), computed BEFORE any granted llm.model-access exception is folded in. Empty = unrestricted. The built-in team-model-scope policy enforces object.defaults.model.allowedModels instead (see the resolved defaults table above) — the exception-folded version of this same list — so a custom policy that needs live exception grants honored should read object.defaults.model.allowedModels, not this field.
object.identity.pat_allowed_modelslist<string>Per-PAT narrowing of effective_allowed_models. Empty = no additional restriction.
object.identity.pat_allowed_toolslist<string>MCP tool names this PAT may call. Empty = unrestricted.
Enforcing verified identity

See Requiring verified identity for the identity.requireVerifiedIdentity config flag that hard-blocks requests where object.identity.verified is false.

Token issuance — object.tokenIssuance.*

Populated only when a policy evaluates a Cross-App Access / ID-JAG token-issuance request — the Authorization Server's grant hook — rather than a proxied tool call or LLM request. This is a separate evaluation point that runs before any token is minted, so object.mcp.* and object.llm.* are not populated here.

FieldTypeDescription
object.tokenIssuance.serverstringName of the MCP server (datasource) the caller is requesting a token for, resolved from the per-server issuer endpoint. Access Bindings enforce group→server grants at this point: a team with no binding covering this server gets invalid_grant. Empty on the single-issuer /oauth/token endpoint.
object.tokenIssuance.grantTypestringThe OAuth grant type being exchanged, e.g. "urn:ietf:params:oauth:grant-type:jwt-bearer". Use object.tokenIssuance.grantType != "" to scope a policy to the token-issuance evaluation point.
object.tokenIssuance.requestedScopestringThe space-delimited scope the client requested for the minted access token (empty if none requested). Compare against a binding's allowed scopes to enforce scope narrowing.
object.tokenIssuance.resourcestringThe RFC 8707 resource indicator the client requested the token for (the target MCP server's canonical URI), empty if none supplied.

Network egress call — object.http.*

Populated for raw outbound HTTP(S) requests intercepted by the network egress filtering proxy — an agent process's own network calls, not MCP tool calls or LLM requests. Empty for both of those; gate egress-only rules with object.http.host != "".

FieldTypeDescription
object.http.hoststringRequest host/authority, e.g. "api.github.com". Empty for non-egress calls.
object.http.methodstringHTTP method, e.g. "GET", "POST"
object.http.pathstringRequest path, e.g. "/repos/octocat/hello-world"
object.http.querystringRaw query string, e.g. "tenant=acme&env=prod"; empty when the request has no query.
object.http.schemestring"http" or "https"
object.http.bodySnippetstringFirst few KB of the request body; empty when the body was not buffered

Delegating user — object.user.*

Populated when a human OIDC token is present alongside the agent token. All fields are empty / false for machine-to-machine calls — guard with object.user.isPresent.

FieldTypeDescription
object.user.isPresentbooltrue if a user token was validated
object.user.emailstringUser email from OIDC token
object.user.groupslist<string>OIDC group memberships
object.user.roleslist<string>OIDC roles or approved actions
object.user.tokenClaimsmapRaw user JWT claims for custom IdP attributes

Context efficiency — object.context.*

Real-time signals from the session anomaly detector, injected at policy evaluation time. All fields default to 0 when monitoring is not enabled.

FieldTypeDescription
object.context.fillPctfloatFraction of the model's context window currently in use (0.0–1.0)
object.context.loopScorefloatStructural repetition score across recent outputs (0.0–1.0; higher = more looping)
object.context.repeatReadCountintTimes the same file or resource has been read in this session
object.context.latencyGrowthRatiofloatCurrent call latency relative to session baseline (>1.0 = degrading)
object.context.cacheHitRatefloatFraction of prompt tokens served from cache (0.0–1.0)

Response redaction — patchType: Redaction

A MutatingPolicy mutation with patchType: Redaction (and proxy.nirmata.io/direction: response) removes the sensitive spans reported in object.response.findings[] from the response text. On non-streaming responses — MCP tool results and synchronous LLM replies (Anthropic Messages, OpenAI Chat Completions and Responses, Copilot passthrough, and the Chat↔Anthropic / Cursor-BYOK translation paths) — the redacted text is what actually reaches the caller. On streaming responses the spec is computed but not yet spliced into the streamed bytes; see the limitation note below. The mutation expression returns a spec — which finding classes/entities to redact and the replacement string:

mutations:
- patchType: Redaction
applyConfiguration:
expression: |
{"classes": dyn(["PII", "CREDENTIAL"]), "replacement": dyn("[REDACTED]")}

Spec fields (all optional): redactAll (bool — redact every finding regardless of class), classes (list of finding classes to redact, ignored when redactAll is true), entities (further limit to specific entity codes), replacement (substitution string, default [REDACTED]). Wrap each value in dyn(...) — CEL map literals are homogeneously typed, so a mixed-value map (a list plus a string) must use dyn for the values to compile (same convention as patchType: PIITokenize).

Redaction requires the buffered-text contract: a spec is only computed on the final / buffered response pass, where the full reply text and its finding spans are available. An unbuffered per-chunk streaming pass carries no assembled text, so object.response.findings[] is empty and no spec is produced — enable bufferForDLP to buffer streaming responses for full-text scanning and spec computation.

Streaming redaction is not yet applied to the wire

On streaming responses (including those buffered under bufferForDLP) the response is still evaluated and DLP-scanned — a scan or policy deny blocks the whole stream — and a redaction spec is still computed on the buffered-final pass. But the redacted text is not currently spliced back into the streamed chunks; the SSE deltas are forwarded as received. Response-side redaction therefore reaches the caller only on non-streaming responses (MCP tool results and synchronous LLM replies) today. To guarantee a sensitive span never leaves the proxy on a streamed response, write a policy that denies it rather than redacts it, or have the client issue a non-streaming request.

Deprecation: patchType: MCPResponse

The older patchType: MCPResponse (regex→replacement pairs collected on the request pass and applied to MCP tool responses) is superseded by Redaction. During the deprecation window both are accepted and MCPResponse keeps its exact existing semantics; after the window closes, saving a policy that still uses MCPResponse is rejected with a pointer to Redaction. Migrate MCP response-redaction policies to Redaction.

Time — top-level variable

VariableTypeDescription
nowtimestampCurrent wall-clock time at policy evaluation. Use now.getHours(), now.getDayOfWeek() for business-hours rules.

Built-in policy library

AIControls ships with a curated library of policies covering common governance scenarios. Enable them from Policies → Library:

CategoryExample policies
SecurityBlock credential patterns in prompts, deny requests with PII in tool arguments
CostEnforce model tiers by developer group, block Opus for non-engineering roles
ComplianceRequire audit mode for regulated data namespaces, block data export tools
OperationsRate-limit bash calls per session, require HITL for infrastructure mutations
Autonomy GovernanceRecord every T1 action, audit high-risk tools by T2 agents, monitor T3+ behavior, flag privileged ops by T4 agents for HITL — see Autonomy Tiers

See also