@prism-d1/cli 1.0.26 → 1.0.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/assets/eval-harness/README.md +114 -0
  2. package/dist/assets/eval-harness/eval-config.json +10 -0
  3. package/dist/assets/eval-harness/rubrics/agent-quality.json +79 -0
  4. package/dist/assets/eval-harness/rubrics/api-response-quality.json +45 -0
  5. package/dist/assets/eval-harness/rubrics/code-quality.json +98 -0
  6. package/dist/assets/eval-harness/rubrics/security-compliance.json +145 -0
  7. package/dist/assets/eval-harness/rubrics/spec-compliance.json +67 -0
  8. package/dist/assets/eval-harness/run-eval.sh +122 -0
  9. package/dist/assets/github-workflows/README.md +110 -0
  10. package/dist/assets/github-workflows/prism-agent-eval.yml +313 -0
  11. package/dist/assets/github-workflows/prism-ai-metrics.yml +261 -0
  12. package/dist/assets/github-workflows/prism-dora-weekly.yml +334 -0
  13. package/dist/assets/github-workflows/prism-eval-gate.yml +310 -0
  14. package/dist/assets/infra/bin/app.ts +56 -0
  15. package/dist/assets/infra/cdk.json +12 -0
  16. package/dist/assets/infra/lib/api-stack.ts +347 -0
  17. package/dist/assets/infra/lib/constructs/bedrock-guardrail-construct.ts +201 -0
  18. package/dist/assets/infra/lib/constructs/guardrail-enforcer-construct.ts +59 -0
  19. package/dist/assets/infra/lib/constructs/prism-vpc-construct.ts +75 -0
  20. package/dist/assets/infra/lib/constructs/security-agent-construct.ts +266 -0
  21. package/dist/assets/infra/lib/dashboard-stack.ts +1392 -0
  22. package/dist/assets/infra/lib/lambda/api-handler.ts +477 -0
  23. package/dist/assets/infra/lib/lambda/defect-correlator.ts +142 -0
  24. package/dist/assets/infra/lib/lambda/exfiltration-detector.ts +100 -0
  25. package/dist/assets/infra/lib/lambda/layers/guardrail-enforcer/nodejs/guardrail-enforcer.js +53 -0
  26. package/dist/assets/infra/lib/lambda/metrics-processor.ts +748 -0
  27. package/dist/assets/infra/lib/lambda/security-agent-processor.ts +231 -0
  28. package/dist/assets/infra/lib/lambda/security-remediation-tracker.ts +120 -0
  29. package/dist/assets/infra/lib/lambda/security-response-automator.ts +130 -0
  30. package/dist/assets/infra/lib/lambda/spec-to-code-calculator.ts +123 -0
  31. package/dist/assets/infra/lib/metrics-pipeline-stack.ts +701 -0
  32. package/dist/assets/infra/package.json +23 -0
  33. package/dist/assets/infra/tsconfig.json +24 -0
  34. package/dist/src/commands/bootstrapper/install-eval-harness.d.ts.map +1 -1
  35. package/dist/src/commands/bootstrapper/install-eval-harness.js +3 -4
  36. package/dist/src/commands/bootstrapper/install-eval-harness.js.map +1 -1
  37. package/dist/src/commands/bootstrapper/install-git-hooks.d.ts.map +1 -1
  38. package/dist/src/commands/bootstrapper/install-git-hooks.js +2 -5
  39. package/dist/src/commands/bootstrapper/install-git-hooks.js.map +1 -1
  40. package/dist/src/commands/securityagent/setup.d.ts.map +1 -1
  41. package/dist/src/commands/securityagent/setup.js +2 -3
  42. package/dist/src/commands/securityagent/setup.js.map +1 -1
  43. package/dist/src/commands/workshop/deploy-infra.d.ts.map +1 -1
  44. package/dist/src/commands/workshop/deploy-infra.js +2 -3
  45. package/dist/src/commands/workshop/deploy-infra.js.map +1 -1
  46. package/dist/src/commands/workshop/generate-demo-data.d.ts.map +1 -1
  47. package/dist/src/commands/workshop/generate-demo-data.js +3 -8
  48. package/dist/src/commands/workshop/generate-demo-data.js.map +1 -1
  49. package/dist/src/commands/workshop/perform-pen-test.d.ts.map +1 -1
  50. package/dist/src/commands/workshop/perform-pen-test.js +5 -14
  51. package/dist/src/commands/workshop/perform-pen-test.js.map +1 -1
  52. package/dist/src/utils/root.d.ts +6 -0
  53. package/dist/src/utils/root.d.ts.map +1 -1
  54. package/dist/src/utils/root.js +29 -0
  55. package/dist/src/utils/root.js.map +1 -1
  56. package/package.json +2 -2
@@ -0,0 +1,347 @@
1
+ import * as cdk from 'aws-cdk-lib';
2
+ import * as apigateway from 'aws-cdk-lib/aws-apigateway';
3
+ import * as lambda from 'aws-cdk-lib/aws-lambda';
4
+ import * as logs from 'aws-cdk-lib/aws-logs';
5
+ import * as events from 'aws-cdk-lib/aws-events';
6
+ import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
7
+ import * as sqs from 'aws-cdk-lib/aws-sqs';
8
+ import * as path from 'path';
9
+ import { Construct } from 'constructs';
10
+ import { NagSuppressions } from 'cdk-nag';
11
+
12
+ import * as kms from 'aws-cdk-lib/aws-kms';
13
+
14
+ export interface ApiStackProps extends cdk.StackProps {
15
+ eventBus: events.EventBus;
16
+ eventsTable: dynamodb.Table;
17
+ metadataTable: dynamodb.Table;
18
+ kmsKey: kms.IKey;
19
+ }
20
+
21
+ export class ApiStack extends cdk.Stack {
22
+ public readonly api: apigateway.RestApi;
23
+
24
+ constructor(scope: Construct, id: string, props: ApiStackProps) {
25
+ super(scope, id, props);
26
+
27
+ // -------------------------------------------------------
28
+ // Dead-letter queue for Lambda failures
29
+ // -------------------------------------------------------
30
+ const apiHandlerDlq = new sqs.Queue(this, 'ApiHandlerDLQ', {
31
+ queueName: 'prism-d1-api-handler-dlq',
32
+ retentionPeriod: cdk.Duration.days(14),
33
+ enforceSSL: true,
34
+ });
35
+
36
+ NagSuppressions.addResourceSuppressions(apiHandlerDlq, [
37
+ {
38
+ id: 'AwsSolutions-SQS3',
39
+ reason: 'This is a dead-letter queue itself; a DLQ on a DLQ is not needed.',
40
+ },
41
+ ]);
42
+
43
+ // -------------------------------------------------------
44
+ // API handler Lambda
45
+ // -------------------------------------------------------
46
+ const apiHandler = new lambda.Function(this, 'ApiHandler', {
47
+ functionName: 'prism-d1-api-handler',
48
+ runtime: lambda.Runtime.NODEJS_22_X,
49
+ handler: 'api-handler.handler',
50
+ code: lambda.Code.fromAsset(path.join(__dirname, 'lambda'), {
51
+ bundling: {
52
+ image: lambda.Runtime.NODEJS_22_X.bundlingImage,
53
+ command: [
54
+ 'bash', '-c',
55
+ [
56
+ 'npm init -y > /dev/null 2>&1',
57
+ 'npm install --save @aws-sdk/client-eventbridge @aws-sdk/client-dynamodb esbuild > /dev/null 2>&1',
58
+ 'npx esbuild api-handler.ts --bundle --platform=node --target=node22 --outfile=/asset-output/api-handler.js --external:@aws-sdk/*',
59
+ ].join(' && '),
60
+ ],
61
+ local: {
62
+ tryBundle(outputDir: string): boolean {
63
+ try {
64
+ const { execSync } = require('child_process');
65
+ execSync(
66
+ `npx esbuild ${path.join(__dirname, 'lambda', 'api-handler.ts')} --bundle --platform=node --target=node22 --outfile=${path.join(outputDir, 'api-handler.js')} --external:@aws-sdk/*`,
67
+ { stdio: 'pipe' },
68
+ );
69
+ return true;
70
+ } catch {
71
+ return false;
72
+ }
73
+ },
74
+ },
75
+ },
76
+ }),
77
+ timeout: cdk.Duration.seconds(30),
78
+ memorySize: 256,
79
+ reservedConcurrentExecutions: 10,
80
+ deadLetterQueue: apiHandlerDlq,
81
+ environment: {
82
+ EVENT_BUS_NAME: props.eventBus.eventBusName,
83
+ EVENTS_TABLE: props.eventsTable.tableName,
84
+ METADATA_TABLE: props.metadataTable.tableName,
85
+ },
86
+ logRetention: logs.RetentionDays.ONE_MONTH,
87
+ description: 'Handles PRISM D1 Velocity API requests',
88
+ });
89
+
90
+ // -------------------------------------------------------
91
+ // IAM permissions
92
+ // -------------------------------------------------------
93
+ props.eventBus.grantPutEventsTo(apiHandler);
94
+ props.eventsTable.grantReadData(apiHandler);
95
+ props.metadataTable.grantReadWriteData(apiHandler);
96
+
97
+ // -------------------------------------------------------
98
+ // API Gateway access log group
99
+ // -------------------------------------------------------
100
+ const accessLogGroup = new logs.LogGroup(this, 'ApiAccessLogs', {
101
+ logGroupName: '/aws/apigateway/prism-d1-api-access',
102
+ retention: logs.RetentionDays.ONE_MONTH,
103
+ encryptionKey: props.kmsKey,
104
+ removalPolicy: cdk.RemovalPolicy.DESTROY,
105
+ });
106
+
107
+ // -------------------------------------------------------
108
+ // Request validator
109
+ // -------------------------------------------------------
110
+
111
+ // -------------------------------------------------------
112
+ // API Gateway REST API
113
+ // -------------------------------------------------------
114
+ this.api = new apigateway.RestApi(this, 'PrismD1Api', {
115
+ restApiName: 'PRISM D1 Velocity API',
116
+ description: 'Metric ingestion and query API for PRISM D1 Velocity platform',
117
+ deployOptions: {
118
+ stageName: 'v1',
119
+ throttlingBurstLimit: 100,
120
+ throttlingRateLimit: 50,
121
+ loggingLevel: apigateway.MethodLoggingLevel.INFO,
122
+ metricsEnabled: true,
123
+ accessLogDestination: new apigateway.LogGroupLogDestination(accessLogGroup),
124
+ accessLogFormat: apigateway.AccessLogFormat.jsonWithStandardFields({
125
+ caller: true,
126
+ httpMethod: true,
127
+ ip: true,
128
+ protocol: true,
129
+ requestTime: true,
130
+ resourcePath: true,
131
+ responseLength: true,
132
+ status: true,
133
+ user: true,
134
+ }),
135
+ },
136
+ });
137
+
138
+ const requestValidator = new apigateway.RequestValidator(this, 'RequestValidator', {
139
+ restApi: this.api,
140
+ requestValidatorName: 'prism-d1-body-validator',
141
+ validateRequestBody: true,
142
+ validateRequestParameters: true,
143
+ });
144
+
145
+ // -------------------------------------------------------
146
+ // API Key + Usage Plan
147
+ // -------------------------------------------------------
148
+ const apiKey = this.api.addApiKey('PrismD1ApiKey', {
149
+ apiKeyName: 'prism-d1-velocity-key',
150
+ description: 'API key for PRISM D1 Velocity metric ingestion',
151
+ });
152
+
153
+ const usagePlan = this.api.addUsagePlan('PrismD1UsagePlan', {
154
+ name: 'prism-d1-standard',
155
+ description: 'Standard usage plan for PRISM D1 API',
156
+ throttle: {
157
+ rateLimit: 50,
158
+ burstLimit: 100,
159
+ },
160
+ quota: {
161
+ limit: 100_000,
162
+ period: apigateway.Period.MONTH,
163
+ },
164
+ });
165
+
166
+ usagePlan.addApiKey(apiKey);
167
+ usagePlan.addApiStage({ stage: this.api.deploymentStage });
168
+
169
+ // -------------------------------------------------------
170
+ // Request model for POST /metrics
171
+ // -------------------------------------------------------
172
+ const metricsModel = new apigateway.Model(this, 'MetricsModel', {
173
+ restApi: this.api,
174
+ contentType: 'application/json',
175
+ modelName: 'MetricsPayload',
176
+ schema: {
177
+ type: apigateway.JsonSchemaType.OBJECT,
178
+ required: ['detail-type', 'detail'],
179
+ properties: {
180
+ 'detail-type': { type: apigateway.JsonSchemaType.STRING },
181
+ detail: {
182
+ type: apigateway.JsonSchemaType.OBJECT,
183
+ required: ['team_id', 'repo', 'timestamp'],
184
+ properties: {
185
+ team_id: { type: apigateway.JsonSchemaType.STRING },
186
+ repo: { type: apigateway.JsonSchemaType.STRING },
187
+ timestamp: { type: apigateway.JsonSchemaType.STRING },
188
+ },
189
+ },
190
+ },
191
+ },
192
+ });
193
+
194
+ // -------------------------------------------------------
195
+ // Lambda integration
196
+ // -------------------------------------------------------
197
+ const lambdaIntegration = new apigateway.LambdaIntegration(apiHandler, {
198
+ proxy: true,
199
+ });
200
+
201
+ // POST /metrics
202
+ const metricsResource = this.api.root.addResource('metrics');
203
+ metricsResource.addMethod('POST', lambdaIntegration, {
204
+ apiKeyRequired: true,
205
+ requestValidator,
206
+ requestModels: { 'application/json': metricsModel },
207
+ });
208
+
209
+ // GET /metrics/{team_id}
210
+ const teamMetricsResource = metricsResource.addResource('{team_id}');
211
+ teamMetricsResource.addMethod('GET', lambdaIntegration, {
212
+ apiKeyRequired: true,
213
+ requestValidator,
214
+ });
215
+
216
+ // POST /assessment
217
+ const assessmentResource = this.api.root.addResource('assessment');
218
+ assessmentResource.addMethod('POST', lambdaIntegration, {
219
+ apiKeyRequired: true,
220
+ requestValidator,
221
+ requestModels: { 'application/json': metricsModel },
222
+ });
223
+
224
+ // POST /security-findings (Security Agent webhook)
225
+ const securityResource = this.api.root.addResource('security-findings');
226
+ securityResource.addMethod('POST', lambdaIntegration, {
227
+ apiKeyRequired: true,
228
+ });
229
+
230
+ // GET /security-findings/{team_id}
231
+ const teamSecurityResource = securityResource.addResource('{team_id}');
232
+ teamSecurityResource.addMethod('GET', lambdaIntegration, {
233
+ apiKeyRequired: true,
234
+ });
235
+
236
+ // -------------------------------------------------------
237
+ // cdk-nag suppressions
238
+ // -------------------------------------------------------
239
+
240
+ NagSuppressions.addResourceSuppressions(
241
+ this.api,
242
+ [
243
+ {
244
+ id: 'AwsSolutions-APIG2',
245
+ reason: 'Request validation is configured on individual methods via requestValidator.',
246
+ },
247
+ ],
248
+ true,
249
+ );
250
+
251
+ // API Gateway uses API key auth; Cognito/IAM auth not required for this internal tool
252
+ NagSuppressions.addResourceSuppressions(
253
+ this.api,
254
+ [
255
+ {
256
+ id: 'AwsSolutions-APIG4',
257
+ reason:
258
+ 'API key authentication is used for this internal metrics ingestion API. ' +
259
+ 'Cognito/IAM authorization is not required for this use case.',
260
+ },
261
+ {
262
+ id: 'AwsSolutions-COG4',
263
+ reason:
264
+ 'This API uses API key auth, not Cognito. Cognito authorizer is not applicable.',
265
+ },
266
+ ],
267
+ true,
268
+ );
269
+
270
+ // WAF is not attached — acceptable for an internal metrics API behind API keys
271
+ NagSuppressions.addResourceSuppressions(
272
+ this.api,
273
+ [
274
+ {
275
+ id: 'AwsSolutions-APIG3',
276
+ reason:
277
+ 'WAF is not required for this internal metrics API. ' +
278
+ 'Access is controlled via API keys and usage plans.',
279
+ },
280
+ ],
281
+ true,
282
+ );
283
+
284
+ // Lambda IAM wildcard from CDK-generated policies (DynamoDB index ARNs)
285
+ NagSuppressions.addResourceSuppressions(
286
+ apiHandler,
287
+ [
288
+ {
289
+ id: 'AwsSolutions-IAM5',
290
+ reason:
291
+ 'Wildcard in DynamoDB index ARN is auto-generated by CDK grantReadData/grantReadWriteData ' +
292
+ 'to cover GSI access. The table ARN itself is scoped.',
293
+ },
294
+ {
295
+ id: 'AwsSolutions-IAM4',
296
+ reason:
297
+ 'AWSLambdaBasicExecutionRole is required for Lambda CloudWatch Logs access. ' +
298
+ 'This is the standard CDK-managed execution role.',
299
+ appliesTo: [
300
+ 'Policy::arn:<AWS::Partition>:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole',
301
+ ],
302
+ },
303
+ ],
304
+ true,
305
+ );
306
+
307
+ // cdk-nag: LogRetention and API GW CloudWatch role use CDK-managed IAM
308
+ NagSuppressions.addStackSuppressions(this, [
309
+ {
310
+ id: 'AwsSolutions-IAM4',
311
+ reason:
312
+ 'CDK-internal constructs (LogRetention, API GW CloudWatch role) require AWS managed policies. ' +
313
+ 'These are not user-configurable.',
314
+ appliesTo: [
315
+ 'Policy::arn:<AWS::Partition>:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole',
316
+ 'Policy::arn:<AWS::Partition>:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs',
317
+ ],
318
+ },
319
+ {
320
+ id: 'AwsSolutions-IAM5',
321
+ reason:
322
+ 'LogRetention custom resource requires wildcard permissions to manage log groups. ' +
323
+ 'This is a CDK-internal construct.',
324
+ appliesTo: ['Resource::*'],
325
+ },
326
+ {
327
+ id: 'AwsSolutions-L1',
328
+ reason: 'Lambda uses nodejs22.x which is the latest Node.js runtime available in CDK',
329
+ },
330
+ ]);
331
+
332
+ // -------------------------------------------------------
333
+ // Outputs
334
+ // -------------------------------------------------------
335
+ new cdk.CfnOutput(this, 'ApiUrl', {
336
+ value: this.api.url,
337
+ description: 'PRISM D1 Velocity API endpoint',
338
+ exportName: 'PrismD1ApiUrl',
339
+ });
340
+
341
+ new cdk.CfnOutput(this, 'ApiKeyId', {
342
+ value: apiKey.keyId,
343
+ description: 'API Key ID (retrieve value from AWS Console or CLI)',
344
+ exportName: 'PrismD1ApiKeyId',
345
+ });
346
+ }
347
+ }
@@ -0,0 +1,201 @@
1
+ import * as cdk from 'aws-cdk-lib';
2
+ import * as bedrock from 'aws-cdk-lib/aws-bedrock';
3
+ import { Construct } from 'constructs';
4
+
5
+ export interface ContentFilterConfig {
6
+ type: 'HATE' | 'INSULTS' | 'SEXUAL' | 'VIOLENCE' | 'MISCONDUCT' | 'PROMPT_ATTACK';
7
+ inputStrength: 'NONE' | 'LOW' | 'MEDIUM' | 'HIGH';
8
+ outputStrength: 'NONE' | 'LOW' | 'MEDIUM' | 'HIGH';
9
+ }
10
+
11
+ export interface DeniedTopicConfig {
12
+ name: string;
13
+ definition: string;
14
+ examples: string[];
15
+ }
16
+
17
+ export interface PiiEntityConfig {
18
+ type: string;
19
+ action: 'BLOCK' | 'ANONYMIZE';
20
+ }
21
+
22
+ export interface RegexFilterConfig {
23
+ name: string;
24
+ pattern: string;
25
+ action: 'BLOCK' | 'ANONYMIZE';
26
+ }
27
+
28
+ export interface BedrockGuardrailProps {
29
+ guardrailName: string;
30
+ description?: string;
31
+ contentFilters?: ContentFilterConfig[];
32
+ deniedTopics?: DeniedTopicConfig[];
33
+ piiEntities?: PiiEntityConfig[];
34
+ regexFilters?: RegexFilterConfig[];
35
+ managedWordLists?: ('PROFANITY')[];
36
+ customWords?: string[];
37
+ blockedInputMessage?: string;
38
+ blockedOutputMessage?: string;
39
+ }
40
+
41
+ export class BedrockGuardrailConstruct extends Construct {
42
+ public readonly guardrailId: string;
43
+ public readonly guardrailVersion: string;
44
+ public readonly guardrailArn: string;
45
+
46
+ constructor(scope: Construct, id: string, props: BedrockGuardrailProps) {
47
+ super(scope, id);
48
+
49
+ // Build content policy config
50
+ const filtersConfig = (props.contentFilters ?? []).map((f) => ({
51
+ type: f.type,
52
+ inputStrength: f.inputStrength,
53
+ outputStrength: f.outputStrength,
54
+ }));
55
+
56
+ // Build topic policy config
57
+ const topicsConfig = (props.deniedTopics ?? []).map((t) => ({
58
+ name: t.name,
59
+ definition: t.definition,
60
+ examples: t.examples,
61
+ type: 'DENY',
62
+ }));
63
+
64
+ // Build sensitive info policy
65
+ const piiEntitiesConfig = (props.piiEntities ?? []).map((p) => ({
66
+ type: p.type,
67
+ action: p.action,
68
+ }));
69
+
70
+ const regexConfig = (props.regexFilters ?? []).map((r) => ({
71
+ name: r.name,
72
+ pattern: r.pattern,
73
+ action: r.action,
74
+ }));
75
+
76
+ // Build word policy config
77
+ const wordPolicyConfig: Record<string, unknown> = {};
78
+ if (props.managedWordLists && props.managedWordLists.length > 0) {
79
+ wordPolicyConfig.managedWordListsConfig = props.managedWordLists.map((w) => ({
80
+ type: w,
81
+ }));
82
+ }
83
+ if (props.customWords && props.customWords.length > 0) {
84
+ wordPolicyConfig.wordsConfig = props.customWords.map((w) => ({
85
+ text: w,
86
+ }));
87
+ }
88
+
89
+ const guardrail = new bedrock.CfnGuardrail(this, 'Guardrail', {
90
+ name: props.guardrailName,
91
+ description: props.description ?? `PRISM D1 guardrail: ${props.guardrailName}`,
92
+ blockedInputMessaging: props.blockedInputMessage ?? 'This request has been blocked by safety guardrails.',
93
+ blockedOutputsMessaging: props.blockedOutputMessage ?? 'This response has been blocked by safety guardrails.',
94
+ ...(filtersConfig.length > 0 && {
95
+ contentPolicyConfig: {
96
+ filtersConfig,
97
+ },
98
+ }),
99
+ ...(topicsConfig.length > 0 && {
100
+ topicPolicyConfig: {
101
+ topicsConfig,
102
+ },
103
+ }),
104
+ ...((piiEntitiesConfig.length > 0 || regexConfig.length > 0) && {
105
+ sensitiveInformationPolicyConfig: {
106
+ ...(piiEntitiesConfig.length > 0 && { piiEntitiesConfig }),
107
+ ...(regexConfig.length > 0 && { regexesConfig: regexConfig }),
108
+ },
109
+ }),
110
+ ...(Object.keys(wordPolicyConfig).length > 0 && {
111
+ wordPolicyConfig,
112
+ }),
113
+ tags: [
114
+ { key: 'prism:component', value: 'guardrail' },
115
+ { key: 'prism:pillar', value: 'security' },
116
+ ],
117
+ });
118
+
119
+ // Create a versioned snapshot
120
+ const guardrailVersion = new bedrock.CfnGuardrailVersion(this, 'GuardrailVersion', {
121
+ guardrailIdentifier: guardrail.attrGuardrailId,
122
+ description: `PRISM D1 guardrail version for ${props.guardrailName}`,
123
+ });
124
+
125
+ guardrailVersion.addDependency(guardrail);
126
+
127
+ this.guardrailId = guardrail.attrGuardrailId;
128
+ this.guardrailArn = guardrail.attrGuardrailArn;
129
+ this.guardrailVersion = guardrailVersion.attrVersion;
130
+
131
+ new cdk.CfnOutput(this, 'GuardrailIdOutput', {
132
+ value: this.guardrailId,
133
+ description: `Guardrail ID for ${props.guardrailName}`,
134
+ exportName: `PrismD1GuardrailId-${props.guardrailName}`,
135
+ });
136
+
137
+ new cdk.CfnOutput(this, 'GuardrailVersionOutput', {
138
+ value: this.guardrailVersion,
139
+ description: `Guardrail version for ${props.guardrailName}`,
140
+ exportName: `PrismD1GuardrailVersion-${props.guardrailName}`,
141
+ });
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Default PRISM guardrail configuration matching the template at
147
+ * bootstrapper/agent-configs/guardrails-template.json
148
+ */
149
+ export function createDefaultPrismGuardrailProps(): BedrockGuardrailProps {
150
+ return {
151
+ guardrailName: 'prism-d1-agent-guardrail',
152
+ description: 'PRISM D1 default guardrail for AI agents — content safety, PII protection, and topic governance.',
153
+ contentFilters: [
154
+ { type: 'HATE', inputStrength: 'HIGH', outputStrength: 'HIGH' },
155
+ { type: 'INSULTS', inputStrength: 'HIGH', outputStrength: 'HIGH' },
156
+ { type: 'SEXUAL', inputStrength: 'HIGH', outputStrength: 'HIGH' },
157
+ { type: 'VIOLENCE', inputStrength: 'HIGH', outputStrength: 'HIGH' },
158
+ { type: 'MISCONDUCT', inputStrength: 'HIGH', outputStrength: 'HIGH' },
159
+ { type: 'PROMPT_ATTACK', inputStrength: 'HIGH', outputStrength: 'NONE' },
160
+ ],
161
+ deniedTopics: [
162
+ {
163
+ name: 'competitor-recommendations',
164
+ definition: 'Recommending or endorsing competitor products or services.',
165
+ examples: [
166
+ 'You should use Competitor X instead.',
167
+ 'Competitor Y has a better solution for this.',
168
+ ],
169
+ },
170
+ {
171
+ name: 'financial-advice',
172
+ definition: 'Providing specific financial, investment, or tax advice.',
173
+ examples: [
174
+ 'You should invest in stocks right now.',
175
+ 'This tax strategy will save you money.',
176
+ ],
177
+ },
178
+ ],
179
+ piiEntities: [
180
+ { type: 'EMAIL', action: 'ANONYMIZE' },
181
+ { type: 'PHONE', action: 'ANONYMIZE' },
182
+ { type: 'US_SOCIAL_SECURITY_NUMBER', action: 'BLOCK' },
183
+ { type: 'CREDIT_DEBIT_CARD_NUMBER', action: 'BLOCK' },
184
+ ],
185
+ regexFilters: [
186
+ {
187
+ name: 'aws-access-key',
188
+ pattern: 'AKIA[A-Z0-9]{16}',
189
+ action: 'BLOCK',
190
+ },
191
+ {
192
+ name: 'generic-api-key',
193
+ pattern: 'sk-[a-zA-Z0-9]{32,}',
194
+ action: 'BLOCK',
195
+ },
196
+ ],
197
+ managedWordLists: ['PROFANITY'],
198
+ blockedInputMessage: 'Your request was blocked by PRISM safety guardrails. Please rephrase.',
199
+ blockedOutputMessage: 'The response was blocked by PRISM safety guardrails.',
200
+ };
201
+ }
@@ -0,0 +1,59 @@
1
+ import * as cdk from 'aws-cdk-lib';
2
+ import * as lambda from 'aws-cdk-lib/aws-lambda';
3
+ import * as iam from 'aws-cdk-lib/aws-iam';
4
+ import * as path from 'path';
5
+ import { Construct } from 'constructs';
6
+
7
+ /**
8
+ * Creates a Lambda Layer containing a guardrail enforcement client.
9
+ * Any Lambda or agent can use this layer to check input/output
10
+ * against the deployed Bedrock Guardrail before returning to the user.
11
+ */
12
+ export class GuardrailEnforcerConstruct extends Construct {
13
+ public readonly layer: lambda.LayerVersion;
14
+ public readonly guardrailId: string;
15
+ public readonly guardrailVersion: string;
16
+
17
+ constructor(
18
+ scope: Construct,
19
+ id: string,
20
+ props: { guardrailId: string; guardrailVersion: string },
21
+ ) {
22
+ super(scope, id);
23
+
24
+ this.guardrailId = props.guardrailId;
25
+ this.guardrailVersion = props.guardrailVersion;
26
+
27
+ // The layer provides a simple guardrail enforcement utility
28
+ this.layer = new lambda.LayerVersion(this, 'GuardrailEnforcerLayer', {
29
+ layerVersionName: 'prism-d1-guardrail-enforcer',
30
+ description: 'PRISM D1 Bedrock Guardrail enforcement client',
31
+ compatibleRuntimes: [lambda.Runtime.NODEJS_22_X],
32
+ code: lambda.Code.fromAsset(path.join(__dirname, '..', 'lambda', 'layers', 'guardrail-enforcer')),
33
+ removalPolicy: cdk.RemovalPolicy.RETAIN,
34
+ });
35
+
36
+ new cdk.CfnOutput(this, 'LayerArn', {
37
+ value: this.layer.layerVersionArn,
38
+ exportName: 'PrismD1GuardrailEnforcerLayerArn',
39
+ });
40
+ }
41
+
42
+ /**
43
+ * Grants a Lambda function permission to invoke the Bedrock Guardrail
44
+ * and adds the layer + environment variables.
45
+ */
46
+ attachToFunction(fn: lambda.Function): void {
47
+ fn.addLayers(this.layer);
48
+ fn.addEnvironment('GUARDRAIL_ID', this.guardrailId);
49
+ fn.addEnvironment('GUARDRAIL_VERSION', this.guardrailVersion);
50
+
51
+ fn.addToRolePolicy(
52
+ new iam.PolicyStatement({
53
+ effect: iam.Effect.ALLOW,
54
+ actions: ['bedrock:ApplyGuardrail'],
55
+ resources: ['*'],
56
+ }),
57
+ );
58
+ }
59
+ }
@@ -0,0 +1,75 @@
1
+ import * as cdk from 'aws-cdk-lib';
2
+ import * as ec2 from 'aws-cdk-lib/aws-ec2';
3
+ import { Construct } from 'constructs';
4
+ import { NagSuppressions } from 'cdk-nag';
5
+
6
+ /**
7
+ * Creates a VPC with private subnets for PRISM Lambda functions.
8
+ * Includes VPC endpoints for all required AWS services so Lambdas
9
+ * can operate without internet access (data residency / IP protection).
10
+ */
11
+ export class PrismVpcConstruct extends Construct {
12
+ public readonly vpc: ec2.Vpc;
13
+ public readonly lambdaSecurityGroup: ec2.SecurityGroup;
14
+
15
+ constructor(scope: Construct, id: string) {
16
+ super(scope, id);
17
+
18
+ this.vpc = new ec2.Vpc(this, 'PrismVpc', {
19
+ vpcName: 'prism-d1-vpc',
20
+ maxAzs: 2,
21
+ natGateways: 0, // No internet access — all traffic through VPC endpoints
22
+ subnetConfiguration: [
23
+ {
24
+ cidrMask: 24,
25
+ name: 'prism-private',
26
+ subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
27
+ },
28
+ ],
29
+ flowLogs: {
30
+ default: {
31
+ destination: ec2.FlowLogDestination.toCloudWatchLogs(),
32
+ trafficType: ec2.FlowLogTrafficType.ALL,
33
+ },
34
+ },
35
+ });
36
+
37
+ this.lambdaSecurityGroup = new ec2.SecurityGroup(this, 'LambdaSG', {
38
+ vpc: this.vpc,
39
+ securityGroupName: 'prism-d1-lambda-sg',
40
+ description: 'Security group for PRISM D1 Lambda functions',
41
+ allowAllOutbound: true, // Safe: VPC has no NAT/IGW, traffic only reaches AWS via endpoints
42
+ });
43
+
44
+ // --- Gateway endpoint: DynamoDB ---
45
+ this.vpc.addGatewayEndpoint('DynamoDBEndpoint', {
46
+ service: ec2.GatewayVpcEndpointAwsService.DYNAMODB,
47
+ });
48
+
49
+ // --- Interface endpoints ---
50
+ const interfaceEndpoints = [
51
+ { id: 'EventBridgeEndpoint', service: ec2.InterfaceVpcEndpointAwsService.EVENTBRIDGE },
52
+ { id: 'CloudWatchEndpoint', service: ec2.InterfaceVpcEndpointAwsService.CLOUDWATCH_MONITORING },
53
+ { id: 'CloudWatchLogsEndpoint', service: ec2.InterfaceVpcEndpointAwsService.CLOUDWATCH_LOGS },
54
+ { id: 'KMSEndpoint', service: ec2.InterfaceVpcEndpointAwsService.KMS },
55
+ { id: 'BedrockRuntimeEndpoint', service: ec2.InterfaceVpcEndpointAwsService.BEDROCK_RUNTIME },
56
+ ];
57
+
58
+ for (const ep of interfaceEndpoints) {
59
+ this.vpc.addInterfaceEndpoint(ep.id, {
60
+ service: ep.service,
61
+ privateDnsEnabled: true,
62
+ securityGroups: [this.lambdaSecurityGroup],
63
+ });
64
+ }
65
+
66
+ new cdk.CfnOutput(this, 'VpcId', {
67
+ value: this.vpc.vpcId,
68
+ exportName: 'PrismD1VpcId',
69
+ });
70
+
71
+ NagSuppressions.addResourceSuppressions(this.lambdaSecurityGroup, [
72
+ { id: 'CdkNagValidationFailure', reason: 'SG egress uses Fn::GetAtt for VPC CIDR which cannot be statically validated' },
73
+ ]);
74
+ }
75
+ }