@prism-d1/cli 1.0.27 → 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.
- package/dist/assets/eval-harness/README.md +114 -0
- package/dist/assets/eval-harness/eval-config.json +10 -0
- package/dist/assets/eval-harness/rubrics/agent-quality.json +79 -0
- package/dist/assets/eval-harness/rubrics/api-response-quality.json +45 -0
- package/dist/assets/eval-harness/rubrics/code-quality.json +98 -0
- package/dist/assets/eval-harness/rubrics/security-compliance.json +145 -0
- package/dist/assets/eval-harness/rubrics/spec-compliance.json +67 -0
- package/dist/assets/eval-harness/run-eval.sh +122 -0
- package/dist/assets/github-workflows/README.md +110 -0
- package/dist/assets/github-workflows/prism-agent-eval.yml +313 -0
- package/dist/assets/github-workflows/prism-ai-metrics.yml +261 -0
- package/dist/assets/github-workflows/prism-dora-weekly.yml +334 -0
- package/dist/assets/github-workflows/prism-eval-gate.yml +310 -0
- package/dist/assets/infra/bin/app.ts +56 -0
- package/dist/assets/infra/cdk.json +12 -0
- package/dist/assets/infra/lib/api-stack.ts +347 -0
- package/dist/assets/infra/lib/constructs/bedrock-guardrail-construct.ts +201 -0
- package/dist/assets/infra/lib/constructs/guardrail-enforcer-construct.ts +59 -0
- package/dist/assets/infra/lib/constructs/prism-vpc-construct.ts +75 -0
- package/dist/assets/infra/lib/constructs/security-agent-construct.ts +266 -0
- package/dist/assets/infra/lib/dashboard-stack.ts +1392 -0
- package/dist/assets/infra/lib/lambda/api-handler.ts +477 -0
- package/dist/assets/infra/lib/lambda/defect-correlator.ts +142 -0
- package/dist/assets/infra/lib/lambda/exfiltration-detector.ts +100 -0
- package/dist/assets/infra/lib/lambda/layers/guardrail-enforcer/nodejs/guardrail-enforcer.js +53 -0
- package/dist/assets/infra/lib/lambda/metrics-processor.ts +748 -0
- package/dist/assets/infra/lib/lambda/security-agent-processor.ts +231 -0
- package/dist/assets/infra/lib/lambda/security-remediation-tracker.ts +120 -0
- package/dist/assets/infra/lib/lambda/security-response-automator.ts +130 -0
- package/dist/assets/infra/lib/lambda/spec-to-code-calculator.ts +123 -0
- package/dist/assets/infra/lib/metrics-pipeline-stack.ts +701 -0
- package/dist/assets/infra/package.json +23 -0
- package/dist/assets/infra/tsconfig.json +24 -0
- package/dist/src/commands/bootstrapper/install-eval-harness.d.ts.map +1 -1
- package/dist/src/commands/bootstrapper/install-eval-harness.js +3 -4
- package/dist/src/commands/bootstrapper/install-eval-harness.js.map +1 -1
- package/dist/src/commands/bootstrapper/install-git-hooks.d.ts.map +1 -1
- package/dist/src/commands/bootstrapper/install-git-hooks.js +2 -5
- package/dist/src/commands/bootstrapper/install-git-hooks.js.map +1 -1
- package/dist/src/commands/securityagent/setup.d.ts.map +1 -1
- package/dist/src/commands/securityagent/setup.js +2 -3
- package/dist/src/commands/securityagent/setup.js.map +1 -1
- package/dist/src/commands/workshop/deploy-infra.d.ts.map +1 -1
- package/dist/src/commands/workshop/deploy-infra.js +2 -3
- package/dist/src/commands/workshop/deploy-infra.js.map +1 -1
- package/dist/src/commands/workshop/generate-demo-data.d.ts.map +1 -1
- package/dist/src/commands/workshop/generate-demo-data.js +3 -8
- package/dist/src/commands/workshop/generate-demo-data.js.map +1 -1
- package/dist/src/commands/workshop/perform-pen-test.d.ts.map +1 -1
- package/dist/src/commands/workshop/perform-pen-test.js +5 -14
- package/dist/src/commands/workshop/perform-pen-test.js.map +1 -1
- package/dist/src/utils/root.d.ts +6 -0
- package/dist/src/utils/root.d.ts.map +1 -1
- package/dist/src/utils/root.js +29 -0
- package/dist/src/utils/root.js.map +1 -1
- 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
|
+
}
|