Skip to content

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.

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 conceptMaps toExample
PrincipalUser identityUser::"alice@acme.com"
ActionTool nameAction::"search"
ResourceStatic (unconstrained)Resource::"agent"
Context.inputTool arguments{ query: "quarterly report" }
Context.sessionInvocation 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.

Permit specific tools and deny everything else:

from strands import Agent, tool
from strands.vended_interventions.cedar import (
CedarAuthorization,
)
@tool
def search(query: str) -> str:
"""Search for information."""
return f"Results for: {query}"
@tool
def 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)

Cedar uses default-deny semantics. Tools without a matching permit statement are automatically blocked.

For multi-tenant agents where each request carries user identity, use principal_resolver principalResolver to extract the principal from invocation_state invocationState and context_enricher contextEnricher to forward role information into Cedar context:

from strands import Agent, tool
from strands.vended_interventions.cedar import (
CedarAuthorization,
)
@tool
def search(query: str) -> str:
"""Search for information."""
return f"Results for: {query}"
@tool
def 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 tool
agent(
"Delete record 42",
invocation_state={
"user_id": "alice",
"role": "admin",
},
)
# analyst can only search
agent(
"Delete record 42",
invocation_state={
"user_id": "bob",
"role": "analyst",
},
)
# denied: no permit for delete_record with "analyst"

When principal_resolver principalResolver returns None undefined (no identity found), the handler denies all tool calls for that request.

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, tool
from strands.vended_interventions.cedar import (
CedarAuthorization,
)
@tool
def send_email(to: str, body: str) -> str:
"""Send an email."""
return f"Sent to {to}"
@tool
def 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 unlimited

Call counts persist with the agent’s state and survive session reloads. Only successful tool calls increment the counter.

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 validation
cedar = 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"

Schema validation integrates with the cedar-for-agents ecosystem via @cedar-policy/mcp-schema-generator-wasm (TypeScript) and cedar-policy-mcp-schema-generator (Python).

Block tools based on deployment context by forwarding environment metadata through context_enricher contextEnricher :

from strands import Agent, tool
from strands.vended_interventions.cedar import (
CedarAuthorization,
)
@tool
def 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 staging
agent(
"Deploy the service",
invocation_state={"environment": "staging"},
)
# denied in production
agent(
"Deploy the service",
invocation_state={"environment": "production"},
)

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",
)

The handler reads and parses files at construction time. Invalid syntax throws immediately.

Update policies without restarting your agent process:

from strands import Agent, tool
from strands.vended_interventions.cedar import (
CedarAuthorization,
)
@tool
def 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.

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.

Every authorization request includes a structured context object:

{
"input": { "query": "quarterly report" },
"session": {
"hour_utc": 14,
"call_count": 3,
"role": "admin"
}
}
  • context.input contains the tool’s input arguments, accessible in policies via context.input.fieldName
  • context.session.hour_utc is auto-populated with the current UTC hour (0-23)
  • context.session.call_count tracks per-tool invocation count
  • Additional context.session fields come from your context_enricher contextEnricher

Cedar engine failures (malformed policies, evaluation errors) are always fail-closed: the tool call is denied regardless of configuration.

The on_error onError option controls what happens when your user-supplied callbacks ( principal_resolver principalResolver or context_enricher contextEnricher ) raise an exception:

  • '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)
Terminal window
pip install strands-agents[cedar]

Requires cedarpy and cedar-policy-mcp-schema-generator (installed automatically with the extra).

For the full policy language grammar, operators, and built-in functions, see the Cedar policy language reference.