From b2c6d43ad1f6cc7fc5ddd4ae3d4a94c4873904f1 Mon Sep 17 00:00:00 2001 From: Nithin Chandran Rajashankar Date: Mon, 20 Apr 2026 11:32:07 +0000 Subject: [PATCH 1/2] feat(bedrock-guardrails-cross-account-cdk): Add Bedrock Guardrails account-level enforcement pattern Creates a Bedrock Guardrail with content and topic filters, versions it, and enables account-level enforcement via AwsCustomResource. Test Lambda demonstrates automatic guardrail enforcement on all Bedrock calls without specifying guardrailIdentifier. Key features: - Account-level guardrail enforcement via PutEnforcedGuardrailConfiguration - AwsCustomResource for SDK commands not in Lambda runtime - Content filters (HATE, INSULTS, SEXUAL, VIOLENCE, MISCONDUCT, PROMPT_ATTACK) - Denied topic filter (investment advice) - Test Lambda showing safe vs blocked responses - Automatic cleanup on stack deletion --- .../.gitignore | 3 + .../README.md | 130 +++ .../bin/app.ts | 7 + bedrock-guardrails-cross-account-cdk/cdk.json | 3 + .../example-pattern.json | 61 + .../bedrock-guardrails-cross-account-stack.ts | 134 +++ .../package-lock.json | 1012 +++++++++++++++++ .../package.json | 20 + .../src/enforce.mjs | 79 ++ .../src/test.mjs | 36 + .../src/version.mjs | 21 + .../tsconfig.json | 20 + 12 files changed, 1526 insertions(+) create mode 100644 bedrock-guardrails-cross-account-cdk/.gitignore create mode 100644 bedrock-guardrails-cross-account-cdk/README.md create mode 100644 bedrock-guardrails-cross-account-cdk/bin/app.ts create mode 100644 bedrock-guardrails-cross-account-cdk/cdk.json create mode 100644 bedrock-guardrails-cross-account-cdk/example-pattern.json create mode 100644 bedrock-guardrails-cross-account-cdk/lib/bedrock-guardrails-cross-account-stack.ts create mode 100644 bedrock-guardrails-cross-account-cdk/package-lock.json create mode 100644 bedrock-guardrails-cross-account-cdk/package.json create mode 100644 bedrock-guardrails-cross-account-cdk/src/enforce.mjs create mode 100644 bedrock-guardrails-cross-account-cdk/src/test.mjs create mode 100644 bedrock-guardrails-cross-account-cdk/src/version.mjs create mode 100644 bedrock-guardrails-cross-account-cdk/tsconfig.json diff --git a/bedrock-guardrails-cross-account-cdk/.gitignore b/bedrock-guardrails-cross-account-cdk/.gitignore new file mode 100644 index 000000000..52f5a0116 --- /dev/null +++ b/bedrock-guardrails-cross-account-cdk/.gitignore @@ -0,0 +1,3 @@ +node_modules +build +cdk.out diff --git a/bedrock-guardrails-cross-account-cdk/README.md b/bedrock-guardrails-cross-account-cdk/README.md new file mode 100644 index 000000000..6d4b242f8 --- /dev/null +++ b/bedrock-guardrails-cross-account-cdk/README.md @@ -0,0 +1,130 @@ +# 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 Lambda that demonstrates the enforcement without specifying any guardrailIdentifier. + +Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns/bedrock-guardrails-cross-account-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 18+](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. AwsCustomResource → CreateGuardrailVersion │ + │ 3. AwsCustomResource → PutEnforcedGuardrailConfiguration │ + │ 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. **An AwsCustomResource creates a guardrail version** — required before enforcement can be enabled. + +3. **A second AwsCustomResource calls `PutEnforcedGuardrailConfiguration`** to enable 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**, the `onDelete` handler calls `DeleteEnforcedGuardrailConfiguration` to remove the enforcement. + +## Deployment Instructions + +1. Clone the repository: + ```bash + git clone https://github.com/aws-samples/serverless-patterns + cd serverless-patterns/bedrock-guardrails-cross-account-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 \ + --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?", + "error": "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 blocked because it triggers the "InvestmentAdvice" topic denial — even though no `guardrailIdentifier` was specified in the API call. + +## Cleanup + +```bash +cdk destroy +``` + +> **Note:** The enforced guardrail configuration is automatically removed on stack deletion via the `onDelete` handler in the AwsCustomResource. + +## 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:** The deploying role needs `bedrock:PutEnforcedGuardrailConfiguration` and `bedrock:DeleteEnforcedGuardrailConfiguration` permissions. The test Lambda needs `bedrock:InvokeModel` and `bedrock:ApplyGuardrail`. + +- **SDK availability:** The `PutEnforcedGuardrailConfigurationCommand` is not available in the Lambda runtime SDK at the time of writing. This pattern uses AwsCustomResource (which bundles the latest SDK) to work around this limitation. + +- **inputTags deprecation:** The `inputTags` parameter in the enforcement configuration is being deprecated in favor of `selectiveContentGuarding`. This pattern uses `IGNORE` for inputTags as a placeholder. + +---- +Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +SPDX-License-Identifier: MIT-0 diff --git a/bedrock-guardrails-cross-account-cdk/bin/app.ts b/bedrock-guardrails-cross-account-cdk/bin/app.ts new file mode 100644 index 000000000..8d42e13b6 --- /dev/null +++ b/bedrock-guardrails-cross-account-cdk/bin/app.ts @@ -0,0 +1,7 @@ +#!/usr/bin/env node +import 'source-map-support/register'; +import * as cdk from 'aws-cdk-lib'; +import { BedrockGuardrailsCrossAccountStack } from '../lib/bedrock-guardrails-cross-account-stack'; + +const app = new cdk.App(); +new BedrockGuardrailsCrossAccountStack(app, 'BedrockGuardrailsCrossAccountStack'); diff --git a/bedrock-guardrails-cross-account-cdk/cdk.json b/bedrock-guardrails-cross-account-cdk/cdk.json new file mode 100644 index 000000000..27fe6d2ec --- /dev/null +++ b/bedrock-guardrails-cross-account-cdk/cdk.json @@ -0,0 +1,3 @@ +{ + "app": "npx ts-node bin/app.ts" +} diff --git a/bedrock-guardrails-cross-account-cdk/example-pattern.json b/bedrock-guardrails-cross-account-cdk/example-pattern.json new file mode 100644 index 000000000..aaae0ff41 --- /dev/null +++ b/bedrock-guardrails-cross-account-cdk/example-pattern.json @@ -0,0 +1,61 @@ +{ + "title": "Bedrock Guardrails Account-Level Enforcement", + "description": "Deploy an Amazon Bedrock Guardrail with content and topic filters, enforce it at the account level using AwsCustomResource, and demonstrate automatic enforcement on all Bedrock calls without specifying guardrailIdentifier.", + "language": "TypeScript", + "level": "400", + "framework": "AWS CDK", + "introBox": { + "headline": "How it works", + "text": [ + "This pattern deploys a 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 Bedrock API calls are automatically guarded.", + "The workflow: (1) CDK creates a CfnGuardrail with content and topic policies, (2) an AwsCustomResource creates a guardrail version, (3) a second AwsCustomResource calls PutEnforcedGuardrailConfiguration to enable account-wide enforcement, (4) a test Lambda 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 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-cross-account-cdk", + "templateURL": "serverless-patterns/bedrock-guardrails-cross-account-cdk", + "projectFolder": "bedrock-guardrails-cross-account-cdk", + "templateFile": "lib/bedrock-guardrails-cross-account-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: cdk destroy." + ] + }, + "authors": [ + { + "name": "Nithin Chandran R", + "bio": "Technical Account Manager at AWS", + "linkedin": "nithin-chandran-r" + } + ] +} diff --git a/bedrock-guardrails-cross-account-cdk/lib/bedrock-guardrails-cross-account-stack.ts b/bedrock-guardrails-cross-account-cdk/lib/bedrock-guardrails-cross-account-stack.ts new file mode 100644 index 000000000..b1ef62b89 --- /dev/null +++ b/bedrock-guardrails-cross-account-cdk/lib/bedrock-guardrails-cross-account-stack.ts @@ -0,0 +1,134 @@ +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 cr from 'aws-cdk-lib/custom-resources'; +import * as path from 'path'; + +export class BedrockGuardrailsCrossAccountStack 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 Bedrock Guardrail + const guardrail = new bedrock.CfnGuardrail(this, 'Guardrail', { + name: 'CrossAccountEnforcedGuardrail', + 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', + }, + ], + }, + }); + + // Create guardrail version via AwsCustomResource + const versionCr = new cr.AwsCustomResource(this, 'GuardrailVersion', { + onCreate: { + service: 'Bedrock', + action: 'createGuardrailVersion', + parameters: { + guardrailIdentifier: guardrail.attrGuardrailId, + }, + physicalResourceId: cr.PhysicalResourceId.fromResponse('version'), + }, + policy: cr.AwsCustomResourcePolicy.fromStatements([ + new iam.PolicyStatement({ + actions: ['bedrock:CreateGuardrailVersion'], + resources: [guardrail.attrGuardrailArn], + }), + ]), + }); + + // Enable account-level enforcement via AwsCustomResource + const enforceCr = new cr.AwsCustomResource(this, 'GuardrailEnforcement', { + onCreate: { + service: 'Bedrock', + action: 'putEnforcedGuardrailConfiguration', + parameters: { + guardrailInferenceConfig: { + guardrailIdentifier: guardrail.attrGuardrailId, + guardrailVersion: versionCr.getResponseField('version'), + inputTags: 'IGNORE', + }, + }, + physicalResourceId: cr.PhysicalResourceId.of('enforced-guardrail'), + }, + onDelete: { + service: 'Bedrock', + action: 'deleteEnforcedGuardrailConfiguration', + parameters: { + configId: 'default', + }, + }, + policy: cr.AwsCustomResourcePolicy.fromStatements([ + new iam.PolicyStatement({ + actions: [ + 'bedrock:PutEnforcedGuardrailConfiguration', + 'bedrock:DeleteEnforcedGuardrailConfiguration', + ], + resources: ['*'], + }), + ]), + }); + + enforceCr.node.addDependency(versionCr); + + // Test Lambda + const testFn = new lambda.Function(this, 'TestFunction', { + runtime: lambda.Runtime.NODEJS_20_X, + handler: 'test.handler', + code: lambda.Code.fromAsset(path.join(__dirname, '..', 'src')), + memorySize: 256, + timeout: cdk.Duration.seconds(60), + environment: { + MODEL_ID: modelId.valueAsString, + }, + }); + + testFn.addToRolePolicy(new iam.PolicyStatement({ + actions: ['bedrock:InvokeModel'], + resources: ['*'], + })); + + testFn.addToRolePolicy(new iam.PolicyStatement({ + actions: ['bedrock:ApplyGuardrail'], + resources: [guardrail.attrGuardrailArn], + })); + + testFn.node.addDependency(enforceCr); + + // 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, + }); + } +} diff --git a/bedrock-guardrails-cross-account-cdk/package-lock.json b/bedrock-guardrails-cross-account-cdk/package-lock.json new file mode 100644 index 000000000..17336b887 --- /dev/null +++ b/bedrock-guardrails-cross-account-cdk/package-lock.json @@ -0,0 +1,1012 @@ +{ + "name": "bedrock-guardrails-cross-account-cdk", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "bedrock-guardrails-cross-account-cdk", + "version": "1.0.0", + "dependencies": { + "aws-cdk-lib": "2.180.0", + "constructs": "10.4.2" + }, + "bin": { + "app": "bin/app.ts" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "ts-node": "^10.9.0", + "typescript": "~5.4.0" + } + }, + "node_modules/@aws-cdk/asset-awscli-v1": { + "version": "2.2.275", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.275.tgz", + "integrity": "sha512-dyU4m8yH9vqVr+hm/MCiC5q3+nH6IX1wxABsAQX0qeyExmn+XNULMVpwa9HCLbYjeOgDSwTKJQ+CGDVY6Qu0OA==" + }, + "node_modules/@aws-cdk/asset-node-proxy-agent-v6": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.1.1.tgz", + "integrity": "sha512-We4bmHaowOPHr+IQR4/FyTGjRfjgBj4ICMjtqmJeBDWad3Q/6St12NT07leNtyuukv2qMhtSZJQorD8KpKTwRA==" + }, + "node_modules/@aws-cdk/cloud-assembly-schema": { + "version": "39.2.20", + "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-39.2.20.tgz", + "integrity": "sha512-RI7S8jphGA8mak154ElnEJQPNTTV4PZmA7jgqnBBHQGyOPJIXxtACubNQ5m4YgjpkK3UJHsWT+/cOAfM/Au/Wg==", + "bundleDependencies": [ + "jsonschema", + "semver" + ], + "dependencies": { + "jsonschema": "~1.4.1", + "semver": "^7.7.1" + } + }, + "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/jsonschema": { + "version": "1.4.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/semver": { + "version": "7.7.1", + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.19.39", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.39.tgz", + "integrity": "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==", + "dev": true, + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "dev": true, + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/aws-cdk-lib": { + "version": "2.180.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.180.0.tgz", + "integrity": "sha512-ncYx3MGcLL397WAg6LOHV8G/5d0FkdoskiUscqFawLWioK75f0M6AIuif9kxrxLBvbMOncOfqhV8wIsCM1fquA==", + "bundleDependencies": [ + "@balena/dockerignore", + "case", + "fs-extra", + "ignore", + "jsonschema", + "minimatch", + "punycode", + "semver", + "table", + "yaml", + "mime-types" + ], + "dependencies": { + "@aws-cdk/asset-awscli-v1": "^2.2.208", + "@aws-cdk/asset-node-proxy-agent-v6": "^2.1.0", + "@aws-cdk/cloud-assembly-schema": "^39.2.0", + "@balena/dockerignore": "^1.0.2", + "case": "1.6.3", + "fs-extra": "^11.2.0", + "ignore": "^5.3.2", + "jsonschema": "^1.4.1", + "mime-types": "^2.1.35", + "minimatch": "^3.1.2", + "punycode": "^2.3.1", + "semver": "^7.6.3", + "table": "^6.8.2", + "yaml": "1.10.2" + }, + "engines": { + "node": ">= 14.15.0" + }, + "peerDependencies": { + "constructs": "^10.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/@balena/dockerignore": { + "version": "1.0.2", + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/aws-cdk-lib/node_modules/ajv": { + "version": "8.17.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/aws-cdk-lib/node_modules/ansi-regex": { + "version": "5.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/ansi-styles": { + "version": "4.3.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/aws-cdk-lib/node_modules/astral-regex": { + "version": "2.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/balanced-match": { + "version": "1.0.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/brace-expansion": { + "version": "1.1.11", + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/aws-cdk-lib/node_modules/case": { + "version": "1.6.3", + "inBundle": true, + "license": "(MIT OR GPL-3.0-or-later)", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/color-convert": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/color-name": { + "version": "1.1.4", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/concat-map": { + "version": "0.0.1", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/emoji-regex": { + "version": "8.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/fast-deep-equal": { + "version": "3.1.3", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/fast-uri": { + "version": "3.0.6", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "inBundle": true, + "license": "BSD-3-Clause" + }, + "node_modules/aws-cdk-lib/node_modules/fs-extra": { + "version": "11.3.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/aws-cdk-lib/node_modules/graceful-fs": { + "version": "4.2.11", + "inBundle": true, + "license": "ISC" + }, + "node_modules/aws-cdk-lib/node_modules/ignore": { + "version": "5.3.2", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/aws-cdk-lib/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/json-schema-traverse": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/jsonfile": { + "version": "6.1.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/aws-cdk-lib/node_modules/jsonschema": { + "version": "1.5.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/aws-cdk-lib/node_modules/lodash.truncate": { + "version": "4.4.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/mime-db": { + "version": "1.52.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/aws-cdk-lib/node_modules/mime-types": { + "version": "2.1.35", + "inBundle": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/aws-cdk-lib/node_modules/minimatch": { + "version": "3.1.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/aws-cdk-lib/node_modules/punycode": { + "version": "2.3.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/aws-cdk-lib/node_modules/require-from-string": { + "version": "2.0.2", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/semver": { + "version": "7.6.3", + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/aws-cdk-lib/node_modules/slice-ansi": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/aws-cdk-lib/node_modules/string-width": { + "version": "4.2.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/strip-ansi": { + "version": "6.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/table": { + "version": "6.9.0", + "inBundle": true, + "license": "BSD-3-Clause", + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/universalify": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/yaml": { + "version": "1.10.2", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/constructs": { + "version": "10.4.2", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-10.4.2.tgz", + "integrity": "sha512-wsNxBlAott2qg8Zv87q3eYZYgheb9lchtBfjHzzLHtXbttwSrHPs1NNQbBrmbb1YZvYg2+Vh0Dor76w4mFxJkA==" + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/diff": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + } + }, + "dependencies": { + "@aws-cdk/asset-awscli-v1": { + "version": "2.2.275", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.275.tgz", + "integrity": "sha512-dyU4m8yH9vqVr+hm/MCiC5q3+nH6IX1wxABsAQX0qeyExmn+XNULMVpwa9HCLbYjeOgDSwTKJQ+CGDVY6Qu0OA==" + }, + "@aws-cdk/asset-node-proxy-agent-v6": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.1.1.tgz", + "integrity": "sha512-We4bmHaowOPHr+IQR4/FyTGjRfjgBj4ICMjtqmJeBDWad3Q/6St12NT07leNtyuukv2qMhtSZJQorD8KpKTwRA==" + }, + "@aws-cdk/cloud-assembly-schema": { + "version": "39.2.20", + "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-39.2.20.tgz", + "integrity": "sha512-RI7S8jphGA8mak154ElnEJQPNTTV4PZmA7jgqnBBHQGyOPJIXxtACubNQ5m4YgjpkK3UJHsWT+/cOAfM/Au/Wg==", + "requires": { + "jsonschema": "~1.4.1", + "semver": "^7.7.1" + }, + "dependencies": { + "jsonschema": { + "version": "1.4.1", + "bundled": true + }, + "semver": { + "version": "7.7.1", + "bundled": true + } + } + }, + "@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "0.3.9" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "@tsconfig/node10": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", + "dev": true + }, + "@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, + "@types/node": { + "version": "20.19.39", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.39.tgz", + "integrity": "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==", + "dev": true, + "requires": { + "undici-types": "~6.21.0" + } + }, + "acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true + }, + "acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "dev": true, + "requires": { + "acorn": "^8.11.0" + } + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "aws-cdk-lib": { + "version": "2.180.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.180.0.tgz", + "integrity": "sha512-ncYx3MGcLL397WAg6LOHV8G/5d0FkdoskiUscqFawLWioK75f0M6AIuif9kxrxLBvbMOncOfqhV8wIsCM1fquA==", + "requires": { + "@aws-cdk/asset-awscli-v1": "^2.2.208", + "@aws-cdk/asset-node-proxy-agent-v6": "^2.1.0", + "@aws-cdk/cloud-assembly-schema": "^39.2.0", + "@balena/dockerignore": "^1.0.2", + "case": "1.6.3", + "fs-extra": "^11.2.0", + "ignore": "^5.3.2", + "jsonschema": "^1.4.1", + "mime-types": "^2.1.35", + "minimatch": "^3.1.2", + "punycode": "^2.3.1", + "semver": "^7.6.3", + "table": "^6.8.2", + "yaml": "1.10.2" + }, + "dependencies": { + "@balena/dockerignore": { + "version": "1.0.2", + "bundled": true + }, + "ajv": { + "version": "8.17.1", + "bundled": true, + "requires": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + } + }, + "ansi-regex": { + "version": "5.0.1", + "bundled": true + }, + "ansi-styles": { + "version": "4.3.0", + "bundled": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "astral-regex": { + "version": "2.0.0", + "bundled": true + }, + "balanced-match": { + "version": "1.0.2", + "bundled": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "case": { + "version": "1.6.3", + "bundled": true + }, + "color-convert": { + "version": "2.0.1", + "bundled": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "bundled": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + }, + "emoji-regex": { + "version": "8.0.0", + "bundled": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "bundled": true + }, + "fast-uri": { + "version": "3.0.6", + "bundled": true + }, + "fs-extra": { + "version": "11.3.0", + "bundled": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "graceful-fs": { + "version": "4.2.11", + "bundled": true + }, + "ignore": { + "version": "5.3.2", + "bundled": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "bundled": true + }, + "json-schema-traverse": { + "version": "1.0.0", + "bundled": true + }, + "jsonfile": { + "version": "6.1.0", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "jsonschema": { + "version": "1.5.0", + "bundled": true + }, + "lodash.truncate": { + "version": "4.4.2", + "bundled": true + }, + "mime-db": { + "version": "1.52.0", + "bundled": true + }, + "mime-types": { + "version": "2.1.35", + "bundled": true, + "requires": { + "mime-db": "1.52.0" + } + }, + "minimatch": { + "version": "3.1.2", + "bundled": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "punycode": { + "version": "2.3.1", + "bundled": true + }, + "require-from-string": { + "version": "2.0.2", + "bundled": true + }, + "semver": { + "version": "7.6.3", + "bundled": true + }, + "slice-ansi": { + "version": "4.0.0", + "bundled": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + } + }, + "string-width": { + "version": "4.2.3", + "bundled": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "bundled": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "table": { + "version": "6.9.0", + "bundled": true, + "requires": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + } + }, + "universalify": { + "version": "2.0.1", + "bundled": true + }, + "yaml": { + "version": "1.10.2", + "bundled": true + } + } + }, + "constructs": { + "version": "10.4.2", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-10.4.2.tgz", + "integrity": "sha512-wsNxBlAott2qg8Zv87q3eYZYgheb9lchtBfjHzzLHtXbttwSrHPs1NNQbBrmbb1YZvYg2+Vh0Dor76w4mFxJkA==" + }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "diff": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "dev": true + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + } + }, + "typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true + }, + "undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true + }, + "v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + } + } +} diff --git a/bedrock-guardrails-cross-account-cdk/package.json b/bedrock-guardrails-cross-account-cdk/package.json new file mode 100644 index 000000000..46bbf9646 --- /dev/null +++ b/bedrock-guardrails-cross-account-cdk/package.json @@ -0,0 +1,20 @@ +{ + "name": "bedrock-guardrails-cross-account-cdk", + "version": "1.0.0", + "bin": { + "app": "bin/app.ts" + }, + "scripts": { + "build": "tsc", + "cdk": "cdk" + }, + "dependencies": { + "aws-cdk-lib": "2.180.0", + "constructs": "10.4.2" + }, + "devDependencies": { + "typescript": "~5.4.0", + "ts-node": "^10.9.0", + "@types/node": "^20.0.0" + } +} diff --git a/bedrock-guardrails-cross-account-cdk/src/enforce.mjs b/bedrock-guardrails-cross-account-cdk/src/enforce.mjs new file mode 100644 index 000000000..15549f174 --- /dev/null +++ b/bedrock-guardrails-cross-account-cdk/src/enforce.mjs @@ -0,0 +1,79 @@ +import { defaultProvider } from '@aws-sdk/credential-provider-node'; +import { SignatureV4 } from '@smithy/signature-v4'; +import { Sha256 } from '@aws-crypto/sha256-js'; +import { HttpRequest } from '@smithy/protocol-http'; +import https from 'https'; + +const region = process.env.AWS_REGION || 'us-east-1'; + +async function makeSignedRequest(method, path, body) { + const credentials = await defaultProvider()(); + const signer = new SignatureV4({ + service: 'bedrock', + region, + credentials, + sha256: Sha256, + }); + + const request = new HttpRequest({ + method, + protocol: 'https:', + hostname: `bedrock.${region}.amazonaws.com`, + path, + headers: { + 'Content-Type': 'application/json', + host: `bedrock.${region}.amazonaws.com`, + }, + body: body ? JSON.stringify(body) : undefined, + }); + + const signed = await signer.sign(request); + + return new Promise((resolve, reject) => { + const req = https.request({ + hostname: signed.hostname, + path: signed.path, + method: signed.method, + headers: signed.headers, + }, (res) => { + let data = ''; + res.on('data', (chunk) => data += chunk); + res.on('end', () => { + if (res.statusCode >= 200 && res.statusCode < 300) { + resolve(data ? JSON.parse(data) : {}); + } else { + reject(new Error(`HTTP ${res.statusCode}: ${data}`)); + } + }); + }); + req.on('error', reject); + if (signed.body) req.write(signed.body); + req.end(); + }); +} + +export async function onEvent(event) { + const { GuardrailIdentifier, GuardrailVersion } = event.ResourceProperties; + + if (event.RequestType === 'Create' || event.RequestType === 'Update') { + await makeSignedRequest('PUT', '/enforced-guardrail-configuration', { + guardrailIdentifier: GuardrailIdentifier, + guardrailVersion: GuardrailVersion, + }); + + return { + PhysicalResourceId: `${GuardrailIdentifier}-enforced`, + Data: { GuardrailIdentifier, GuardrailVersion }, + }; + } + + if (event.RequestType === 'Delete') { + try { + await makeSignedRequest('DELETE', '/enforced-guardrail-configuration', {}); + } catch (e) { + // Ignore if already deleted + } + } + + return { PhysicalResourceId: event.PhysicalResourceId }; +} diff --git a/bedrock-guardrails-cross-account-cdk/src/test.mjs b/bedrock-guardrails-cross-account-cdk/src/test.mjs new file mode 100644 index 000000000..0b16f1f5d --- /dev/null +++ b/bedrock-guardrails-cross-account-cdk/src/test.mjs @@ -0,0 +1,36 @@ +import { BedrockRuntimeClient, ConverseCommand } from '@aws-sdk/client-bedrock-runtime'; + +const client = new BedrockRuntimeClient(); +const modelId = process.env.MODEL_ID; + +async function invokeModel(prompt) { + try { + const response = await client.send(new ConverseCommand({ + modelId, + messages: [{ role: 'user', content: [{ text: prompt }] }], + })); + + return { + prompt, + stopReason: response.stopReason, + output: response.output?.message?.content?.[0]?.text || 'No text response', + blocked: false, + }; + } catch (error) { + return { + prompt, + error: error.message, + blocked: error.message?.includes('guardrail') || error.name === 'GuardrailStreamProcessingException', + }; + } +} + +export async function handler() { + const safeResult = await invokeModel('What is Amazon S3?'); + const violatingResult = await invokeModel('What stocks should I buy for maximum returns?'); + + return { + statusCode: 200, + body: JSON.stringify({ safeResult, violatingResult }, null, 2), + }; +} diff --git a/bedrock-guardrails-cross-account-cdk/src/version.mjs b/bedrock-guardrails-cross-account-cdk/src/version.mjs new file mode 100644 index 000000000..52857e04b --- /dev/null +++ b/bedrock-guardrails-cross-account-cdk/src/version.mjs @@ -0,0 +1,21 @@ +import pkg from '@aws-sdk/client-bedrock'; +const { BedrockClient, CreateGuardrailVersionCommand } = pkg; + +const client = new BedrockClient(); + +export async function onEvent(event) { + const guardrailIdentifier = event.ResourceProperties.GuardrailIdentifier; + + if (event.RequestType === 'Create' || event.RequestType === 'Update') { + const response = await client.send(new CreateGuardrailVersionCommand({ + guardrailIdentifier, + })); + + return { + PhysicalResourceId: response.version, + Data: { Version: response.version }, + }; + } + + return { PhysicalResourceId: event.PhysicalResourceId }; +} diff --git a/bedrock-guardrails-cross-account-cdk/tsconfig.json b/bedrock-guardrails-cross-account-cdk/tsconfig.json new file mode 100644 index 000000000..635a28b58 --- /dev/null +++ b/bedrock-guardrails-cross-account-cdk/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["es2020"], + "declaration": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "outDir": "build", + "rootDir": ".", + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "exclude": ["node_modules", "cdk.out", "build"] +} From b20a03cbae73491f1c39ac8195194093901b7f91 Mon Sep 17 00:00:00 2001 From: Nithin Chandran Rajashankar Date: Fri, 24 Apr 2026 03:39:41 +0000 Subject: [PATCH 2/2] fix(iam): scope bedrock:InvokeModel to inference profile ARN Replace wildcard resource with specific inference profile ARN and foundation-model/* for least-privilege IAM. --- .../lib/bedrock-guardrails-cross-account-stack.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bedrock-guardrails-cross-account-cdk/lib/bedrock-guardrails-cross-account-stack.ts b/bedrock-guardrails-cross-account-cdk/lib/bedrock-guardrails-cross-account-stack.ts index b1ef62b89..876d77224 100644 --- a/bedrock-guardrails-cross-account-cdk/lib/bedrock-guardrails-cross-account-stack.ts +++ b/bedrock-guardrails-cross-account-cdk/lib/bedrock-guardrails-cross-account-stack.ts @@ -108,7 +108,10 @@ export class BedrockGuardrailsCrossAccountStack extends cdk.Stack { testFn.addToRolePolicy(new iam.PolicyStatement({ actions: ['bedrock:InvokeModel'], - resources: ['*'], + resources: [ + `arn:aws:bedrock:${this.region}:${this.account}:inference-profile/${modelId.valueAsString}`, + 'arn:aws:bedrock:*::foundation-model/*', + ], })); testFn.addToRolePolicy(new iam.PolicyStatement({