@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,701 @@
1
+ import * as cdk from 'aws-cdk-lib';
2
+ import * as events from 'aws-cdk-lib/aws-events';
3
+ import * as targets from 'aws-cdk-lib/aws-events-targets';
4
+ import * as lambda from 'aws-cdk-lib/aws-lambda';
5
+ import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
6
+ import * as iam from 'aws-cdk-lib/aws-iam';
7
+ import * as logs from 'aws-cdk-lib/aws-logs';
8
+ import * as path from 'path';
9
+ import { Construct } from 'constructs';
10
+ import { BedrockGuardrailConstruct, createDefaultPrismGuardrailProps } from './constructs/bedrock-guardrail-construct';
11
+ import * as kms from 'aws-cdk-lib/aws-kms';
12
+ import { PrismVpcConstruct } from './constructs/prism-vpc-construct';
13
+ import { GuardrailEnforcerConstruct } from './constructs/guardrail-enforcer-construct';
14
+ import { SecurityAgentConstruct } from './constructs/security-agent-construct';
15
+ import { NagSuppressions } from 'cdk-nag';
16
+
17
+ export class MetricsPipelineStack extends cdk.Stack {
18
+ public readonly eventBus: events.EventBus;
19
+ public readonly eventsTable: dynamodb.Table;
20
+ public readonly metadataTable: dynamodb.Table;
21
+ public readonly kmsKey: kms.Key;
22
+ public readonly guardrail: BedrockGuardrailConstruct;
23
+ public readonly securityAgent?: SecurityAgentConstruct;
24
+
25
+ constructor(scope: Construct, id: string, props?: cdk.StackProps) {
26
+ super(scope, id, props);
27
+
28
+ // -------------------------------------------------------
29
+ // KMS encryption key (Pillar 6 — IP & Data Protection)
30
+ // -------------------------------------------------------
31
+ const prismKmsKey = new kms.Key(this, 'PrismDataKey', {
32
+ alias: 'alias/prism-d1-data-key',
33
+ description: 'Encryption key for PRISM D1 data at rest',
34
+ enableKeyRotation: true,
35
+ removalPolicy: cdk.RemovalPolicy.RETAIN,
36
+ });
37
+ this.kmsKey = prismKmsKey;
38
+
39
+ // Grant Security Agent service access to KMS key for agent space encryption
40
+ prismKmsKey.grantEncryptDecrypt(new iam.ServicePrincipal('securityagent.amazonaws.com'));
41
+ // Grant CloudWatch Logs access to KMS key for encrypted log groups
42
+ prismKmsKey.addToResourcePolicy(new iam.PolicyStatement({
43
+ effect: iam.Effect.ALLOW,
44
+ principals: [new iam.ServicePrincipal('logs.amazonaws.com')],
45
+ actions: ['kms:Encrypt', 'kms:Decrypt', 'kms:GenerateDataKey*', 'kms:DescribeKey'],
46
+ resources: ['*'],
47
+ conditions: {
48
+ ArnLike: {
49
+ 'kms:EncryptionContext:aws:logs:arn': [
50
+ `arn:aws:logs:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:log-group:/aws/securityagent/*`,
51
+ `arn:aws:logs:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:log-group:/aws/apigateway/prism-d1-*`,
52
+ ],
53
+ },
54
+ },
55
+ }));
56
+
57
+ // -------------------------------------------------------
58
+ // EventBridge custom event bus
59
+ // -------------------------------------------------------
60
+ this.eventBus = new events.EventBus(this, 'PrismMetricsBus', {
61
+ eventBusName: 'prism-d1-metrics',
62
+ });
63
+
64
+ // -------------------------------------------------------
65
+ // EventBridge resource policy — restrict PutEvents callers
66
+ // -------------------------------------------------------
67
+ new events.CfnEventBusPolicy(this, 'PrismBusPolicy', {
68
+ eventBusName: this.eventBus.eventBusName,
69
+ statementId: 'AllowOnlyPrismCallers',
70
+ statement: {
71
+ Effect: 'Allow',
72
+ Principal: { AWS: cdk.Aws.ACCOUNT_ID },
73
+ Action: 'events:PutEvents',
74
+ Resource: this.eventBus.eventBusArn,
75
+ Condition: {
76
+ ArnLike: {
77
+ 'aws:PrincipalArn': [
78
+ `arn:aws:iam::${cdk.Aws.ACCOUNT_ID}:role/prism-d1-github-oidc-*`,
79
+ `arn:aws:iam::${cdk.Aws.ACCOUNT_ID}:role/${this.stackName}-*`,
80
+ ],
81
+ },
82
+ },
83
+ },
84
+ });
85
+
86
+ // -------------------------------------------------------
87
+ // DynamoDB events table — KMS encrypted
88
+ // -------------------------------------------------------
89
+ this.eventsTable = new dynamodb.Table(this, 'EventsTable', {
90
+ tableName: 'prism-d1-events',
91
+ partitionKey: { name: 'pk', type: dynamodb.AttributeType.STRING },
92
+ sortKey: { name: 'sk', type: dynamodb.AttributeType.STRING },
93
+ billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
94
+ pointInTimeRecoverySpecification: { pointInTimeRecoveryEnabled: true },
95
+ removalPolicy: cdk.RemovalPolicy.RETAIN,
96
+ timeToLiveAttribute: 'ttl',
97
+ encryption: dynamodb.TableEncryption.CUSTOMER_MANAGED,
98
+ encryptionKey: prismKmsKey,
99
+ });
100
+
101
+ this.eventsTable.addGlobalSecondaryIndex({
102
+ indexName: 'by-detail-type',
103
+ partitionKey: { name: 'detail_type', type: dynamodb.AttributeType.STRING },
104
+ sortKey: { name: 'sk', type: dynamodb.AttributeType.STRING },
105
+ projectionType: dynamodb.ProjectionType.ALL,
106
+ });
107
+
108
+ this.eventsTable.addGlobalSecondaryIndex({
109
+ indexName: 'by-finding-id',
110
+ partitionKey: { name: 'finding_id', type: dynamodb.AttributeType.STRING },
111
+ sortKey: { name: 'sk', type: dynamodb.AttributeType.STRING },
112
+ projectionType: dynamodb.ProjectionType.ALL,
113
+ });
114
+
115
+ // -------------------------------------------------------
116
+ // DynamoDB metadata table
117
+ // -------------------------------------------------------
118
+ this.metadataTable = new dynamodb.Table(this, 'TeamMetadataTable', {
119
+ tableName: 'prism-team-metadata',
120
+ partitionKey: { name: 'team_id', type: dynamodb.AttributeType.STRING },
121
+ sortKey: { name: 'repo', type: dynamodb.AttributeType.STRING },
122
+ billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
123
+ pointInTimeRecoverySpecification: { pointInTimeRecoveryEnabled: true },
124
+ removalPolicy: cdk.RemovalPolicy.RETAIN,
125
+ encryption: dynamodb.TableEncryption.CUSTOMER_MANAGED,
126
+ encryptionKey: prismKmsKey,
127
+ });
128
+
129
+ // -------------------------------------------------------
130
+ // VPC for Lambda isolation (Pillar 6)
131
+ // -------------------------------------------------------
132
+ const vpcConstruct = new PrismVpcConstruct(this, 'VPC');
133
+
134
+ // -------------------------------------------------------
135
+ // Metrics processor Lambda
136
+ // -------------------------------------------------------
137
+ const metricsProcessor = new lambda.Function(this, 'MetricsProcessor', {
138
+ functionName: 'prism-d1-metrics-processor',
139
+ runtime: lambda.Runtime.NODEJS_22_X,
140
+ handler: 'metrics-processor.handler',
141
+ vpc: vpcConstruct.vpc,
142
+ securityGroups: [vpcConstruct.lambdaSecurityGroup],
143
+ code: lambda.Code.fromAsset(path.join(__dirname, 'lambda'), {
144
+ bundling: {
145
+ image: lambda.Runtime.NODEJS_22_X.bundlingImage,
146
+ command: [
147
+ 'bash', '-c',
148
+ [
149
+ 'npm init -y > /dev/null 2>&1',
150
+ 'npm install --save @aws-sdk/client-dynamodb @aws-sdk/client-cloudwatch esbuild > /dev/null 2>&1',
151
+ 'npx esbuild metrics-processor.ts --bundle --platform=node --target=node22 --outfile=/asset-output/metrics-processor.js --external:@aws-sdk/*',
152
+ ].join(' && '),
153
+ ],
154
+ local: {
155
+ tryBundle(outputDir: string): boolean {
156
+ // Local bundling via esbuild if available
157
+ try {
158
+ const { execSync } = require('child_process');
159
+ execSync(
160
+ `npx esbuild ${path.join(__dirname, 'lambda', 'metrics-processor.ts')} --bundle --platform=node --target=node22 --outfile=${path.join(outputDir, 'metrics-processor.js')} --external:@aws-sdk/*`,
161
+ { stdio: 'pipe' },
162
+ );
163
+ return true;
164
+ } catch {
165
+ return false;
166
+ }
167
+ },
168
+ },
169
+ },
170
+ }),
171
+ timeout: cdk.Duration.seconds(30),
172
+ memorySize: 256,
173
+ environment: {
174
+ EVENTS_TABLE: this.eventsTable.tableName,
175
+ METADATA_TABLE: this.metadataTable.tableName,
176
+ METRIC_NAMESPACE: 'PRISM/D1/Velocity',
177
+ },
178
+ logRetention: logs.RetentionDays.ONE_MONTH,
179
+ description: 'Processes PRISM D1 metric events from EventBridge into DynamoDB and CloudWatch',
180
+ });
181
+
182
+ // -------------------------------------------------------
183
+ // IAM permissions for the processor
184
+ // -------------------------------------------------------
185
+ this.eventsTable.grantWriteData(metricsProcessor);
186
+ this.metadataTable.grantWriteData(metricsProcessor);
187
+
188
+ metricsProcessor.addToRolePolicy(
189
+ new iam.PolicyStatement({
190
+ effect: iam.Effect.ALLOW,
191
+ actions: ['cloudwatch:PutMetricData'],
192
+ resources: ['*'],
193
+ conditions: {
194
+ StringEquals: {
195
+ 'cloudwatch:namespace': 'PRISM/D1/Velocity',
196
+ },
197
+ },
198
+ }),
199
+ );
200
+
201
+ // -------------------------------------------------------
202
+ // EventBridge rules — one per detail-type category
203
+ // -------------------------------------------------------
204
+ const detailTypes = [
205
+ 'prism.d1.commit',
206
+ 'prism.d1.pr',
207
+ 'prism.d1.deploy',
208
+ 'prism.d1.eval',
209
+ 'prism.d1.incident',
210
+ 'prism.d1.assessment',
211
+ 'prism.d1.agent',
212
+ 'prism.d1.agent.eval',
213
+ 'prism.d1.guardrail',
214
+ 'prism.d1.mcp.tool_call',
215
+ 'prism.d1.security',
216
+ 'prism.d1.security.design_review',
217
+ 'prism.d1.security.code_review',
218
+ 'prism.d1.security.pen_test',
219
+ 'prism.d1.security.remediation',
220
+ 'prism.d1.quality',
221
+ ];
222
+
223
+ for (const detailType of detailTypes) {
224
+ const ruleName = detailType.replace(/\./g, '-');
225
+ new events.Rule(this, `Rule-${ruleName}`, {
226
+ ruleName: `prism-d1-${ruleName}`,
227
+ eventBus: this.eventBus,
228
+ eventPattern: {
229
+ source: ['prism.d1.velocity'],
230
+ detailType: [detailType],
231
+ },
232
+ targets: [new targets.LambdaFunction(metricsProcessor)],
233
+ description: `Routes ${detailType} events to the metrics processor`,
234
+ });
235
+ }
236
+
237
+ // -------------------------------------------------------
238
+ // -------------------------------------------------------
239
+ // Bedrock Guardrail (Pillar 4)
240
+ // -------------------------------------------------------
241
+ this.guardrail = new BedrockGuardrailConstruct(this, 'PrismGuardrail', createDefaultPrismGuardrailProps());
242
+
243
+ // -------------------------------------------------------
244
+ // AWS Security Agent (opt-in — requires Security Agent access)
245
+ // Enable with: npx cdk deploy --context enableSecurityAgent=true
246
+ // -------------------------------------------------------
247
+ const enableSecurityAgent = this.node.tryGetContext('enableSecurityAgent') === 'true';
248
+ if (enableSecurityAgent) {
249
+ this.securityAgent = new SecurityAgentConstruct(this, 'SecurityAgent', {
250
+ agentSpaceName: 'prism-d1-security',
251
+ description: 'PRISM D1 Security Agent space for design review, code review, and pen testing',
252
+ kmsKey: prismKmsKey,
253
+ codeRemediationStrategy: 'DISABLED',
254
+ tags: {
255
+ 'prism:pillar': 'security',
256
+ },
257
+ });
258
+ }
259
+
260
+ // -------------------------------------------------------
261
+ // Guardrail Enforcer Layer (Pillar 6)
262
+ // -------------------------------------------------------
263
+ const guardrailEnforcer = new GuardrailEnforcerConstruct(this, 'GuardrailEnforcer', {
264
+ guardrailId: this.guardrail.guardrailId,
265
+ guardrailVersion: this.guardrail.guardrailVersion,
266
+ });
267
+
268
+ // Attach guardrail enforcer to the metrics processor
269
+ guardrailEnforcer.attachToFunction(metricsProcessor);
270
+
271
+ // -------------------------------------------------------
272
+ // Exfiltration Detector (Pillar 6)
273
+ // -------------------------------------------------------
274
+ const exfiltrationDetector = new lambda.Function(this, 'ExfiltrationDetector', {
275
+ functionName: 'prism-d1-exfiltration-detector',
276
+ runtime: lambda.Runtime.NODEJS_22_X,
277
+ handler: 'exfiltration-detector.handler',
278
+ vpc: vpcConstruct.vpc,
279
+ securityGroups: [vpcConstruct.lambdaSecurityGroup],
280
+ code: lambda.Code.fromAsset(path.join(__dirname, 'lambda'), {
281
+ bundling: {
282
+ image: lambda.Runtime.NODEJS_22_X.bundlingImage,
283
+ command: [
284
+ 'bash', '-c',
285
+ [
286
+ 'npm init -y > /dev/null 2>&1',
287
+ 'npm install --save @aws-sdk/client-eventbridge esbuild > /dev/null 2>&1',
288
+ 'npx esbuild exfiltration-detector.ts --bundle --platform=node --target=node22 --outfile=/asset-output/exfiltration-detector.js --external:@aws-sdk/*',
289
+ ].join(' && '),
290
+ ],
291
+ local: {
292
+ tryBundle(outputDir: string): boolean {
293
+ try {
294
+ const { execSync } = require('child_process');
295
+ execSync(
296
+ `npx esbuild ${path.join(__dirname, 'lambda', 'exfiltration-detector.ts')} --bundle --platform=node --target=node22 --outfile=${path.join(outputDir, 'exfiltration-detector.js')} --external:@aws-sdk/*`,
297
+ { stdio: 'pipe' },
298
+ );
299
+ return true;
300
+ } catch {
301
+ return false;
302
+ }
303
+ },
304
+ },
305
+ },
306
+ }),
307
+ timeout: cdk.Duration.seconds(30),
308
+ memorySize: 128,
309
+ environment: {
310
+ EVENT_BUS_NAME: this.eventBus.eventBusName,
311
+ ALERT_THRESHOLD_READS: '100',
312
+ },
313
+ logRetention: logs.RetentionDays.ONE_MONTH,
314
+ description: 'Detects anomalous read patterns on PRISM DynamoDB tables',
315
+ });
316
+
317
+ this.eventBus.grantPutEventsTo(exfiltrationDetector);
318
+
319
+ // CloudTrail → EventBridge rule for DynamoDB read events on PRISM tables
320
+ new events.Rule(this, 'DynamoDBReadDetectorRule', {
321
+ ruleName: 'prism-d1-dynamodb-read-detector',
322
+ eventPattern: {
323
+ source: ['aws.dynamodb'],
324
+ detailType: ['AWS API Call via CloudTrail'],
325
+ detail: {
326
+ eventSource: ['dynamodb.amazonaws.com'],
327
+ eventName: ['Query', 'Scan', 'GetItem', 'BatchGetItem'],
328
+ },
329
+ },
330
+ targets: [new targets.LambdaFunction(exfiltrationDetector)],
331
+ description: 'Routes DynamoDB read events to the exfiltration detector',
332
+ });
333
+
334
+ // -------------------------------------------------------
335
+ // Defect Correlator (Pillar 7)
336
+ // -------------------------------------------------------
337
+ const defectCorrelator = new lambda.Function(this, 'DefectCorrelator', {
338
+ functionName: 'prism-d1-defect-correlator',
339
+ runtime: lambda.Runtime.NODEJS_22_X,
340
+ handler: 'defect-correlator.handler',
341
+ vpc: vpcConstruct.vpc,
342
+ securityGroups: [vpcConstruct.lambdaSecurityGroup],
343
+ code: lambda.Code.fromAsset(path.join(__dirname, 'lambda'), {
344
+ bundling: {
345
+ image: lambda.Runtime.NODEJS_22_X.bundlingImage,
346
+ command: [
347
+ 'bash', '-c',
348
+ [
349
+ 'npm init -y > /dev/null 2>&1',
350
+ 'npm install --save @aws-sdk/client-dynamodb @aws-sdk/client-eventbridge esbuild > /dev/null 2>&1',
351
+ 'npx esbuild defect-correlator.ts --bundle --platform=node --target=node22 --outfile=/asset-output/defect-correlator.js --external:@aws-sdk/*',
352
+ ].join(' && '),
353
+ ],
354
+ local: {
355
+ tryBundle(outputDir: string): boolean {
356
+ try {
357
+ const { execSync } = require('child_process');
358
+ execSync(
359
+ `npx esbuild ${path.join(__dirname, 'lambda', 'defect-correlator.ts')} --bundle --platform=node --target=node22 --outfile=${path.join(outputDir, 'defect-correlator.js')} --external:@aws-sdk/*`,
360
+ { stdio: 'pipe' },
361
+ );
362
+ return true;
363
+ } catch {
364
+ return false;
365
+ }
366
+ },
367
+ },
368
+ },
369
+ }),
370
+ timeout: cdk.Duration.seconds(30),
371
+ memorySize: 256,
372
+ environment: {
373
+ EVENTS_TABLE: this.eventsTable.tableName,
374
+ EVENT_BUS_NAME: this.eventBus.eventBusName,
375
+ LOOKBACK_HOURS: '24',
376
+ },
377
+ logRetention: logs.RetentionDays.ONE_MONTH,
378
+ description: 'Correlates deployment failures with AI vs human commit origins',
379
+ });
380
+
381
+ this.eventsTable.grantReadData(defectCorrelator);
382
+ this.eventBus.grantPutEventsTo(defectCorrelator);
383
+
384
+ // Trigger defect correlator on failed deploy events
385
+ new events.Rule(this, 'DeployToDefectCorrelatorRule', {
386
+ ruleName: 'prism-d1-deploy-to-defect-correlator',
387
+ eventBus: this.eventBus,
388
+ eventPattern: {
389
+ source: ['prism.d1.velocity'],
390
+ detailType: ['prism.d1.deploy'],
391
+ },
392
+ targets: [new targets.LambdaFunction(defectCorrelator)],
393
+ description: 'Triggers defect correlation on deployment events',
394
+ });
395
+
396
+ // -------------------------------------------------------
397
+ // Spec-to-Code Calculator (Pillar 7)
398
+ // -------------------------------------------------------
399
+ const specToCodeCalc = new lambda.Function(this, 'SpecToCodeCalculator', {
400
+ functionName: 'prism-d1-spec-to-code-calculator',
401
+ runtime: lambda.Runtime.NODEJS_22_X,
402
+ handler: 'spec-to-code-calculator.handler',
403
+ vpc: vpcConstruct.vpc,
404
+ securityGroups: [vpcConstruct.lambdaSecurityGroup],
405
+ code: lambda.Code.fromAsset(path.join(__dirname, 'lambda'), {
406
+ bundling: {
407
+ image: lambda.Runtime.NODEJS_22_X.bundlingImage,
408
+ command: [
409
+ 'bash', '-c',
410
+ [
411
+ 'npm init -y > /dev/null 2>&1',
412
+ 'npm install --save @aws-sdk/client-dynamodb @aws-sdk/client-eventbridge esbuild > /dev/null 2>&1',
413
+ 'npx esbuild spec-to-code-calculator.ts --bundle --platform=node --target=node22 --outfile=/asset-output/spec-to-code-calculator.js --external:@aws-sdk/*',
414
+ ].join(' && '),
415
+ ],
416
+ local: {
417
+ tryBundle(outputDir: string): boolean {
418
+ try {
419
+ const { execSync } = require('child_process');
420
+ execSync(
421
+ `npx esbuild ${path.join(__dirname, 'lambda', 'spec-to-code-calculator.ts')} --bundle --platform=node --target=node22 --outfile=${path.join(outputDir, 'spec-to-code-calculator.js')} --external:@aws-sdk/*`,
422
+ { stdio: 'pipe' },
423
+ );
424
+ return true;
425
+ } catch {
426
+ return false;
427
+ }
428
+ },
429
+ },
430
+ },
431
+ }),
432
+ timeout: cdk.Duration.seconds(30),
433
+ memorySize: 256,
434
+ environment: {
435
+ EVENTS_TABLE: this.eventsTable.tableName,
436
+ EVENT_BUS_NAME: this.eventBus.eventBusName,
437
+ },
438
+ logRetention: logs.RetentionDays.ONE_MONTH,
439
+ description: 'Calculates spec-to-code hours for merged PRs with spec references',
440
+ });
441
+
442
+ this.eventsTable.grantReadData(specToCodeCalc);
443
+ this.eventBus.grantPutEventsTo(specToCodeCalc);
444
+
445
+ // Trigger spec-to-code calculator on merged PR events
446
+ new events.Rule(this, 'PrToSpecCalcRule', {
447
+ ruleName: 'prism-d1-pr-to-spec-calc',
448
+ eventBus: this.eventBus,
449
+ eventPattern: {
450
+ source: ['prism.d1.velocity'],
451
+ detailType: ['prism.d1.pr'],
452
+ },
453
+ targets: [new targets.LambdaFunction(specToCodeCalc)],
454
+ description: 'Triggers spec-to-code calculation on merged PR events',
455
+ });
456
+
457
+ // -------------------------------------------------------
458
+ // AWS Security Agent Integration
459
+ // -------------------------------------------------------
460
+ const securityAgentProcessor = new lambda.Function(this, 'SecurityAgentProcessor', {
461
+ functionName: 'prism-d1-security-agent-processor',
462
+ runtime: lambda.Runtime.NODEJS_22_X,
463
+ handler: 'security-agent-processor.handler',
464
+ vpc: vpcConstruct.vpc,
465
+ securityGroups: [vpcConstruct.lambdaSecurityGroup],
466
+ code: lambda.Code.fromAsset(path.join(__dirname, 'lambda'), {
467
+ bundling: {
468
+ image: lambda.Runtime.NODEJS_22_X.bundlingImage,
469
+ command: [
470
+ 'bash', '-c',
471
+ [
472
+ 'npm init -y > /dev/null 2>&1',
473
+ 'npm install --save @aws-sdk/client-dynamodb @aws-sdk/client-eventbridge esbuild > /dev/null 2>&1',
474
+ 'npx esbuild security-agent-processor.ts --bundle --platform=node --target=node22 --outfile=/asset-output/security-agent-processor.js --external:@aws-sdk/*',
475
+ ].join(' && '),
476
+ ],
477
+ local: {
478
+ tryBundle(outputDir: string): boolean {
479
+ try {
480
+ const { execSync } = require('child_process');
481
+ execSync(
482
+ `npx esbuild ${path.join(__dirname, 'lambda', 'security-agent-processor.ts')} --bundle --platform=node --target=node22 --outfile=${path.join(outputDir, 'security-agent-processor.js')} --external:@aws-sdk/*`,
483
+ { stdio: 'pipe' },
484
+ );
485
+ return true;
486
+ } catch {
487
+ return false;
488
+ }
489
+ },
490
+ },
491
+ },
492
+ }),
493
+ timeout: cdk.Duration.seconds(60),
494
+ memorySize: 256,
495
+ environment: {
496
+ EVENTS_TABLE: this.eventsTable.tableName,
497
+ METADATA_TABLE: this.metadataTable.tableName,
498
+ EVENT_BUS_NAME: this.eventBus.eventBusName,
499
+ },
500
+ logRetention: logs.RetentionDays.ONE_MONTH,
501
+ description: 'Normalizes AWS Security Agent findings and emits to PRISM pipeline',
502
+ });
503
+
504
+ this.eventsTable.grantReadData(securityAgentProcessor);
505
+ this.metadataTable.grantReadData(securityAgentProcessor);
506
+ this.eventBus.grantPutEventsTo(securityAgentProcessor);
507
+
508
+ // Security Remediation Tracker
509
+ const securityRemediationTracker = new lambda.Function(this, 'SecurityRemediationTracker', {
510
+ functionName: 'prism-d1-security-remediation-tracker',
511
+ runtime: lambda.Runtime.NODEJS_22_X,
512
+ handler: 'security-remediation-tracker.handler',
513
+ vpc: vpcConstruct.vpc,
514
+ securityGroups: [vpcConstruct.lambdaSecurityGroup],
515
+ code: lambda.Code.fromAsset(path.join(__dirname, 'lambda'), {
516
+ bundling: {
517
+ image: lambda.Runtime.NODEJS_22_X.bundlingImage,
518
+ command: [
519
+ 'bash', '-c',
520
+ [
521
+ 'npm init -y > /dev/null 2>&1',
522
+ 'npm install --save @aws-sdk/client-dynamodb @aws-sdk/client-eventbridge esbuild > /dev/null 2>&1',
523
+ 'npx esbuild security-remediation-tracker.ts --bundle --platform=node --target=node22 --outfile=/asset-output/security-remediation-tracker.js --external:@aws-sdk/*',
524
+ ].join(' && '),
525
+ ],
526
+ local: {
527
+ tryBundle(outputDir: string): boolean {
528
+ try {
529
+ const { execSync } = require('child_process');
530
+ execSync(
531
+ `npx esbuild ${path.join(__dirname, 'lambda', 'security-remediation-tracker.ts')} --bundle --platform=node --target=node22 --outfile=${path.join(outputDir, 'security-remediation-tracker.js')} --external:@aws-sdk/*`,
532
+ { stdio: 'pipe' },
533
+ );
534
+ return true;
535
+ } catch {
536
+ return false;
537
+ }
538
+ },
539
+ },
540
+ },
541
+ }),
542
+ timeout: cdk.Duration.seconds(30),
543
+ memorySize: 256,
544
+ environment: {
545
+ EVENTS_TABLE: this.eventsTable.tableName,
546
+ EVENT_BUS_NAME: this.eventBus.eventBusName,
547
+ },
548
+ logRetention: logs.RetentionDays.ONE_MONTH,
549
+ description: 'Tracks Security Agent finding remediation via merged PRs',
550
+ });
551
+
552
+ this.eventsTable.grantReadData(securityRemediationTracker);
553
+ this.eventBus.grantPutEventsTo(securityRemediationTracker);
554
+
555
+ // Trigger remediation tracker on PR merge events
556
+ new events.Rule(this, 'PrToRemediationTrackerRule', {
557
+ ruleName: 'prism-d1-pr-to-remediation-tracker',
558
+ eventBus: this.eventBus,
559
+ eventPattern: {
560
+ source: ['prism.d1.velocity'],
561
+ detailType: ['prism.d1.pr'],
562
+ },
563
+ targets: [new targets.LambdaFunction(securityRemediationTracker)],
564
+ description: 'Triggers security remediation tracking on merged PR events',
565
+ });
566
+
567
+ // Security Response Automator
568
+ const securityResponseAutomator = new lambda.Function(this, 'SecurityResponseAutomator', {
569
+ functionName: 'prism-d1-security-response-automator',
570
+ runtime: lambda.Runtime.NODEJS_22_X,
571
+ handler: 'security-response-automator.handler',
572
+ vpc: vpcConstruct.vpc,
573
+ securityGroups: [vpcConstruct.lambdaSecurityGroup],
574
+ code: lambda.Code.fromAsset(path.join(__dirname, 'lambda'), {
575
+ bundling: {
576
+ image: lambda.Runtime.NODEJS_22_X.bundlingImage,
577
+ command: [
578
+ 'bash', '-c',
579
+ [
580
+ 'npm init -y > /dev/null 2>&1',
581
+ 'npm install --save @aws-sdk/client-dynamodb @aws-sdk/client-eventbridge esbuild > /dev/null 2>&1',
582
+ 'npx esbuild security-response-automator.ts --bundle --platform=node --target=node22 --outfile=/asset-output/security-response-automator.js --external:@aws-sdk/*',
583
+ ].join(' && '),
584
+ ],
585
+ local: {
586
+ tryBundle(outputDir: string): boolean {
587
+ try {
588
+ const { execSync } = require('child_process');
589
+ execSync(
590
+ `npx esbuild ${path.join(__dirname, 'lambda', 'security-response-automator.ts')} --bundle --platform=node --target=node22 --outfile=${path.join(outputDir, 'security-response-automator.js')} --external:@aws-sdk/*`,
591
+ { stdio: 'pipe' },
592
+ );
593
+ return true;
594
+ } catch {
595
+ return false;
596
+ }
597
+ },
598
+ },
599
+ },
600
+ }),
601
+ timeout: cdk.Duration.seconds(30),
602
+ memorySize: 256,
603
+ environment: {
604
+ EVENTS_TABLE: this.eventsTable.tableName,
605
+ EVENT_BUS_NAME: this.eventBus.eventBusName,
606
+ GUARDRAIL_ID: this.guardrail.guardrailId,
607
+ GUARDRAIL_VERSION: this.guardrail.guardrailVersion,
608
+ },
609
+ logRetention: logs.RetentionDays.ONE_MONTH,
610
+ description: 'Auto-responds to critical Security Agent findings (guardrail tightening, alerts)',
611
+ });
612
+
613
+ this.eventsTable.grantWriteData(securityResponseAutomator);
614
+ this.eventBus.grantPutEventsTo(securityResponseAutomator);
615
+
616
+ // Trigger automator on critical security findings
617
+ new events.Rule(this, 'SecurityFindingToAutomatorRule', {
618
+ ruleName: 'prism-d1-security-finding-to-automator',
619
+ eventBus: this.eventBus,
620
+ eventPattern: {
621
+ source: ['prism.d1.velocity'],
622
+ detailType: ['prism.d1.security.code_review', 'prism.d1.security.pen_test'],
623
+ },
624
+ targets: [new targets.LambdaFunction(securityResponseAutomator)],
625
+ description: 'Triggers automated response on code review and pen test findings',
626
+ });
627
+
628
+ // -------------------------------------------------------
629
+ // Data Residency Controls (Pillar 6)
630
+ // -------------------------------------------------------
631
+ metricsProcessor.addToRolePolicy(
632
+ new iam.PolicyStatement({
633
+ effect: iam.Effect.DENY,
634
+ actions: ['dynamodb:*'],
635
+ resources: ['*'],
636
+ conditions: {
637
+ StringNotEquals: {
638
+ 'aws:RequestedRegion': cdk.Aws.REGION,
639
+ },
640
+ },
641
+ }),
642
+ );
643
+
644
+ // -------------------------------------------------------
645
+ // CDK-nag suppressions
646
+ // -------------------------------------------------------
647
+ NagSuppressions.addStackSuppressions(this, [
648
+ {
649
+ id: 'AwsSolutions-IAM4',
650
+ reason: 'AWSLambdaBasicExecutionRole is required for Lambda CloudWatch Logs access',
651
+ appliesTo: ['Policy::arn:<AWS::Partition>:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'],
652
+ },
653
+ {
654
+ id: 'AwsSolutions-IAM4',
655
+ reason: 'AWSLambdaVPCAccessExecutionRole is required for VPC-attached Lambdas to manage ENIs',
656
+ appliesTo: ['Policy::arn:<AWS::Partition>:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole'],
657
+ },
658
+ {
659
+ id: 'AwsSolutions-IAM5',
660
+ reason: 'CDK grantWriteData/grantReadData generates wildcard for DynamoDB GSI index ARNs (table/*/index/*)',
661
+ appliesTo: ['Resource::arn:<AWS::Partition>:dynamodb:<AWS::Region>:<AWS::AccountId>:table/prism-d1-events/index/*',
662
+ 'Resource::arn:<AWS::Partition>:dynamodb:<AWS::Region>:<AWS::AccountId>:table/prism-team-metadata/index/*'],
663
+ },
664
+ {
665
+ id: 'AwsSolutions-IAM5',
666
+ reason: 'CloudWatch PutMetricData does not support resource-level permissions',
667
+ appliesTo: ['Resource::*'],
668
+ },
669
+ {
670
+ id: 'AwsSolutions-IAM5',
671
+ reason: 'CDK KMS grant generates kms:GenerateDataKey* and kms:ReEncrypt* wildcard actions which are required for envelope encryption',
672
+ appliesTo: ['Action::kms:GenerateDataKey*', 'Action::kms:ReEncrypt*'],
673
+ },
674
+ {
675
+ id: 'AwsSolutions-IAM5',
676
+ reason: 'CDK grantReadData on DynamoDB generates wildcard for GSI index ARNs via token reference',
677
+ appliesTo: ['Resource::<EventsTableD24865E5.Arn>/index/*'],
678
+ },
679
+ { id: 'AwsSolutions-L1', reason: 'All Lambdas use nodejs22.x which is the latest Node.js runtime available' },
680
+ ]);
681
+
682
+ // -------------------------------------------------------
683
+ // Outputs
684
+ // -------------------------------------------------------
685
+ new cdk.CfnOutput(this, 'EventBusArn', {
686
+ value: this.eventBus.eventBusArn,
687
+ description: 'PRISM D1 Metrics EventBridge bus ARN',
688
+ exportName: 'PrismD1EventBusArn',
689
+ });
690
+
691
+ new cdk.CfnOutput(this, 'EventsTableName', {
692
+ value: this.eventsTable.tableName,
693
+ exportName: 'PrismD1EventsTable',
694
+ });
695
+
696
+ new cdk.CfnOutput(this, 'MetadataTableName', {
697
+ value: this.metadataTable.tableName,
698
+ exportName: 'PrismD1MetadataTable',
699
+ });
700
+ }
701
+ }