@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.
- 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,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
|
+
}
|