Skip to content

ToolPolicy CRD

The ToolPolicy custom resource defines CEL-based access control rules for tool invocations. Rules are evaluated by a policy proxy sidecar that intercepts requests to tool services.

apiVersion: omnia.altairalabs.ai/v1alpha1
kind: ToolPolicy

Defines which tools this policy applies to. The proxy matches incoming requests based on the X-Omnia-Tool-Registry and X-Omnia-Tool-Name headers.

FieldTypeRequiredDescription
registrystringYesName of the ToolRegistry to match.
tools[]stringNoSpecific tool names to match. If empty, applies to all tools in the registry.
spec:
selector:
registry: customer-tools
tools:
- process_refund
- issue_credit

CEL-based deny rules evaluated in order. The first rule whose CEL expression evaluates to true denies the request. Minimum 1 rule is required.

Each rule has:

FieldTypeRequiredDescription
namestringYesUnique identifier for the rule.
descriptionstringNoHuman-readable description of the rule’s purpose.
deny.celstringYesCEL expression that, when true, denies the request.
deny.messagestringYesMessage returned to the caller when the rule denies.
spec:
rules:
- name: max-refund-amount
description: "Prevent refunds over $500"
deny:
cel: 'double(body.amount) > 500.0'
message: "Refund amount exceeds the $500 limit"
- name: require-reason
description: "All refunds must include a reason"
deny:
cel: '!has(body.reason) || body.reason == ""'
message: "A reason is required for refund requests"

The following variables are available in CEL expressions:

VariableTypeDescription
headersmap<string, string>All HTTP request headers (first value only for multi-value headers).
bodymap<string, dyn>Parsed JSON request body. Empty map if body is not JSON.

The CEL environment includes the cel-go string extensions, providing functions like:

  • string.contains(substring) — check if a string contains a substring
  • string.startsWith(prefix) — check if a string starts with a prefix
  • string.endsWith(suffix) — check if a string ends with a suffix
  • string.matches(regex) — regex matching
  • string.lowerAscii() — convert to lowercase
  • string.upperAscii() — convert to uppercase
  • string.trim() — trim whitespace
  • string.split(separator) — split into a list

Example using string extensions:

- name: block-external-urls
deny:
cel: 'has(body.url) && !body.url.startsWith("https://internal.")'
message: "Only internal URLs are allowed"

Claims that must be present as X-Omnia-Claim-* headers. If a required claim header is missing, the request is denied before CEL rules are evaluated.

FieldTypeRequiredDescription
claimstringYesClaim name (maps to X-Omnia-Claim-<Claim> header).
messagestringYesError message returned when the claim is missing.
spec:
requiredClaims:
- claim: Team
message: "Team claim is required — configure claimMapping in your AgentPolicy"
- claim: Customer-Id
message: "Customer ID claim is required for this tool"

Headers to inject into the upstream request after policy evaluation passes. Each rule provides either a static value or a dynamic cel expression (mutually exclusive).

FieldTypeRequiredDescription
headerstringYesHTTP header name to inject.
valuestringConditionalStatic header value. Mutually exclusive with cel.
celstringConditionalCEL expression computing the header value. Mutually exclusive with value.
spec:
headerInjection:
# Static value
- header: X-Policy-Version
value: "v1"
# Dynamic value from claims
- header: X-Tenant-Id
cel: 'headers["X-Omnia-Claim-Customer-Id"]'
# Computed value
- header: X-Request-Source
cel: '"policy-proxy/" + headers["X-Omnia-Agent-Name"]'

Controls how the policy is applied.

ValueDescription
enforce(Default) Deny rules block the request with a 403 response.
auditDeny rules are evaluated but the request is allowed through. Violations are logged with wouldDeny: true.

Defines behavior when policy evaluation encounters an error (e.g., CEL expression failure).

ValueDescription
deny(Default) Deny the request on evaluation failure.
allowAllow the request despite the evaluation error.

Configures audit logging for policy decisions.

FieldTypeRequiredDescription
logDecisionsboolNoEnable logging of all policy decisions (allow and deny).
redactFields[]stringNoField names whose values are redacted in audit logs.
spec:
audit:
logDecisions: true
redactFields:
- credit_card
- ssn
- password
ValueDescription
ActivePolicy is valid, all CEL rules compiled successfully.
ErrorPolicy has a configuration error (e.g., invalid CEL expression).

Integer count of compiled CEL rules.

Standard Kubernetes conditions indicating the current state of the resource.

The most recent .metadata.generation observed by the controller.

When using kubectl get toolpolicies, the following columns are displayed:

ColumnSource
Registry.spec.selector.registry
Mode.spec.mode
Phase.status.phase
Rules.status.ruleCount
Age.metadata.creationTimestamp

When a request is denied, the proxy returns HTTP 403 with:

{
"error": "policy_denied",
"rule": "max-refund-amount",
"message": "Refund amount exceeds the $500 limit"
}
apiVersion: omnia.altairalabs.ai/v1alpha1
kind: ToolPolicy
metadata:
name: refund-limits
namespace: production
spec:
selector:
registry: customer-tools
tools:
- process_refund
rules:
- name: max-refund-amount
description: "Prevent refunds over $500"
deny:
cel: 'double(body.amount) > 500.0'
message: "Refund amount exceeds the $500 limit"
- name: require-reason
description: "All refunds must include a reason"
deny:
cel: '!has(body.reason) || body.reason == ""'
message: "A reason is required for refund requests"
- name: block-banned-customers
description: "Deny refunds for flagged accounts"
deny:
cel: 'has(body.customer_status) && body.customer_status == "banned"'
message: "Refunds are not available for this account"
requiredClaims:
- claim: Team
message: "Team identity is required"
- claim: Customer-Id
message: "Customer ID is required for refund operations"
headerInjection:
- header: X-Tenant-Id
cel: 'headers["X-Omnia-Claim-Customer-Id"]'
- header: X-Audit-Source
value: "policy-proxy"
mode: enforce
onFailure: deny
audit:
logDecisions: true
redactFields:
- credit_card

Expected status after reconciliation:

status:
phase: Active
ruleCount: 3
observedGeneration: 1
conditions:
- type: Ready
status: "True"
reason: RulesCompiled
message: "3 rules compiled successfully"