diff --git a/bedrock-guardrails-enforcement-cdk/.gitignore b/bedrock-guardrails-enforcement-cdk/.gitignore new file mode 100644 index 0000000000..52f5a01162 --- /dev/null +++ b/bedrock-guardrails-enforcement-cdk/.gitignore @@ -0,0 +1,3 @@ +node_modules +build +cdk.out diff --git a/bedrock-guardrails-enforcement-cdk/README.md b/bedrock-guardrails-enforcement-cdk/README.md new file mode 100644 index 0000000000..c9c35fdaa9 --- /dev/null +++ b/bedrock-guardrails-enforcement-cdk/README.md @@ -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 \ + --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 diff --git a/bedrock-guardrails-enforcement-cdk/bin/app.ts b/bedrock-guardrails-enforcement-cdk/bin/app.ts new file mode 100644 index 0000000000..274b8153f7 --- /dev/null +++ b/bedrock-guardrails-enforcement-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 { BedrockGuardrailsEnforcementStack } from '../lib/bedrock-guardrails-enforcement-stack'; + +const app = new cdk.App(); +new BedrockGuardrailsEnforcementStack(app, 'BedrockGuardrailsEnforcementStack'); diff --git a/bedrock-guardrails-enforcement-cdk/cdk.json b/bedrock-guardrails-enforcement-cdk/cdk.json new file mode 100644 index 0000000000..27fe6d2ecb --- /dev/null +++ b/bedrock-guardrails-enforcement-cdk/cdk.json @@ -0,0 +1,3 @@ +{ + "app": "npx ts-node bin/app.ts" +} diff --git a/bedrock-guardrails-enforcement-cdk/example-pattern.json b/bedrock-guardrails-enforcement-cdk/example-pattern.json new file mode 100644 index 0000000000..2c0b02b85e --- /dev/null +++ b/bedrock-guardrails-enforcement-cdk/example-pattern.json @@ -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: cdk destroy." + ] + }, + "authors": [ + { + "name": "Nithin Chandran R", + "bio": "Technical Account Manager at AWS", + "linkedin": "nithin-chandran-r" + } + ] +} diff --git a/bedrock-guardrails-enforcement-cdk/lib/bedrock-guardrails-enforcement-stack.ts b/bedrock-guardrails-enforcement-cdk/lib/bedrock-guardrails-enforcement-stack.ts new file mode 100644 index 0000000000..0a895561f1 --- /dev/null +++ b/bedrock-guardrails-enforcement-cdk/lib/bedrock-guardrails-enforcement-stack.ts @@ -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, + }); + } +} diff --git a/bedrock-guardrails-enforcement-cdk/package-lock.json b/bedrock-guardrails-enforcement-cdk/package-lock.json new file mode 100644 index 0000000000..17336b8879 --- /dev/null +++ b/bedrock-guardrails-enforcement-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-enforcement-cdk/package.json b/bedrock-guardrails-enforcement-cdk/package.json new file mode 100644 index 0000000000..28309cf51a --- /dev/null +++ b/bedrock-guardrails-enforcement-cdk/package.json @@ -0,0 +1,20 @@ +{ + "name": "bedrock-guardrails-enforcement-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-enforcement-cdk/src/test.mjs b/bedrock-guardrails-enforcement-cdk/src/test.mjs new file mode 100644 index 0000000000..5fdbefb189 --- /dev/null +++ b/bedrock-guardrails-enforcement-cdk/src/test.mjs @@ -0,0 +1,39 @@ +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 }] }], + })); + + // An enforced (account-level) guardrail intervenes in-band: the call succeeds + // but returns stopReason 'guardrail_intervened' rather than throwing. + const stopReason = response.stopReason; + return { + prompt, + stopReason, + output: response.output?.message?.content?.[0]?.text || 'No text response', + blocked: stopReason === 'guardrail_intervened', + }; + } 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-enforcement-cdk/tsconfig.json b/bedrock-guardrails-enforcement-cdk/tsconfig.json new file mode 100644 index 0000000000..635a28b58d --- /dev/null +++ b/bedrock-guardrails-enforcement-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"] +} diff --git a/s3-lambda-bedrock-annotations-cdk/.gitignore b/s3-lambda-bedrock-annotations-cdk/.gitignore new file mode 100644 index 0000000000..2d4387fd6b --- /dev/null +++ b/s3-lambda-bedrock-annotations-cdk/.gitignore @@ -0,0 +1,9 @@ +node_modules/ +build/ +cdk.out/ +cdk.context.json +src/boto3-layer/python/ +*.js +*.d.ts +!bin/app.ts +!lib/*.ts diff --git a/s3-lambda-bedrock-annotations-cdk/README.md b/s3-lambda-bedrock-annotations-cdk/README.md new file mode 100644 index 0000000000..161f590664 --- /dev/null +++ b/s3-lambda-bedrock-annotations-cdk/README.md @@ -0,0 +1,128 @@ +# Automated AI Document Annotation with Amazon S3 Annotations and Amazon Bedrock + +This pattern deploys an automated document enrichment pipeline that uses Amazon Bedrock to generate AI-powered metadata and stores it as queryable Amazon S3 annotations. + +Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns/s3-lambda-bedrock-annotations-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 20+](https://nodejs.org/en/download/) installed +- [AWS CDK v2](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html) installed (`npm install -g aws-cdk`) +- [Python 3.12](https://www.python.org/downloads/) installed (for building the boto3 layer) +- [Amazon Bedrock model access](https://docs.aws.amazon.com/bedrock/latest/userguide/model-access.html) enabled for Anthropic Claude Sonnet in your account + +## Architecture + +1. A file is uploaded to the Amazon S3 bucket. +2. Amazon S3 sends an Object Created event to Amazon EventBridge. +3. An Amazon EventBridge rule triggers the AWS Lambda function. +4. The Lambda function reads the object, invokes Amazon Bedrock (Claude Sonnet) to generate a structured summary, keywords, and content classification. +5. The AI-generated metadata is written back as an Amazon S3 annotation using the `PutObjectAnnotation` API. + +## Deployment + +1. Clone the repository and navigate to the pattern directory: + +```bash +git clone https://github.com/aws-samples/serverless-patterns +cd serverless-patterns/s3-lambda-bedrock-annotations-cdk +``` + +2. Build the boto3 layer (required for S3 Annotations API support): + +```bash +cd src/boto3-layer +pip install -r requirements.txt -t python +cd ../.. +``` + +3. Install CDK dependencies and deploy: + +```bash +npm install +cdk bootstrap # if not already done +cdk deploy +``` + +## Testing + +1. Upload a text file to the bucket: + +```bash +BUCKET_NAME=$(aws cloudformation describe-stacks --stack-name S3LambdaBedrockAnnotationsStack --query 'Stacks[0].Outputs[?OutputKey==`BucketName`].OutputValue' --output text) + +echo "Amazon S3 Annotations is a new feature that allows you to attach rich, queryable metadata directly to S3 objects. Each object supports up to 1000 annotations of up to 1MB each." > sample.txt + +aws s3 cp sample.txt s3://$BUCKET_NAME/sample.txt +``` + +2. Wait ~10 seconds for the Lambda to process, then retrieve the annotation: + +```bash +aws s3api get-object-annotation \ + --bucket $BUCKET_NAME \ + --key sample.txt \ + --annotation-name ai-enrichment \ + annotation-output.json + +cat annotation-output.json +``` + +Expected output (example): + +```json +{ + "ai_summary": "A brief description of Amazon S3 Annotations, a feature for attaching rich queryable metadata to S3 objects with support for up to 1000 annotations per object.", + "keywords": ["S3", "annotations", "metadata", "queryable", "objects"], + "content_type": "article", + "model": "us.anthropic.claude-sonnet-4-20250514-v1:0" +} +``` + +3. List all annotations on the object: + +```bash +aws s3api list-object-annotations \ + --bucket $BUCKET_NAME \ + --key sample.txt +``` + +## Optional: Enable Annotation Tables for Athena Querying + +To query annotations at scale across all objects using Amazon Athena, enable annotation tables: + +```bash +aws s3api update-bucket-metadata-annotation-table-configuration \ + --bucket $BUCKET_NAME \ + --annotation-table-configuration '{"ConfigurationState":"ENABLED","Role":"arn:aws:iam::YOUR_ACCOUNT_ID:role/S3MetadataAnnotationRole"}' +``` + +> **Note**: Annotation table configuration is not yet available via AWS CloudFormation. Use the CLI or SDK to enable it. + +## Cleanup + +> **Warning**: This will delete the S3 bucket and all objects, including annotations. + +```bash +cdk destroy +``` + +## Useful Commands + +| Command | Description | +|---------|-------------| +| `npm install` | Install project dependencies | +| `cdk synth` | Emit the synthesized CloudFormation template | +| `cdk deploy` | Deploy the stack | +| `cdk destroy` | Remove the stack | + +--- + +Copyright 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +SPDX-License-Identifier: MIT-0 diff --git a/s3-lambda-bedrock-annotations-cdk/bin/app.ts b/s3-lambda-bedrock-annotations-cdk/bin/app.ts new file mode 100644 index 0000000000..c525fbd551 --- /dev/null +++ b/s3-lambda-bedrock-annotations-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 { S3LambdaBedrockAnnotationsStack } from '../lib/s3-lambda-bedrock-annotations-stack'; + +const app = new cdk.App(); +new S3LambdaBedrockAnnotationsStack(app, 'S3LambdaBedrockAnnotationsStack'); diff --git a/s3-lambda-bedrock-annotations-cdk/cdk.json b/s3-lambda-bedrock-annotations-cdk/cdk.json new file mode 100644 index 0000000000..a6700a2ff4 --- /dev/null +++ b/s3-lambda-bedrock-annotations-cdk/cdk.json @@ -0,0 +1,3 @@ +{ + "app": "npx ts-node --prefer-ts-exts bin/app.ts" +} diff --git a/s3-lambda-bedrock-annotations-cdk/example-pattern.json b/s3-lambda-bedrock-annotations-cdk/example-pattern.json new file mode 100644 index 0000000000..0157f74863 --- /dev/null +++ b/s3-lambda-bedrock-annotations-cdk/example-pattern.json @@ -0,0 +1,88 @@ +{ + "title": "Automated AI document annotation with Amazon S3 Annotations and Amazon Bedrock", + "description": "Automatically enrich Amazon S3 objects with AI-generated metadata annotations using Amazon Bedrock. When a file is uploaded, an AWS Lambda function generates a summary, keywords, and content classification, then stores them as a queryable Amazon S3 annotation.", + "language": "TypeScript", + "level": "200", + "framework": "AWS CDK", + "introBox": { + "headline": "How it works", + "text": [ + "When an object is uploaded to the Amazon S3 bucket, Amazon EventBridge triggers an AWS Lambda function.", + "The AWS Lambda function reads the object content and sends it to Amazon Bedrock (Claude Sonnet) to generate a structured summary, keywords, and content type classification.", + "The AI-generated metadata is written back to the object as an Amazon S3 annotation using the PutObjectAnnotation API.", + "Annotations are mutable, queryable via Amazon Athena (when annotation tables are enabled), and move automatically with the object during copy and replication." + ] + }, + "gitHub": { + "template": { + "repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/s3-lambda-bedrock-annotations-cdk", + "templateURL": "serverless-patterns/s3-lambda-bedrock-annotations-cdk", + "projectFolder": "s3-lambda-bedrock-annotations-cdk", + "templateFile": "lib/s3-lambda-bedrock-annotations-stack.ts" + } + }, + "resources": { + "bullets": [ + { + "text": "Amazon S3 Annotations documentation", + "link": "https://docs.aws.amazon.com/AmazonS3/latest/userguide/annotations.html" + }, + { + "text": "Amazon S3 Annotations launch blog", + "link": "https://aws.amazon.com/blogs/aws/amazon-s3-annotations-attach-rich-queryable-context-directly-to-your-objects/" + }, + { + "text": "Amazon Bedrock documentation", + "link": "https://docs.aws.amazon.com/bedrock/latest/userguide/what-is-bedrock.html" + } + ] + }, + "deploy": { + "text": [ + "cdk deploy" + ] + }, + "testing": { + "text": [ + "See the GitHub repo for detailed testing instructions." + ] + }, + "cleanup": { + "text": [ + "cdk destroy" + ] + }, + "authors": [ + { + "name": "Nithin Chandran R", + "bio": "Technical Account Manager at AWS, focused on SAP workloads and serverless architecture.", + "linkedin": "nithin-chandran-r" + } + ], + "patternArch": { + "icon1": { + "x": 20, + "y": 50, + "service": "s3", + "label": "Amazon S3" + }, + "icon2": { + "x": 50, + "y": 50, + "service": "eventbridge", + "label": "Amazon EventBridge" + }, + "icon3": { + "x": 80, + "y": 50, + "service": "lambda", + "label": "AWS Lambda" + }, + "icon4": { + "x": 110, + "y": 50, + "service": "bedrock", + "label": "Amazon Bedrock" + } + } +} diff --git a/s3-lambda-bedrock-annotations-cdk/lib/s3-lambda-bedrock-annotations-stack.ts b/s3-lambda-bedrock-annotations-cdk/lib/s3-lambda-bedrock-annotations-stack.ts new file mode 100644 index 0000000000..0334359b2c --- /dev/null +++ b/s3-lambda-bedrock-annotations-cdk/lib/s3-lambda-bedrock-annotations-stack.ts @@ -0,0 +1,71 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as path from 'path'; + +export class S3LambdaBedrockAnnotationsStack extends cdk.Stack { + constructor(scope: Construct, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + // Amazon S3 bucket with Amazon EventBridge notifications enabled + const bucket = new s3.Bucket(this, 'AnnotationsBucket', { + eventBridgeEnabled: true, + removalPolicy: cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: true, + }); + + // Lambda layer with latest boto3 (required for put_object_annotation) + const boto3Layer = new lambda.LayerVersion(this, 'Boto3Layer', { + code: lambda.Code.fromAsset(path.join(__dirname, '..', 'src', 'boto3-layer')), + compatibleRuntimes: [lambda.Runtime.PYTHON_3_12], + description: 'boto3 >= 1.43.31 with Amazon S3 Annotations support', + }); + + // Lambda function + const annotator = new lambda.Function(this, 'AnnotatorFunction', { + runtime: lambda.Runtime.PYTHON_3_12, + handler: 'index.handler', + code: lambda.Code.fromAsset(path.join(__dirname, '..', 'src', 'annotator')), + timeout: cdk.Duration.seconds(60), + memorySize: 256, + layers: [boto3Layer], + environment: { + BUCKET_NAME: bucket.bucketName, + }, + }); + + // IAM permissions: read Amazon S3 objects, write annotations, invoke Amazon Bedrock + bucket.grantRead(annotator); + annotator.addToRolePolicy(new iam.PolicyStatement({ + actions: ['s3:PutObjectAnnotation'], + resources: [bucket.arnForObjects('*')], + })); + annotator.addToRolePolicy(new iam.PolicyStatement({ + actions: ['bedrock:InvokeModel'], + resources: [ + 'arn:aws:bedrock:*::foundation-model/anthropic.claude-sonnet-4-20250514-v1:0', + `arn:aws:bedrock:*:${this.account}:inference-profile/us.anthropic.claude-sonnet-4-20250514-v1:0`, + ], + })); + + // Amazon EventBridge rule: trigger on Amazon S3 Object Created + const rule = new events.Rule(this, 'S3ObjectCreatedRule', { + eventPattern: { + source: ['aws.s3'], + detailType: ['Object Created'], + detail: { + bucket: { name: [bucket.bucketName] }, + }, + }, + }); + rule.addTarget(new targets.LambdaFunction(annotator)); + + // Outputs + new cdk.CfnOutput(this, 'BucketName', { value: bucket.bucketName }); + new cdk.CfnOutput(this, 'FunctionName', { value: annotator.functionName }); + } +} diff --git a/s3-lambda-bedrock-annotations-cdk/package.json b/s3-lambda-bedrock-annotations-cdk/package.json new file mode 100644 index 0000000000..956a99aa8c --- /dev/null +++ b/s3-lambda-bedrock-annotations-cdk/package.json @@ -0,0 +1,20 @@ +{ + "name": "s3-lambda-bedrock-annotations-cdk", + "version": "1.0.0", + "bin": { + "app": "bin/app.js" + }, + "scripts": { + "build": "tsc", + "synth": "cdk synth" + }, + "dependencies": { + "aws-cdk-lib": "^2.150.0", + "constructs": "^10.3.0" + }, + "devDependencies": { + "@types/node": "^25.9.3", + "aws-cdk": "^2.150.0", + "typescript": "~5.4.0" + } +} diff --git a/s3-lambda-bedrock-annotations-cdk/src/annotator/index.py b/s3-lambda-bedrock-annotations-cdk/src/annotator/index.py new file mode 100644 index 0000000000..e704f1ac94 --- /dev/null +++ b/s3-lambda-bedrock-annotations-cdk/src/annotator/index.py @@ -0,0 +1,100 @@ +""" +Amazon S3 Annotations enrichment handler. +Triggered by Amazon S3 Object Created events via Amazon EventBridge. +Reads the object, generates AI summary via Amazon Bedrock, writes annotation. +""" +import json +import boto3 +import os + +s3 = boto3.client('s3') +bedrock = boto3.client('bedrock-runtime') + +MODEL_ID = 'us.anthropic.claude-sonnet-4-20250514-v1:0' +MAX_CONTENT_BYTES = 10_000 # Limit content sent to Bedrock + + +def handler(event, context): + detail = event['detail'] + bucket = detail['bucket']['name'] + key = detail['object']['key'] + size = detail['object'].get('size', 0) + + # Skip non-text files and very large files + if size > 5_000_000 or not _is_supported(key): + print(f'Skipping {key} (size={size}, unsupported type)') + return {'status': 'skipped', 'key': key} + + try: + # Read object content + response = s3.get_object(Bucket=bucket, Key=key) + content = response['Body'].read(MAX_CONTENT_BYTES).decode('utf-8', errors='replace') + + # Generate summary via Amazon Bedrock + summary = _generate_summary(key, content) + + # Write annotation + annotation_payload = json.dumps({ + 'ai_summary': summary['summary'], + 'keywords': summary['keywords'], + 'content_type': summary['content_type'], + 'model': MODEL_ID, + }) + + s3.put_object_annotation( + Bucket=bucket, + Key=key, + AnnotationName='ai-enrichment', + AnnotationPayload=annotation_payload.encode('utf-8'), + ) + + print(f'Annotated {key} with {len(annotation_payload)} bytes') + return {'status': 'annotated', 'key': key, 'annotation_size': len(annotation_payload)} + + except Exception as e: + print(f'Error processing {key}: {e}') + raise + + +def _generate_summary(key, content): + prompt = f"""Analyze this document and return a JSON object with: +- "summary": a 2-3 sentence summary +- "keywords": up to 5 relevant keywords as an array +- "content_type": the type of content (e.g. "report", "code", "article", "data", "log") + +Filename: {key} +Content (first {MAX_CONTENT_BYTES} bytes): +{content} + +Respond ONLY with valid JSON.""" + + response = bedrock.invoke_model( + modelId=MODEL_ID, + contentType='application/json', + accept='application/json', + body=json.dumps({ + 'anthropic_version': 'bedrock-2023-05-31', + 'max_tokens': 300, + 'messages': [{'role': 'user', 'content': prompt}], + }), + ) + + result = json.loads(response['body'].read()) + text = result['content'][0]['text'] + + # Parse JSON from response + try: + return json.loads(text) + except json.JSONDecodeError: + # Fallback if model wraps in markdown + import re + match = re.search(r'\{.*\}', text, re.DOTALL) + if match: + return json.loads(match.group()) + return {'summary': text[:200], 'keywords': [], 'content_type': 'unknown'} + + +def _is_supported(key): + supported = ('.txt', '.md', '.json', '.csv', '.xml', '.yaml', '.yml', + '.py', '.js', '.ts', '.java', '.html', '.log') + return any(key.lower().endswith(ext) for ext in supported) diff --git a/s3-lambda-bedrock-annotations-cdk/src/boto3-layer/build.sh b/s3-lambda-bedrock-annotations-cdk/src/boto3-layer/build.sh new file mode 100644 index 0000000000..5e78eed57c --- /dev/null +++ b/s3-lambda-bedrock-annotations-cdk/src/boto3-layer/build.sh @@ -0,0 +1,7 @@ +#!/bin/bash +# Build boto3 layer with S3 Annotations support +set -e +cd "$(dirname "$0")" +rm -rf python +pip install -r requirements.txt -t python --quiet +echo "Layer built: $(python -c 'import importlib.metadata; print(importlib.metadata.version("boto3"))' 2>/dev/null || pip show boto3 2>/dev/null | grep Version)" diff --git a/s3-lambda-bedrock-annotations-cdk/src/boto3-layer/requirements.txt b/s3-lambda-bedrock-annotations-cdk/src/boto3-layer/requirements.txt new file mode 100644 index 0000000000..a1f88eb8b3 --- /dev/null +++ b/s3-lambda-bedrock-annotations-cdk/src/boto3-layer/requirements.txt @@ -0,0 +1 @@ +boto3>=1.43.31 diff --git a/s3-lambda-bedrock-annotations-cdk/tsconfig.json b/s3-lambda-bedrock-annotations-cdk/tsconfig.json new file mode 100644 index 0000000000..30115fbcc8 --- /dev/null +++ b/s3-lambda-bedrock-annotations-cdk/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["es2020"], + "types": ["node"], + "declaration": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "outDir": "./build", + "rootDir": "." + }, + "exclude": ["node_modules", "build"] +}