Cedar Authorization
Cedar Authorization evaluates Cedar policies before each tool call, giving you declarative, identity-aware access control over agent behavior. It ships as a vended intervention handler in both the Python and TypeScript SDKs.
How it works
Section titled “How it works”The handler sits at the tool-call boundary. When the agent attempts to invoke a tool, Cedar Authorization maps the call to a Cedar authorization request and evaluates your policies. If no permit statement matches, the tool call is denied and the agent receives feedback explaining the denial.
| Cedar concept | Maps to | Example |
|---|---|---|
| Principal | User identity | User::"alice@acme.com" |
| Action | Tool name | Action::"search" |
| Resource | Static (unconstrained) | Resource::"agent" |
| Context.input | Tool arguments | { query: "quarterly report" } |
| Context.session | Invocation metadata | { hour_utc: 14, call_count: 3, role: "admin" } |
The design is fail-closed: if principal identity cannot be resolved, all tool calls are denied.
Basic usage
Section titled “Basic usage”Permit specific tools and deny everything else:
from strands import Agent, toolfrom strands.vended_interventions.cedar import ( CedarAuthorization,)
@tooldef search(query: str) -> str: """Search for information.""" return f"Results for: {query}"
@tooldef delete_record(record_id: str) -> str: """Delete a record by ID.""" return f"Deleted {record_id}"
cedar = CedarAuthorization( policies=( 'permit(principal, action == Action::"search",' " resource);" ),)
agent = Agent( tools=[search, delete_record], interventions=[cedar],)
agent( "Search for quarterly reports then delete record 42")# search is permitted; delete_record is denied# (no matching permit)import { Agent, tool } from '@strands-agents/sdk'import { CedarAuthorization } from '@strands-agents/sdk/vended-interventions/cedar'import { z } from 'zod'
const searchTool = tool({ name: 'search', description: 'Search for information', inputSchema: z.object({ query: z.string() }), callback: (input) => `Results for: ${input.query}`,})
const deleteTool = tool({ name: 'delete_record', description: 'Delete a record by ID', inputSchema: z.object({ record_id: z.string() }), callback: (input) => `Deleted ${input.record_id}`,})
const cedar = new CedarAuthorization({ policies: ` permit(principal, action == Action::"search", resource); `,})
const agent = new Agent({ tools: [searchTool, deleteTool], interventions: [cedar],})
await agent.invoke('Search for quarterly reports then delete record 42')// If the agent calls search, it's permitted; a delete_record call is denied (no matching permit)Cedar uses default-deny semantics. Tools without a matching permit statement are automatically blocked.
Role-based access control
Section titled “Role-based access control”For multi-tenant agents where each request carries user identity, use principal_resolver principalResolver invocation_state invocationState context_enricher contextEnricher
from strands import Agent, toolfrom strands.vended_interventions.cedar import ( CedarAuthorization,)
@tooldef search(query: str) -> str: """Search for information.""" return f"Results for: {query}"
@tooldef delete_record(record_id: str) -> str: """Delete a record by ID.""" return f"Deleted {record_id}"
cedar = CedarAuthorization( policies=""" permit(principal, action, resource) when { context.session.role == "admin" };
permit( principal, action == Action::"search", resource ) when { context.session.role == "analyst" }; """, principal_resolver=lambda state: ( {"type": "User", "id": state["user_id"]} if state.get("user_id") else None ), context_enricher=lambda ctx: { "role": ctx["invocation_state"].get( "role", "none" ), },)
agent = Agent( tools=[search, delete_record], interventions=[cedar],)
# admin can use any toolagent( "Delete record 42", invocation_state={ "user_id": "alice", "role": "admin", },)
# analyst can only searchagent( "Delete record 42", invocation_state={ "user_id": "bob", "role": "analyst", },)# denied: no permit for delete_record with "analyst"import { Agent, tool } from '@strands-agents/sdk'import { CedarAuthorization } from '@strands-agents/sdk/vended-interventions/cedar'import { z } from 'zod'
const searchTool = tool({ name: 'search', description: 'Search for information', inputSchema: z.object({ query: z.string() }), callback: (input) => `Results for: ${input.query}`,})
const deleteTool = tool({ name: 'delete_record', description: 'Delete a record by ID', inputSchema: z.object({ record_id: z.string() }), callback: (input) => `Deleted ${input.record_id}`,})
const cedar = new CedarAuthorization({ policies: ` permit(principal, action, resource) when { context.session.role == "admin" };
permit(principal, action == Action::"search", resource) when { context.session.role == "analyst" }; `, principalResolver: (state) => { if (!state.user_id) return undefined return { type: 'User', id: String(state.user_id) } }, contextEnricher: ({ invocationState }) => ({ role: String(invocationState.role ?? 'none'), }),})
const agent = new Agent({ tools: [searchTool, deleteTool], interventions: [cedar],})
// admin can use any toolawait agent.invoke('Delete record 42', { invocationState: { user_id: 'alice', role: 'admin' },})
// analyst can only searchawait agent.invoke('Delete record 42', { invocationState: { user_id: 'bob', role: 'analyst' },})// denied: no permit matches for delete_record with role "analyst"When principal_resolver principalResolver None undefined
Rate limiting
Section titled “Rate limiting”Cedar policies can reference context.session.call_count, which tracks how many times each tool has been invoked successfully during the session:
from strands import Agent, toolfrom strands.vended_interventions.cedar import ( CedarAuthorization,)
@tooldef send_email(to: str, body: str) -> str: """Send an email.""" return f"Sent to {to}"
@tooldef search(query: str) -> str: """Search for information.""" return f"Results for: {query}"
cedar = CedarAuthorization( policies=""" permit( principal, action == Action::"send_email", resource ) when { context.session.call_count < 5 };
permit( principal, action == Action::"search", resource ); """,)
agent = Agent( tools=[send_email, search], interventions=[cedar],)
# send_email permitted for calls 1-4, denied on 5th# search is unlimitedimport { Agent, tool } from '@strands-agents/sdk'import { CedarAuthorization } from '@strands-agents/sdk/vended-interventions/cedar'import { z } from 'zod'
const sendEmailTool = tool({ name: 'send_email', description: 'Send an email', inputSchema: z.object({ to: z.string(), body: z.string() }), callback: (input) => `Sent to ${input.to}`,})
const searchTool = tool({ name: 'search', description: 'Search for information', inputSchema: z.object({ query: z.string() }), callback: (input) => `Results for: ${input.query}`,})
const cedar = new CedarAuthorization({ policies: ` permit(principal, action == Action::"send_email", resource) when { context.session.call_count < 5 };
permit(principal, action == Action::"search", resource); `,})
const agent = new Agent({ tools: [sendEmailTool, searchTool], interventions: [cedar],})
// send_email is permitted for the first 4 calls, then denied on the 5th// search is unlimitedCall counts persist with the agent’s state and survive session reloads. Only successful tool calls increment the counter.
Schema validation
Section titled “Schema validation”Pass your tool definitions to catch policy typos at construction time. The handler generates a Cedar schema from tool definitions and validates policies against it:
from strands.vended_interventions.cedar import ( CedarAuthorization, ToolDefinition,)
search_def: ToolDefinition = { "name": "search", "inputSchema": { "type": "object", "properties": { "query": {"type": "string"} }, },}
delete_def: ToolDefinition = { "name": "delete_record", "inputSchema": { "type": "object", "properties": { "record_id": {"type": "string"} }, },}
# Valid policies pass schema validationcedar = CedarAuthorization( policies=""" permit( principal, action == Action::"search", resource ); permit( principal, action == Action::"delete_record", resource ) when { context.session.role == "admin" }; """, tools=[search_def, delete_def], context_enricher=lambda ctx: { "role": ctx["invocation_state"].get( "role", "none" ), },)
# A typo in the action name raises at construction:# CedarAuthorization(# policies='permit(principal, action == Action::"deleet_record", resource);',# tools=[search_def, delete_def],# )# raises ValueError: Cedar policy validation failed:# unrecognized action "deleet_record"import { Agent, tool } from '@strands-agents/sdk'import { CedarAuthorization } from '@strands-agents/sdk/vended-interventions/cedar'import { z } from 'zod'
const searchTool = tool({ name: 'search', description: 'Search for information', inputSchema: z.object({ query: z.string() }), callback: (input) => `Results for: ${input.query}`,})
const deleteTool = tool({ name: 'delete_record', description: 'Delete a record by ID', inputSchema: z.object({ record_id: z.string() }), callback: (input) => `Deleted ${input.record_id}`,})
// Valid policies pass schema validationconst cedar = new CedarAuthorization({ policies: ` permit(principal, action == Action::"search", resource); permit(principal, action == Action::"delete_record", resource) when { context.session.role == "admin" }; `, tools: [searchTool, deleteTool], contextEnricher: ({ invocationState }) => ({ role: String(invocationState.role ?? 'none'), }),})
// A typo in the action name throws at construction:// new CedarAuthorization({// policies: 'permit(principal, action == Action::"deleet_record", resource);',// tools: [searchTool, deleteTool],// })// throws "Cedar policy validation failed: unrecognized action"Schema validation integrates with the cedar-for-agents ecosystem via @cedar-policy/mcp-schema-generator-wasm (TypeScript) and cedar-policy-mcp-schema-generator (Python).
Environment gating
Section titled “Environment gating”Block tools based on deployment context by forwarding environment metadata through context_enricher contextEnricher
from strands import Agent, toolfrom strands.vended_interventions.cedar import ( CedarAuthorization,)
@tooldef deploy(version: str) -> str: """Deploy the service.""" return f"Deployed {version}"
cedar = CedarAuthorization( policies=""" permit( principal, action == Action::"deploy", resource ) when { context.session has environment && context.session.environment != "production" }; """, context_enricher=lambda ctx: { "environment": ctx["invocation_state"].get( "environment", "unknown" ), },)
agent = Agent( tools=[deploy], interventions=[cedar],)
# works in stagingagent( "Deploy the service", invocation_state={"environment": "staging"},)
# denied in productionagent( "Deploy the service", invocation_state={"environment": "production"},)import { Agent, tool } from '@strands-agents/sdk'import { CedarAuthorization } from '@strands-agents/sdk/vended-interventions/cedar'import { z } from 'zod'
const deployTool = tool({ name: 'deploy', description: 'Deploy the service', inputSchema: z.object({ version: z.string() }), callback: (input) => `Deployed ${input.version}`,})
const cedar = new CedarAuthorization({ policies: ` permit(principal, action == Action::"deploy", resource) when { context.session has environment && context.session.environment != "production" }; `, contextEnricher: ({ invocationState }) => ({ environment: String(invocationState.environment ?? 'unknown'), }),})
const agent = new Agent({ tools: [deployTool], interventions: [cedar],})
// works in stagingawait agent.invoke('Deploy the service', { invocationState: { environment: 'staging' },})
// denied in productionawait agent.invoke('Deploy the service', { invocationState: { environment: 'production' },})File-based policies
Section titled “File-based policies”For production deployments, keep policies in .cedar files rather than inline strings:
from strands.vended_interventions.cedar import ( CedarAuthorization,)
cedar = CedarAuthorization( policies="./policies/agent.cedar", entities="./policies/entities.json",)import { CedarAuthorization } from '@strands-agents/sdk/vended-interventions/cedar'
const cedar = new CedarAuthorization({ policies: './policies/agent.cedar', entities: './policies/entities.json',})The handler reads and parses files at construction time. Invalid syntax throws immediately.
Hot reload
Section titled “Hot reload”Update policies without restarting your agent process:
from strands import Agent, toolfrom strands.vended_interventions.cedar import ( CedarAuthorization,)
@tooldef search(query: str) -> str: """Search for information.""" return f"Results for: {query}"
cedar = CedarAuthorization( policies="./policies/agent.cedar",)
agent = Agent( tools=[search], interventions=[cedar],)
# After editing agent.cedar on disk:cedar.reload()# Validates new policies before applying.# Raises ValueError if invalid.import { Agent, tool } from '@strands-agents/sdk'import { CedarAuthorization } from '@strands-agents/sdk/vended-interventions/cedar'import { z } from 'zod'
const searchTool = tool({ name: 'search', description: 'Search for information', inputSchema: z.object({ query: z.string() }), callback: (input) => `Results for: ${input.query}`,})
const cedar = new CedarAuthorization({ policies: './policies/agent.cedar',})
const agent = new Agent({ tools: [searchTool], interventions: [cedar],})
// After editing agent.cedar on disk:cedar.reload()// Validates new policies before applying. Throws if invalid.reload() reads fresh policy, entity, and schema files, validates them, and atomically swaps the active policy set. If validation fails, the previous policies remain in effect and the method throws.
Context structure
Section titled “Context structure”Every authorization request includes a structured context object:
{ "input": { "query": "quarterly report" }, "session": { "hour_utc": 14, "call_count": 3, "role": "admin" }}context.inputcontains the tool’s input arguments, accessible in policies viacontext.input.fieldNamecontext.session.hour_utcis auto-populated with the current UTC hour (0-23)context.session.call_counttracks per-tool invocation count- Additional
context.sessionfields come from yourcontext_enrichercontextEnricher
Error handling
Section titled “Error handling”Cedar engine failures (malformed policies, evaluation errors) are always fail-closed: the tool call is denied regardless of configuration.
The on_error onError principal_resolver principalResolver context_enricher contextEnricher
'throw'(default): re-raises the exception to the caller'deny': treats the callback failure as a denial (fail-closed)'proceed': allows the tool call despite the callback error (fail-open, use with caution)
Installation
Section titled “Installation”pip install strands-agents[cedar]Requires cedarpy and cedar-policy-mcp-schema-generator (installed automatically with the extra).
npm install @strands-agents/sdkCedar support is included in the core package via @cedar-policy/mcp-schema-generator-wasm.
Cedar policy syntax
Section titled “Cedar policy syntax”For the full policy language grammar, operators, and built-in functions, see the Cedar policy language reference.