Action and resource scoped policies
The policies in this section are matched on what is being called — which action, on which gateway, with which input values — rather than on who is calling. They do not govern which principals may take the action; any caller the gateway authenticates is in scope.
For rules based on the caller’s identity, see Principal-scoped policies. The Combined policies section at the end shows the two kinds working together.
Every example authorizes the gateway described in Resource configuration for examples. These policies use only constructs that are valid in both Dogwood and Cedar.
Requirements for the action and resource scope
Two requirements apply to every policy on this page, and a policy that breaks either one is rejected when you create it.
The action scope and the resource scope have to agree. Which resource form you write follows from whether you named specific actions:
| If the action scope is | The resource scope must be |
|---|---|
|
One or more specific actions — |
A specific gateway — |
|
Unconstrained — a bare |
A type test — |
The resource can never be left unconstrained. A bare resource is rejected with a wildcard resource was
detected, whichever action scope you used.
Naming a specific action therefore means knowing the gateway ARN, which you only have once the gateway exists. For details, see Resource specificity requirements.
Where wildcards work
* means different things in different parts of a policy, and it is not accepted everywhere.
Not in an action name. Cedar has no wildcard for action names, so AgentCore::Action::"InsuranceAPI___get*"
is not valid. List the actions with action in [ … ], or scope the policy to the whole target with
action in AgentCore::Action::"<TargetName>", which also picks up tools added to that target later.
For more information, see Multiple actions.
Not in a resource. A bare resource is rejected. Use a specific gateway ARN or the type test
resource is AgentCore::Gateway, as described in the previous section.
Yes in a string comparison. Inside a condition, like takes a pattern in which * matches any run
of characters — this is the one place a wildcard applies:
context.input.coverageType like "auto*"
Anchor the pattern where you can: auto* matches values beginning with auto, while
*auto* would also match semi-auto-salvage.
Selecting actions
Permit several actions at once
Intent: Allow reading contract details and claim status.
Grouping related read operations with action in [ … ] avoids one policy per tool. Each action is named
explicitly because action names take no wildcard — see
Where wildcards work.
We recommend scoping to the target when you want the whole tool set. Writing
action in AgentCore::Action::"<TargetName>" covers every tool the target exposes, including tools added
later, so the policy does not need editing each time the target gains a tool. The trade-off is that a
target-scoped policy cannot carry a condition on tool input, because the input differs per tool. For more
information, see Multiple actions.
permit( principal, action in [ AgentCore::Action::"InsuranceAPI___get_contract", AgentCore::Action::"InsuranceAPI___get_claim_status" ], resource == AgentCore::Gateway::"arn:aws:bedrock-agentcore:us-west-2:123456789012:gateway/insurance-gw-a1b2c3d4e5" );
Permit a runtime target invocation
Intent: Allow invoking the claims assistant.
An AgentCore Runtime target is authorized as a single action, named for the target and the method and path it
is invoked with. It is not one action per tool the agent might use internally. For a runtime target that path
is always /invocations. Whatever tools the agent calls behind that boundary are outside this policy’s reach,
so govern those on their own gateway.
permit( principal, action == AgentCore::Action::"ClaimsAgent___POST:/invocations", resource == AgentCore::Gateway::"arn:aws:bedrock-agentcore:us-west-2:123456789012:gateway/insurance-gw-a1b2c3d4e5" );
Permit inference requests on every path
Intent: Allow all three inference APIs the gateway exposes.
Each inference target is one action, and a gateway has one target per API shape, so a policy must name
every path you intend to allow. Naming only one leaves the others denied — or, for a forbid,
unscreened.
permit( principal, action in [ AgentCore::Action::"ClaimsChat___POST:/v1/chat/completions", AgentCore::Action::"ClaimsResponses___POST:/v1/responses", AgentCore::Action::"ClaimsMessages___POST:/v1/messages" ], resource == AgentCore::Gateway::"arn:aws:bedrock-agentcore:us-west-2:123456789012:gateway/insurance-gw-a1b2c3d4e5" );
Restrict which model may be invoked
Intent: Allow chat completions only against an approved model family.
model is a scalar string in every inference envelope, so an ordinary condition reads it directly. This works
the same way in Dogwood, because Dogwood accepts every Cedar condition.
permit( principal, action == AgentCore::Action::"ClaimsChat___POST:/v1/chat/completions", resource == AgentCore::Gateway::"arn:aws:bedrock-agentcore:us-west-2:123456789012:gateway/insurance-gw-a1b2c3d4e5" ) when { context.input has model && context.input.model like "openai.gpt-oss-*" };
The model value carries both the provider and the model, in the form <provider>.<model>. Match it exactly
to pin one model, as in "openai.gpt-oss-120b-1:0". Anchor a like pattern on the provider prefix to allow
that provider’s models only, as in like "openai.*". Repeat the policy for ClaimsResponses and
ClaimsMessages with their own approved values.
Important
A Cedar condition cannot read the conversation itself — use a guardrail for that.
Conditions on an inference target reach only the scalar members of the request body, such as model or
max_tokens. They cannot reach the message text: context.input.messages is a Set<record>, which no Cedar
operator accepts, and Dogwood has no array-projection syntax for the strings inside it.
To act on what was actually said, write a guardrail whose data path is a JSONPath into the message array. For a policy you can copy for each inference API, see Reaching text inside arrays with a JSONPath.
Conditions on tool input
context.input holds the parameters of the call being authorized. For an MCP tool it holds the tool
arguments; for a runtime or inference target it holds the request body. The operators available on a
parameter follow from its type — see Parameter types in conditions.
String equality
Intent: Allow filing a claim only for the claim types the business supports.
permit( principal, action == AgentCore::Action::"InsuranceAPI___file_claim", resource == AgentCore::Gateway::"arn:aws:bedrock-agentcore:us-west-2:123456789012:gateway/insurance-gw-a1b2c3d4e5" ) when { (context.input.claimType == "health" || context.input.claimType == "property" || context.input.claimType == "auto") };
Set membership
Intent: Express the same rule against a list of accepted values.
A set literal with .contains() reads better than a chain of || comparisons as the list grows, and the
two forms are equivalent. Note the direction: the set is the receiver and the value is the argument.
permit( principal, action == AgentCore::Action::"InsuranceAPI___file_claim", resource == AgentCore::Gateway::"arn:aws:bedrock-agentcore:us-west-2:123456789012:gateway/insurance-gw-a1b2c3d4e5" ) when { ["health", "property", "auto"].contains(context.input.claimType) };
Pattern matching
Intent: Allow premium calculation for any automotive coverage type.
In a like pattern * matches any run of characters, so auto, auto-liability, and
auto-collision all match. This is the only place in a policy where a wildcard applies — not in action names
or resources. For more information, see
Where wildcards work.
permit( principal, action == AgentCore::Action::"InsuranceAPI___calculate_premium", resource == AgentCore::Gateway::"arn:aws:bedrock-agentcore:us-west-2:123456789012:gateway/insurance-gw-a1b2c3d4e5" ) when { context.input.coverageType like "auto*" };
Integer comparison
Intent: Allow filing a claim only against a policy term of at least twelve months.
policyTermMonths is an integer in the tool schema, so it is a Cedar Long and the ordered operators
<, ⇐, >, and >= apply directly.
permit( principal, action == AgentCore::Action::"InsuranceAPI___file_claim", resource == AgentCore::Gateway::"arn:aws:bedrock-agentcore:us-west-2:123456789012:gateway/insurance-gw-a1b2c3d4e5" ) when { context.input.policyTermMonths >= 12 };
Decimal comparison
Intent: Allow a claim to be filed only for amounts below 1,000.
permit( principal, action == AgentCore::Action::"InsuranceAPI___file_claim", resource == AgentCore::Gateway::"arn:aws:bedrock-agentcore:us-west-2:123456789012:gateway/insurance-gw-a1b2c3d4e5" ) when { context.input.amount.lessThan(decimal("1000.00")) };
Important
Before writing a comparison on a numeric parameter, confirm which operators its type supports. amount is a
number in the tool schema, so it is a Cedar decimal: compare it with lessThan, lessThanOrEqual,
greaterThan, or greaterThanOrEqual against a quoted decimal("…") literal. An integer
parameter is a Cedar Long and uses the ordinary < and > operators instead.
For more information about the operators each numeric type accepts, see
Numeric parameters, which also covers the extra
restriction inside a temporal { } block, and how the type error is reported.
Boolean condition
Intent: Allow an approval to be recorded only when it is affirmative.
A boolean parameter is used directly as a condition. To
require the opposite, write !context.input.approved. Ordered operators do not apply — context.input.approved > true
fails creation with expected datetime, or duration, or Long.
permit( principal, action == AgentCore::Action::"InsuranceAPI___approve_claim", resource == AgentCore::Gateway::"arn:aws:bedrock-agentcore:us-west-2:123456789012:gateway/insurance-gw-a1b2c3d4e5" ) when { context.input.approved };
Nested object members
Intent: Allow premium calculation only for policies with no prior claims in a supported region.
An object parameter becomes a Cedar record, a set of named members you reach with a dot. Test each level
with has
before reading it: because riskFactors is optional, omitting either guard fails policy creation with
unable to guarantee safety of access to optional attribute. Records nest to a maximum depth of 64 levels; a
deeper context.input is rejected at runtime with Cannot determine input entity type.
permit( principal, action == AgentCore::Action::"InsuranceAPI___calculate_premium", resource == AgentCore::Gateway::"arn:aws:bedrock-agentcore:us-west-2:123456789012:gateway/insurance-gw-a1b2c3d4e5" ) when { context.input has riskFactors && context.input.riskFactors has priorClaims && context.input.riskFactors.priorClaims == 0 && context.input.riskFactors has region && ["us-west", "us-east"].contains(context.input.riskFactors.region) };
Require an optional parameter
Intent: Require a description on every filed claim, even though the tool schema makes it optional.
An optional parameter is one the tool schema does not list as required, so a caller may omit it. On the
reference gateway’s MCP tools, description on file_claim and riskFactors on calculate_premium are the only
optional parameters; the runtime target also takes an optional sessionAttributes.
There are two ways to write this, and they behave differently. We recommend the forbid form shown second,
because it holds no matter which other policies apply. The permit form comes first because it is the one
readers reach for, and it does not have that property. A permit grants access when the field is present:
permit( principal, action == AgentCore::Action::"InsuranceAPI___file_claim", resource == AgentCore::Gateway::"arn:aws:bedrock-agentcore:us-west-2:123456789012:gateway/insurance-gw-a1b2c3d4e5" ) when { context.input has description };
A forbid makes the field mandatory no matter which other policies apply:
forbid( principal, action == AgentCore::Action::"InsuranceAPI___file_claim", resource == AgentCore::Gateway::"arn:aws:bedrock-agentcore:us-west-2:123456789012:gateway/insurance-gw-a1b2c3d4e5" ) unless { context.input has description };
Prefer the forbid form for a business rule you want to hold unconditionally, because another permit
cannot override it.
Important
has is mandatory for an optional parameter — not merely defensive. Reading an optional attribute without
guarding it fails policy creation:
unable to guarantee safety of access to optional attribute `input.riskFactors`
The guard is required rather than advisory — omitting it fails policy creation. A required parameter needs
no guard, so has is only needed where the tool schema marks the field optional.
Operational patterns
These policies are tools for running a gateway rather than expressions of a business rule. Because they are broad, add them deliberately and remove them when the situation that called for them has passed.
Stop all tool calls
Intent: Halt every action on the gateway during an incident.
This denies every request to every gateway attached to the policy engine, overriding all permit
policies. Adding it takes effect without changing any other policy, and deleting it restores normal
operation.
forbid( principal, action, resource is AgentCore::Gateway );
Warning
Verify the engine’s enforcement mode before you rely on this policy. If the gateway’s
policyEngineConfiguration.mode is LOG_ONLY, the decision is recorded but not enforced and every tool
call still succeeds. For more information, see Policy enforcement modes.
Disable one tool
Intent: Deny access to a single tool without affecting the rest of the gateway.
The tool disappears from tools/list for every caller while this policy is in place, so agents stop
attempting it rather than calling it and being denied.
forbid( principal, action == AgentCore::Action::"InsuranceAPI___disburse_payment", resource == AgentCore::Gateway::"arn:aws:bedrock-agentcore:us-west-2:123456789012:gateway/insurance-gw-a1b2c3d4e5" );
Combined policies
The two preceding sections separate policies by what they match on. A rule often constrains both. These examples combine a principal condition with an action and input condition in one policy.
Constrain a job title and its input together
Intent: Allow coverage updates only for supported coverage types, and only when a new limit is supplied and stays within the authority the caller’s job title carries.
One policy can constrain the principal and the input together, and doing so in a single permit is what
makes the conditions conjunctive. Where a parameter is optional, put its has test before the read it protects.
permit( principal is AgentCore::OAuthUser, action == AgentCore::Action::"InsuranceAPI___update_coverage", resource == AgentCore::Gateway::"arn:aws:bedrock-agentcore:us-west-2:123456789012:gateway/insurance-gw-a1b2c3d4e5" ) when { principal.hasTag("role") && principal.getTag("role") == "senior-adjuster" && ["liability", "collision"].contains(context.input.coverageType) && context.input.newLimit.lessThanOrEqual(decimal("50000.00")) };
Give two roles different limits on one tool
Intent: Let adjusters disburse small amounts and managers disburse large ones.
Tiered authority takes one permit per tier, each pairing the job title with its own ceiling. Because the two
policies are alternatives, a manager is not restricted by the adjuster policy. amountCents is an
integer, so it uses ordinary ordered operators rather than decimal methods.
// Adjusters: up to 1,000.00 permit( principal is AgentCore::OAuthUser, action == AgentCore::Action::"InsuranceAPI___disburse_payment", resource == AgentCore::Gateway::"arn:aws:bedrock-agentcore:us-west-2:123456789012:gateway/insurance-gw-a1b2c3d4e5" ) when { principal.hasTag("role") && principal.getTag("role") == "adjuster" && context.input.amountCents <= 100000 }; // Managers: up to 25,000.00 permit( principal is AgentCore::OAuthUser, action == AgentCore::Action::"InsuranceAPI___disburse_payment", resource == AgentCore::Gateway::"arn:aws:bedrock-agentcore:us-west-2:123456789012:gateway/insurance-gw-a1b2c3d4e5" ) when { principal.hasTag("role") && principal.getTag("role") == "manager" && context.input.amountCents <= 2500000 };
How these policies combine
Policies on one engine are evaluated together, and the result is not the sum of what each policy allows. Three rules decide every request:
- Deny by default
-
A request is allowed only if some
permitmatches it. An engine with no policies denies everything. - Forbid wins
-
If any
forbidmatches, the request is denied, even when apermitmatches it too. This is what makes Stop all tool calls reliable, and why aforbid … unlessrestriction cannot be overridden by adding apermit. - Permits do not intersect
-
Two
permitpolicies for the same action are alternatives, not requirements. If onepermitallowsupdate_coveragefor managers and another allows it for liability changes, then a manager changing anything is allowed and anyone changing liability is allowed. To require both conditions, put them in onepermitas Constrain a job title and its input together does, or express the restriction as aforbid … unless.
The last rule is the one that most often surprises people. Consider these three policies applied to
update_coverage:
-
Permit a specific user permits
clare. -
Forbid everyone except a set of roles denies anyone who is not a senior adjuster or manager.
-
Constrain a job title and its input together permits senior adjusters changing liability or collision within their limit.
A request from clare, who is neither a senior adjuster nor a manager, is denied: the first policy permits
it but the
second forbids it. A request from a senior adjuster raising a liability limit to 40,000 is allowed by the
third policy, and the second does not forbid it. A senior adjuster changing a comprehensive coverage type
is denied, because no permit covers that coverage type.
To check your reasoning against the deployed engine before you enforce it, run the request through
Test a policy in LOG_ONLY mode, or attach the engine in LOG_ONLY mode and read the decision
from the policy log.