Skip to content

(bedrockagentcore): Policy construct always emits Definition.Cedar.Statement, so guardrail and temporal policies cannot be declared with it, and there is no enforcementMode prop #39530

Open

Description

@hsiddhu2

Describe the bug

The Policy construct in aws-cdk-lib/aws-bedrockagentcore takes a statement: PolicyStatement. A PolicyStatement is built either with its constructor, whose effect can only be PERMIT or FORBID, or with fromCedar(). Either way, the construct emits the statement as Definition.Cedar.Statement on AWS::BedrockAgentCore::Policy.

The CloudFormation resource defines Definition as a union of Cedar.Statement and Policy.Statement. The AgentCore developer guide says to put a guardrail statement (when guardrails { ... }, BedrockGuardrails::* functions, the suppressOutput effect) in the policy field of the definition property. The API reference describes the policy member as the one that supports temporal conditions and guardrails. AWS's own sample, aws-samples/sample-native-guardrails-for-bedrock-agentcore, does this with the L1 resource and comments that only the policy member accepts a when guardrails block.

The L2 construct has no prop and no factory for the policy member. I used CfnPolicy directly. I did not try a raw override of Definition on the construct's default child. A second, smaller gap: the construct has no enforcementMode prop, although the resource has EnforcementMode (ACTIVE or LOG_ONLY) and the guide's recommended rollout is to create a policy in LOG_ONLY first.

Regression Issue

  • Select this option if this issue appears to be a regression.

Last Known Working CDK Library Version

No response

Expected Behavior

  1. A way to place a statement in the policy member. For example, a PolicyStatement.fromPolicy(statement) factory, or a definitionType prop on PolicyProps.
  2. An enforcementMode prop on PolicyProps, with an enum for ACTIVE and LOG_ONLY.
  3. At minimum, a note in the module README that guardrail and temporal statements and the enforcement mode need CfnPolicy today.

Current Behavior

Deploying a guardrail policy with the documented when guardrails { ... } form through Policy fails in CloudFormation with (CreatePolicy request id 522ee87b-48d1-4fce-ac25-a121b368967d, us-east-1):

When parsing the policy statement, the following errors occurred:
* unexpected token `guardrails`

Sending the same statement with a plain when { ... } block fails with (CreatePolicy request id 8d12cb06-1d35-44cb-abce-2192db48eae6, us-east-1):

When parsing the policy statement, the following errors occurred:
* invalid policy effect: suppressOutput
* `BedrockGuardrails::SensitiveInformation` is not a valid function

Both are the Cedar grammar rejecting the statement, which is expected because the construct placed it in Definition.Cedar.Statement.

Dropping to CfnPolicy with definition: { policy: { statement } } works and is what I deployed. The same statement through the control plane API with boto3 create_policy(definition={"policy": {"statement": ...}}) returns 202 and reaches ACTIVE (request ids f78a810b-eb35-4719-b0df-48bc4b44a734 and 95e3d2ea-f1d5-48a8-ad76-eee89481849e, us-east-1).

For the enforcement mode I use policy.node.default_child.add_property_override("EnforcementMode", "LOG_ONLY"), which works but bypasses the construct.

Reproduction Steps

Python, aws-cdk-lib 2.273.0.

from aws_cdk import Stack
from aws_cdk import aws_bedrockagentcore as agentcore
from constructs import Construct

GATEWAY_ARN = "arn:aws:bedrock-agentcore:us-east-1:111122223333:gateway/example-gw-abcdefghij"

STATEMENT = f'''suppressOutput(
  principal,
  action == AgentCore::Action::"ExampleTarget___get_record",
  resource == AgentCore::Gateway::"{GATEWAY_ARN}"
)
when guardrails {{
  BedrockGuardrails::SensitiveInformation(
    ["US_SOCIAL_SECURITY_NUMBER"],
    [context.output.text]
  ).count() > 0
}};'''


class ReproStack(Stack):
    def __init__(self, scope: Construct, id: str) -> None:
        super().__init__(scope, id)
        engine = agentcore.PolicyEngine(self, "Engine", policy_engine_name="repro_engine")
        agentcore.Policy(
            self,
            "GuardrailPolicy",
            policy_engine=engine,
            policy_name="repro_suppress_output",
            statement=agentcore.PolicyStatement.from_cedar(STATEMENT),
            validation_mode=agentcore.PolicyValidationMode.IGNORE_ALL_FINDINGS,
        )
  1. cdk synth. The template shows Definition: { Cedar: { Statement: ... } }. There is no way to produce Definition: { Policy: { Statement: ... } } from PolicyProps.
  2. cdk deploy. The stack fails with unexpected token `guardrails` .
  3. Replace when guardrails { with when { and deploy again. The stack fails with invalid policy effect: suppressOutput and `BedrockGuardrails::SensitiveInformation` is not a valid function.
  4. Replace the Policy construct with CfnPolicy and definition=CfnPolicy.PolicyDefinitionProperty(policy=CfnPolicy.PolicyStatementProperty(statement=STATEMENT)). The deploy succeeds and the policy reaches ACTIVE.

Possible Solution

export class PolicyStatement {
  /** Raw Cedar, placed in Definition.Cedar.Statement (existing). */
  static fromCedar(cedarStatement: string): PolicyStatement;
  /** A guardrail or temporal statement, placed in Definition.Policy.Statement. */
  static fromPolicy(policyStatement: string): PolicyStatement;
}

export interface PolicyProps {
  // existing props
  /** @default PolicyEnforcementMode.ACTIVE */
  readonly enforcementMode?: PolicyEnforcementMode;
}

export enum PolicyEnforcementMode { ACTIVE = 'ACTIVE', LOG_ONLY = 'LOG_ONLY' }

I am willing to open a PR for this if the approach is acceptable.

Additional Information/Context

AWS CDK Library version (aws-cdk-lib)

2.273.0

AWS CDK CLI version

2.1145.0

Node.js Version

v20.19.5

OS

macOS 26.5.2

Language

Python

Language Version

3.12

Other information

Region us-east-1. The CloudFormation rejections above were produced by this construct in my stack on Oct 22, four per form. The statements in those attempts carried comment headers, and the successful API calls through the policy member did not, so the two sets are not byte-identical. The claim in this issue rests on two things: the synthesized template shows Definition.Cedar.Statement as the only member the construct can emit, and the Cedar grammar rejects guardrail syntax, as the recorded errors show. The AWS sample linked above reaches the same conclusion and uses the L1 resource for that reason.

Activity

  1. added
    bugThis issue is a bug.
    needs-triageThis issue or PR still needs to be triaged.
    on Oct 28, 2026
  2. hsiddhu2 commented on Oct 28, 2026

    @hsiddhu2
    Author

    FYI - This is the aws-bedrockagentcore module, not aws-iam.

  3. pahud commented on Oct 28, 2026

    @pahud
    Contributor

    Hi @hsiddhu2,

    Issue at a Glance:

    ┌───────────────────────────┐     ┌───────────────────────────┐
    │ Policy + PolicyStatement  │ ──> │ L2 always emits           │
    │ .fromCedar(guardrail)     │     │ Definition.Cedar.Statement│
    └───────────────────────────┘     │ (no EnforcementMode prop) │
                                      └───────────────────────────┘
                                                    │
                                                    v
                                      ┌───────────────────────────┐
                                      │ CreatePolicy: unexpected  │
                                      │ token `guardrails`        │
                                      └───────────────────────────┘
    

    Thanks for the detailed report and the CfnPolicy workaround.

    I reproduced this with cdk synth on 2.273.0 (reported) and 2.275.0 (latest); both produce identical templates with the statement under Definition.Cedar.Statement, and enforcementMode fails to compile (TS2353). CfnPolicy or property overrides emit Definition.Policy.Statement with EnforcementMode as expected.

    It appears the L2 predates the Policy member and EnforcementMode (L1 update #38189, v2.261.0) and was graduated in #38687 without picking them up. The cedar member is hardcoded here:

    const cfnProps: CfnPolicyProps = {
    name: this.policyName,
    policyEngineId: this.policyEngine.policyEngineId,
    definition: {
    cedar: { statement: this.definition },
    },
    description: this.description,
    validationMode: this.validationMode?.value,
    };

    From my view, your proposal looks right: an enforcementMode prop plus a PolicyStatement.fromPolicy() factory, with today's output kept when neither is set. I don't see an open PR yet, and since you offered, we'd be happy to review one!

  4. added
    effort/mediumMedium work item – several days of effort
    and removed
    needs-triageThis issue or PR still needs to be triaged.
    @aws-cdk/aws-iamRelated to AWS Identity and Access Management
    on Oct 28, 2026
  5. hsiddhu2 commented on Oct 29, 2026

    @hsiddhu2
    Author

    Thanks, @pahud, for reproducing this in both versions and for the pointers to #38189 and #38687.

    I'll open a PR with the approach from the issue:

    • An enforcementMode prop on PolicyProps, with an enum for ACTIVE and LOG_ONLY.
    • A PolicyStatement.fromPolicy() factory that writes the statement to Definition.Policy.Statement.
    • When neither is set, the synthesized output stays exactly as it is today.

    It will include unit tests, an integ test, and a README update. Thanks again!

  6. hsiddhu2 commented on Oct 29, 2026

    @hsiddhu2
    Author

    Hi @pahud - I've opened #39548 with the approach above. It adds the enforcementMode prop and PolicyStatement.fromPolicy(), and the default output is unchanged. It includes unit tests, an integ test, and a README update. Please let me know if you have any questions. Thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions