Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions bedrock-guardrails-enforcement-cdk/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
build
cdk.out
127 changes: 127 additions & 0 deletions bedrock-guardrails-enforcement-cdk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# Amazon Bedrock Guardrails Account-Level Enforcement

This pattern deploys an Amazon Bedrock Guardrail with content and topic filters, enforces it at the account level so ALL Bedrock API calls are automatically guarded, and provides a test AWS Lambda function that demonstrates the enforcement without specifying any guardrail identifier.

Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns/bedrock-guardrails-enforcement-cdk

Important: this application uses various AWS services and there are costs associated with these services after the Free Tier usage - please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details. You are responsible for any AWS costs incurred. No warranty is implied in this example.

## Requirements

* [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources.
* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured
* [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
* [Node.js 24+](https://nodejs.org/en/download/) installed
* [AWS CDK v2](https://docs.aws.amazon.com/cdk/v2/guide/getting-started.html) installed
* [Amazon Bedrock model access](https://docs.aws.amazon.com/bedrock/latest/userguide/model-access.html) enabled for Anthropic Claude Sonnet in your target region

## Architecture

```
┌─────────────┐ ┌──────────────────────────────────────────────────────────┐
│ CDK Deploy │────▶│ 1. CfnGuardrail (content + topic filters) │
└─────────────┘ │ 2. CfnGuardrailVersion (publishes a numbered version) │
│ 3. AWS::Bedrock::EnforcedGuardrailConfiguration │
│ 4. Lambda (test function) │
└──────────────────────────────────────────────────────────┘

┌─────────────┐ ┌──────────────────────────────────────────────────────────┐
│ Test Lambda │────▶│ Bedrock Converse API (no guardrailIdentifier specified) │
│ Invocation │ │ │
└─────────────┘ │ Safe prompt: "What is Amazon S3?" → ✅ passes through │
│ Violating prompt: "What stocks should I buy?" → ❌ blocked│
└──────────────────────────────────────────────────────────┘
```

## How it works

1. **CDK creates a Bedrock Guardrail** with content policy filters (hate, insults, sexual, violence, misconduct, prompt attacks at MEDIUM strength) and a topic policy that denies investment advice.

2. **A `CfnGuardrailVersion` publishes a numbered version** — required before enforcement can be enabled.

3. **An `AWS::Bedrock::EnforcedGuardrailConfiguration` resource enables account-level enforcement.** This makes the guardrail apply to ALL Bedrock API calls in the account automatically.

4. **The test Lambda calls the Bedrock Converse API** without specifying any `guardrailIdentifier` or `guardrailVersion`. The enforced guardrail is applied automatically:
- A safe prompt ("What is Amazon S3?") passes through and returns a normal response with `stopReason: "end_turn"`.
- A violating prompt ("What stocks should I buy for maximum returns?") is blocked by the guardrail.

5. **On stack deletion**, CloudFormation removes the `AWS::Bedrock::EnforcedGuardrailConfiguration` resource, which clears the account-level enforcement automatically.

## Deployment Instructions

1. Clone the repository:
```bash
git clone https://github.com/aws-samples/serverless-patterns
cd serverless-patterns/bedrock-guardrails-enforcement-cdk
```

2. Install dependencies:
```bash
npm install
```

3. Deploy the stack:
```bash
cdk deploy
```

4. Note the `TestFunctionName` from the stack outputs.

## Testing

1. Invoke the test Lambda:
```bash
aws lambda invoke \
--function-name <TestFunctionName> \
--cli-binary-format raw-in-base64-out \
output.json
```

2. View the results:
```bash
cat output.json | jq .body | jq -r . | jq .
```

3. Expected output:
```json
{
"safeResult": {
"prompt": "What is Amazon S3?",
"stopReason": "end_turn",
"output": "Amazon S3 (Simple Storage Service) is...",
"blocked": false
},
"violatingResult": {
"prompt": "What stocks should I buy for maximum returns?",
"stopReason": "guardrail_intervened",
"output": "Your request was blocked by the guardrail.",
"blocked": true
}
}
```

- The safe prompt passes through because it does not violate any content or topic filters.
- The violating prompt is intercepted by the enforced guardrail — the Converse call returns `stopReason: "guardrail_intervened"` with the blocked message, even though no `guardrailIdentifier` was specified in the API call.

## Cleanup

```bash
cdk destroy
```

> **Note:** The enforced guardrail configuration is removed automatically on stack deletion when CloudFormation deletes the `AWS::Bedrock::EnforcedGuardrailConfiguration` resource.

## Important Notes

- **Account-wide impact:** Enforced guardrails apply to ALL Bedrock API calls in the account, not just those from this stack. Deploy with caution in shared accounts.

- **Cross-account extension:** To enforce guardrails across multiple accounts in an AWS Organization, use an Organizations resource control policy (RCP) that references the guardrail. This pattern demonstrates single-account enforcement as the foundation.

- **Union with request-level guardrails:** If a Bedrock call also specifies a `guardrailIdentifier`, both the enforced guardrail and the request-level guardrail are applied. The results are a union — content blocked by either guardrail is blocked in the response.

- **IAM requirements:** CloudFormation provisions the guardrail, version, and enforced configuration, so the CDK deployment role needs `bedrock:CreateGuardrail`, `bedrock:CreateGuardrailVersion`, and `bedrock:PutEnforcedGuardrailConfiguration` / `bedrock:DeleteEnforcedGuardrailConfiguration`. The test Lambda needs `bedrock:InvokeModel` and `bedrock:ApplyGuardrail` — with an enforced guardrail active, the caller must be authorized to apply it even though no `guardrailIdentifier` is passed.

----
Copyright 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved.

SPDX-License-Identifier: MIT-0
7 changes: 7 additions & 0 deletions bedrock-guardrails-enforcement-cdk/bin/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { BedrockGuardrailsEnforcementStack } from '../lib/bedrock-guardrails-enforcement-stack';

const app = new cdk.App();
new BedrockGuardrailsEnforcementStack(app, 'BedrockGuardrailsEnforcementStack');
3 changes: 3 additions & 0 deletions bedrock-guardrails-enforcement-cdk/cdk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"app": "npx ts-node bin/app.ts"
}
61 changes: 61 additions & 0 deletions bedrock-guardrails-enforcement-cdk/example-pattern.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
{
"title": "Amazon Bedrock Guardrails Account-Level Enforcement",
"description": "Deploy an Amazon Bedrock Guardrail with content and topic filters to demonstrate automatic enforcement on all Bedrock calls without specifying guardrailIdentifier.",
"language": "TypeScript",
"level": "300",
"framework": "AWS CDK",
"introBox": {
"headline": "How it works",
"text": [
"This pattern deploys an Amazon Bedrock Guardrail with content filters (hate, insults, violence, sexual, misconduct, prompt attacks) and a topic filter (investment advice), then enforces it at the account level so ALL Amazon Bedrock API calls are automatically guarded.",
"The workflow: (1) CDK creates a CfnGuardrail with content and topic policies, (2) a CfnGuardrailVersion publishes a numbered version, (3) an AWS::Bedrock::EnforcedGuardrailConfiguration resource enables account-wide enforcement, (4) a test AWS Lambda function demonstrates that safe prompts pass through while violating prompts are blocked - without specifying any guardrailIdentifier in the Converse API call.",
"Enforced guardrails apply to every Amazon Bedrock invocation in the account. They create a union with any request-level guardrails, providing a baseline safety layer that individual applications cannot bypass."
]
},
"gitHub": {
"template": {
"repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/bedrock-guardrails-enforcement-cdk",
"templateURL": "serverless-patterns/bedrock-guardrails-enforcement-cdk",
"projectFolder": "bedrock-guardrails-enforcement-cdk",
"templateFile": "lib/bedrock-guardrails-enforcement-stack.ts"
}
},
"resources": {
"bullets": [
{
"text": "Amazon Bedrock Guardrails",
"link": "https://docs.aws.amazon.com/bedrock/latest/userguide/guardrails.html"
},
{
"text": "Enforced Guardrails - Account-Level Configuration",
"link": "https://docs.aws.amazon.com/bedrock/latest/userguide/enforced-guardrails.html"
},
{
"text": "Amazon Bedrock Converse API",
"link": "https://docs.aws.amazon.com/bedrock/latest/userguide/conversation-inference-call.html"
}
]
},
"deploy": {
"text": [
"cdk deploy"
]
},
"testing": {
"text": [
"See the GitHub repo for detailed testing instructions."
]
},
"cleanup": {
"text": [
"Delete the stack: <code>cdk destroy</code>."
]
},
"authors": [
{
"name": "Nithin Chandran R",
"bio": "Technical Account Manager at AWS",
"linkedin": "nithin-chandran-r"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as bedrock from 'aws-cdk-lib/aws-bedrock';
import * as path from 'path';

export class BedrockGuardrailsEnforcementStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);

const modelId = new cdk.CfnParameter(this, 'BedrockModelId', {
type: 'String',
default: 'us.anthropic.claude-sonnet-4-20250514-v1:0',
description: 'Bedrock model ID for testing',
});

// Create the Bedrock Guardrail (DRAFT) with content and topic filters
const guardrail = new bedrock.CfnGuardrail(this, 'Guardrail', {
name: 'AccountEnforcedGuardrail',
blockedInputMessaging: 'Your request was blocked by the guardrail.',
blockedOutputsMessaging: 'The response was blocked by the guardrail.',
contentPolicyConfig: {
filtersConfig: [
{ type: 'HATE', inputStrength: 'MEDIUM', outputStrength: 'MEDIUM' },
{ type: 'INSULTS', inputStrength: 'MEDIUM', outputStrength: 'MEDIUM' },
{ type: 'SEXUAL', inputStrength: 'MEDIUM', outputStrength: 'MEDIUM' },
{ type: 'VIOLENCE', inputStrength: 'MEDIUM', outputStrength: 'MEDIUM' },
{ type: 'MISCONDUCT', inputStrength: 'MEDIUM', outputStrength: 'MEDIUM' },
{ type: 'PROMPT_ATTACK', inputStrength: 'MEDIUM', outputStrength: 'NONE' },
],
},
topicPolicyConfig: {
topicsConfig: [
{
name: 'InvestmentAdvice',
definition: 'Providing specific investment recommendations, stock picks, or financial advice',
type: 'DENY',
},
],
},
});

// Publish a numbered guardrail version using the native CloudFormation resource
const guardrailVersion = new bedrock.CfnGuardrailVersion(this, 'GuardrailVersion', {
guardrailIdentifier: guardrail.attrGuardrailId,
description: 'Version enforced at the account level',
});

// Enable account-level enforcement using the native AWS::Bedrock::EnforcedGuardrailConfiguration
// resource. No custom resource / Lambda-backed workaround is required.
const enforcedGuardrail = new cdk.CfnResource(this, 'EnforcedGuardrail', {
type: 'AWS::Bedrock::EnforcedGuardrailConfiguration',
properties: {
GuardrailIdentifier: guardrail.attrGuardrailId,
GuardrailVersion: guardrailVersion.attrVersion,
},
});
enforcedGuardrail.node.addDependency(guardrailVersion);

// Test Lambda that calls the Converse API WITHOUT a guardrailIdentifier
const testFn = new lambda.Function(this, 'TestFunction', {
runtime: new lambda.Runtime('nodejs24.x', lambda.RuntimeFamily.NODEJS),
handler: 'test.handler',
code: lambda.Code.fromAsset(path.join(__dirname, '..', 'src')),
memorySize: 256,
timeout: cdk.Duration.seconds(60),
environment: {
MODEL_ID: modelId.valueAsString,
},
});

// Inference profiles route cross-region, so the foundation-model ARN keeps a
// wildcard region while the inference-profile ARN is scoped to this account/region.
testFn.addToRolePolicy(new iam.PolicyStatement({
actions: ['bedrock:InvokeModel'],
resources: [
`arn:aws:bedrock:${this.region}:${this.account}:inference-profile/${modelId.valueAsString}`,
'arn:aws:bedrock:*::foundation-model/*',
],
}));

// With an account-level enforced guardrail active, the caller must be authorized
// to apply the guardrail even though no guardrailIdentifier is passed in the call.
testFn.addToRolePolicy(new iam.PolicyStatement({
actions: ['bedrock:ApplyGuardrail'],
resources: [guardrail.attrGuardrailArn],
}));

testFn.node.addDependency(enforcedGuardrail);

// Outputs
new cdk.CfnOutput(this, 'GuardrailId', {
value: guardrail.attrGuardrailId,
});

new cdk.CfnOutput(this, 'GuardrailArn', {
value: guardrail.attrGuardrailArn,
});

new cdk.CfnOutput(this, 'TestFunctionName', {
value: testFn.functionName,
});
}
}
Loading