@cdot65/prisma-airs 0.2.2 → 0.2.4
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/README.md +40 -28
- package/hooks/prisma-airs-audit/handler.ts +10 -3
- package/hooks/prisma-airs-context/handler.ts +56 -3
- package/hooks/prisma-airs-guard/HOOK.md +2 -5
- package/hooks/prisma-airs-guard/handler.ts +5 -0
- package/hooks/prisma-airs-outbound/handler.test.ts +62 -18
- package/hooks/prisma-airs-outbound/handler.ts +24 -1
- package/hooks/prisma-airs-tools/handler.ts +66 -62
- package/index.ts +74 -18
- package/openclaw.plugin.json +23 -6
- package/package.json +1 -1
- package/src/scan-cache.test.ts +6 -2
- package/src/scanner.test.ts +432 -22
- package/src/scanner.ts +351 -20
package/src/scanner.ts
CHANGED
|
@@ -12,6 +12,38 @@ const AIRS_SCAN_ENDPOINT = `${AIRS_API_BASE}/v1/scan/sync/request`;
|
|
|
12
12
|
export type Action = "allow" | "warn" | "block";
|
|
13
13
|
export type Severity = "SAFE" | "LOW" | "MEDIUM" | "HIGH" | "CRITICAL";
|
|
14
14
|
|
|
15
|
+
export interface ToolEventMetadata {
|
|
16
|
+
ecosystem: string;
|
|
17
|
+
method: string;
|
|
18
|
+
serverName: string;
|
|
19
|
+
toolInvoked?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ToolEventInput {
|
|
23
|
+
metadata: ToolEventMetadata;
|
|
24
|
+
input?: string;
|
|
25
|
+
output?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ToolDetectionFlags {
|
|
29
|
+
injection?: boolean;
|
|
30
|
+
urlCats?: boolean;
|
|
31
|
+
dlp?: boolean;
|
|
32
|
+
dbSecurity?: boolean;
|
|
33
|
+
toxicContent?: boolean;
|
|
34
|
+
maliciousCode?: boolean;
|
|
35
|
+
agent?: boolean;
|
|
36
|
+
topicViolation?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface ToolDetected {
|
|
40
|
+
verdict: string;
|
|
41
|
+
metadata: ToolEventMetadata;
|
|
42
|
+
summary: string;
|
|
43
|
+
inputDetected?: ToolDetectionFlags;
|
|
44
|
+
outputDetected?: ToolDetectionFlags;
|
|
45
|
+
}
|
|
46
|
+
|
|
15
47
|
export interface ScanRequest {
|
|
16
48
|
prompt?: string;
|
|
17
49
|
response?: string;
|
|
@@ -21,17 +53,57 @@ export interface ScanRequest {
|
|
|
21
53
|
appName?: string;
|
|
22
54
|
appUser?: string;
|
|
23
55
|
aiModel?: string;
|
|
56
|
+
apiKey?: string;
|
|
57
|
+
toolEvents?: ToolEventInput[];
|
|
24
58
|
}
|
|
25
59
|
|
|
26
60
|
export interface PromptDetected {
|
|
27
61
|
injection: boolean;
|
|
28
62
|
dlp: boolean;
|
|
29
63
|
urlCats: boolean;
|
|
64
|
+
toxicContent: boolean;
|
|
65
|
+
maliciousCode: boolean;
|
|
66
|
+
agent: boolean;
|
|
67
|
+
topicViolation: boolean;
|
|
30
68
|
}
|
|
31
69
|
|
|
32
70
|
export interface ResponseDetected {
|
|
33
71
|
dlp: boolean;
|
|
34
72
|
urlCats: boolean;
|
|
73
|
+
dbSecurity: boolean;
|
|
74
|
+
toxicContent: boolean;
|
|
75
|
+
maliciousCode: boolean;
|
|
76
|
+
agent: boolean;
|
|
77
|
+
ungrounded: boolean;
|
|
78
|
+
topicViolation: boolean;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface TopicGuardrails {
|
|
82
|
+
allowedTopics: string[];
|
|
83
|
+
blockedTopics: string[];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface DetectionDetails {
|
|
87
|
+
topicGuardrailsDetails?: TopicGuardrails;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface PatternDetection {
|
|
91
|
+
pattern: string;
|
|
92
|
+
locations: number[][];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface MaskedData {
|
|
96
|
+
data?: string;
|
|
97
|
+
patternDetections: PatternDetection[];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export type ContentErrorType = "prompt" | "response";
|
|
101
|
+
export type ErrorStatus = "error" | "timeout";
|
|
102
|
+
|
|
103
|
+
export interface ContentError {
|
|
104
|
+
contentType: ContentErrorType;
|
|
105
|
+
feature: string;
|
|
106
|
+
status: ErrorStatus;
|
|
35
107
|
}
|
|
36
108
|
|
|
37
109
|
export interface ScanResult {
|
|
@@ -47,12 +119,63 @@ export interface ScanResult {
|
|
|
47
119
|
trId?: string;
|
|
48
120
|
latencyMs: number;
|
|
49
121
|
error?: string;
|
|
122
|
+
promptDetectionDetails?: DetectionDetails;
|
|
123
|
+
responseDetectionDetails?: DetectionDetails;
|
|
124
|
+
promptMaskedData?: MaskedData;
|
|
125
|
+
responseMaskedData?: MaskedData;
|
|
126
|
+
timeout: boolean;
|
|
127
|
+
hasError: boolean;
|
|
128
|
+
contentErrors: ContentError[];
|
|
129
|
+
toolDetected?: ToolDetected;
|
|
130
|
+
source?: string;
|
|
131
|
+
profileId?: string;
|
|
132
|
+
createdAt?: string;
|
|
133
|
+
completedAt?: string;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Default prompt detection flags (all false) */
|
|
137
|
+
export function defaultPromptDetected(): PromptDetected {
|
|
138
|
+
return {
|
|
139
|
+
injection: false,
|
|
140
|
+
dlp: false,
|
|
141
|
+
urlCats: false,
|
|
142
|
+
toxicContent: false,
|
|
143
|
+
maliciousCode: false,
|
|
144
|
+
agent: false,
|
|
145
|
+
topicViolation: false,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Default response detection flags (all false) */
|
|
150
|
+
export function defaultResponseDetected(): ResponseDetected {
|
|
151
|
+
return {
|
|
152
|
+
dlp: false,
|
|
153
|
+
urlCats: false,
|
|
154
|
+
dbSecurity: false,
|
|
155
|
+
toxicContent: false,
|
|
156
|
+
maliciousCode: false,
|
|
157
|
+
agent: false,
|
|
158
|
+
ungrounded: false,
|
|
159
|
+
topicViolation: false,
|
|
160
|
+
};
|
|
50
161
|
}
|
|
51
162
|
|
|
52
163
|
// AIRS API request/response types (per OpenAPI spec)
|
|
53
164
|
interface AIRSContentItem {
|
|
54
165
|
prompt?: string;
|
|
55
166
|
response?: string;
|
|
167
|
+
tool_calls?: AIRSToolEvent[];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
interface AIRSToolEvent {
|
|
171
|
+
metadata: {
|
|
172
|
+
ecosystem: string;
|
|
173
|
+
method: string;
|
|
174
|
+
server_name: string;
|
|
175
|
+
tool_invoked?: string;
|
|
176
|
+
};
|
|
177
|
+
input?: string;
|
|
178
|
+
output?: string;
|
|
56
179
|
}
|
|
57
180
|
|
|
58
181
|
interface AIRSRequest {
|
|
@@ -74,11 +197,70 @@ interface AIRSPromptDetected {
|
|
|
74
197
|
injection?: boolean;
|
|
75
198
|
dlp?: boolean;
|
|
76
199
|
url_cats?: boolean;
|
|
200
|
+
toxic_content?: boolean;
|
|
201
|
+
malicious_code?: boolean;
|
|
202
|
+
agent?: boolean;
|
|
203
|
+
topic_violation?: boolean;
|
|
77
204
|
}
|
|
78
205
|
|
|
79
206
|
interface AIRSResponseDetected {
|
|
80
207
|
dlp?: boolean;
|
|
81
208
|
url_cats?: boolean;
|
|
209
|
+
db_security?: boolean;
|
|
210
|
+
toxic_content?: boolean;
|
|
211
|
+
malicious_code?: boolean;
|
|
212
|
+
agent?: boolean;
|
|
213
|
+
ungrounded?: boolean;
|
|
214
|
+
topic_violation?: boolean;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
interface AIRSTopicGuardrails {
|
|
218
|
+
allowed_topics?: string[];
|
|
219
|
+
blocked_topics?: string[];
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
interface AIRSDetectionDetails {
|
|
223
|
+
topic_guardrails_details?: AIRSTopicGuardrails;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
interface AIRSPatternDetection {
|
|
227
|
+
pattern?: string;
|
|
228
|
+
locations?: number[][];
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
interface AIRSMaskedData {
|
|
232
|
+
data?: string;
|
|
233
|
+
pattern_detections?: AIRSPatternDetection[];
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
interface AIRSContentError {
|
|
237
|
+
content_type?: string;
|
|
238
|
+
feature?: string;
|
|
239
|
+
status?: string;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
interface AIRSToolDetectionFlags {
|
|
243
|
+
injection?: boolean;
|
|
244
|
+
url_cats?: boolean;
|
|
245
|
+
dlp?: boolean;
|
|
246
|
+
db_security?: boolean;
|
|
247
|
+
toxic_content?: boolean;
|
|
248
|
+
malicious_code?: boolean;
|
|
249
|
+
agent?: boolean;
|
|
250
|
+
topic_violation?: boolean;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
interface AIRSToolDetected {
|
|
254
|
+
verdict?: string;
|
|
255
|
+
metadata?: {
|
|
256
|
+
ecosystem?: string;
|
|
257
|
+
method?: string;
|
|
258
|
+
server_name?: string;
|
|
259
|
+
tool_invoked?: string;
|
|
260
|
+
};
|
|
261
|
+
summary?: string;
|
|
262
|
+
input_detected?: AIRSToolDetectionFlags;
|
|
263
|
+
output_detected?: AIRSToolDetectionFlags;
|
|
82
264
|
}
|
|
83
265
|
|
|
84
266
|
interface AIRSResponse {
|
|
@@ -89,16 +271,27 @@ interface AIRSResponse {
|
|
|
89
271
|
action?: string;
|
|
90
272
|
prompt_detected?: AIRSPromptDetected;
|
|
91
273
|
response_detected?: AIRSResponseDetected;
|
|
274
|
+
prompt_detection_details?: AIRSDetectionDetails;
|
|
275
|
+
response_detection_details?: AIRSDetectionDetails;
|
|
276
|
+
prompt_masked_data?: AIRSMaskedData;
|
|
277
|
+
response_masked_data?: AIRSMaskedData;
|
|
92
278
|
tr_id?: string;
|
|
279
|
+
timeout?: boolean;
|
|
280
|
+
error?: boolean;
|
|
281
|
+
errors?: AIRSContentError[];
|
|
282
|
+
tool_detected?: AIRSToolDetected;
|
|
283
|
+
source?: string;
|
|
284
|
+
profile_id?: string;
|
|
285
|
+
created_at?: string;
|
|
286
|
+
completed_at?: string;
|
|
93
287
|
}
|
|
94
288
|
|
|
95
289
|
/**
|
|
96
290
|
* Scan content through Prisma AIRS API
|
|
97
291
|
*/
|
|
98
292
|
export async function scan(request: ScanRequest): Promise<ScanResult> {
|
|
99
|
-
const apiKey =
|
|
100
|
-
|
|
101
|
-
const profileName = request.profileName ?? process.env.PANW_AI_SEC_PROFILE_NAME ?? "default";
|
|
293
|
+
const apiKey = request.apiKey;
|
|
294
|
+
const profileName = request.profileName ?? "default";
|
|
102
295
|
|
|
103
296
|
if (!apiKey) {
|
|
104
297
|
return {
|
|
@@ -108,10 +301,13 @@ export async function scan(request: ScanRequest): Promise<ScanResult> {
|
|
|
108
301
|
scanId: "",
|
|
109
302
|
reportId: "",
|
|
110
303
|
profileName,
|
|
111
|
-
promptDetected:
|
|
112
|
-
responseDetected:
|
|
304
|
+
promptDetected: defaultPromptDetected(),
|
|
305
|
+
responseDetected: defaultResponseDetected(),
|
|
113
306
|
latencyMs: 0,
|
|
114
|
-
|
|
307
|
+
timeout: false,
|
|
308
|
+
hasError: false,
|
|
309
|
+
contentErrors: [],
|
|
310
|
+
error: "API key not configured. Set it in plugin config.",
|
|
115
311
|
};
|
|
116
312
|
}
|
|
117
313
|
|
|
@@ -122,6 +318,20 @@ export async function scan(request: ScanRequest): Promise<ScanResult> {
|
|
|
122
318
|
if (request.prompt) contentItem.prompt = request.prompt;
|
|
123
319
|
if (request.response) contentItem.response = request.response;
|
|
124
320
|
|
|
321
|
+
// Map tool events into contents
|
|
322
|
+
if (request.toolEvents && request.toolEvents.length > 0) {
|
|
323
|
+
contentItem.tool_calls = request.toolEvents.map((te) => ({
|
|
324
|
+
metadata: {
|
|
325
|
+
ecosystem: te.metadata.ecosystem,
|
|
326
|
+
method: te.metadata.method,
|
|
327
|
+
server_name: te.metadata.serverName,
|
|
328
|
+
...(te.metadata.toolInvoked ? { tool_invoked: te.metadata.toolInvoked } : {}),
|
|
329
|
+
},
|
|
330
|
+
...(te.input ? { input: te.input } : {}),
|
|
331
|
+
...(te.output ? { output: te.output } : {}),
|
|
332
|
+
}));
|
|
333
|
+
}
|
|
334
|
+
|
|
125
335
|
// Build request body (per OpenAPI spec)
|
|
126
336
|
const body: AIRSRequest = {
|
|
127
337
|
ai_profile: {
|
|
@@ -164,9 +374,12 @@ export async function scan(request: ScanRequest): Promise<ScanResult> {
|
|
|
164
374
|
scanId: "",
|
|
165
375
|
reportId: "",
|
|
166
376
|
profileName,
|
|
167
|
-
promptDetected:
|
|
168
|
-
responseDetected:
|
|
377
|
+
promptDetected: defaultPromptDetected(),
|
|
378
|
+
responseDetected: defaultResponseDetected(),
|
|
169
379
|
latencyMs,
|
|
380
|
+
timeout: false,
|
|
381
|
+
hasError: true,
|
|
382
|
+
contentErrors: [],
|
|
170
383
|
error: `API error ${resp.status}: ${errorText}`,
|
|
171
384
|
};
|
|
172
385
|
}
|
|
@@ -182,9 +395,12 @@ export async function scan(request: ScanRequest): Promise<ScanResult> {
|
|
|
182
395
|
scanId: "",
|
|
183
396
|
reportId: "",
|
|
184
397
|
profileName,
|
|
185
|
-
promptDetected:
|
|
186
|
-
responseDetected:
|
|
398
|
+
promptDetected: defaultPromptDetected(),
|
|
399
|
+
responseDetected: defaultResponseDetected(),
|
|
187
400
|
latencyMs,
|
|
401
|
+
timeout: false,
|
|
402
|
+
hasError: true,
|
|
403
|
+
contentErrors: [],
|
|
188
404
|
error: err instanceof Error ? err.message : String(err),
|
|
189
405
|
};
|
|
190
406
|
}
|
|
@@ -210,38 +426,56 @@ function parseResponse(
|
|
|
210
426
|
injection: data.prompt_detected?.injection ?? false,
|
|
211
427
|
dlp: data.prompt_detected?.dlp ?? false,
|
|
212
428
|
urlCats: data.prompt_detected?.url_cats ?? false,
|
|
429
|
+
toxicContent: data.prompt_detected?.toxic_content ?? false,
|
|
430
|
+
maliciousCode: data.prompt_detected?.malicious_code ?? false,
|
|
431
|
+
agent: data.prompt_detected?.agent ?? false,
|
|
432
|
+
topicViolation: data.prompt_detected?.topic_violation ?? false,
|
|
213
433
|
};
|
|
214
434
|
|
|
215
435
|
const responseDetected: ResponseDetected = {
|
|
216
436
|
dlp: data.response_detected?.dlp ?? false,
|
|
217
437
|
urlCats: data.response_detected?.url_cats ?? false,
|
|
438
|
+
dbSecurity: data.response_detected?.db_security ?? false,
|
|
439
|
+
toxicContent: data.response_detected?.toxic_content ?? false,
|
|
440
|
+
maliciousCode: data.response_detected?.malicious_code ?? false,
|
|
441
|
+
agent: data.response_detected?.agent ?? false,
|
|
442
|
+
ungrounded: data.response_detected?.ungrounded ?? false,
|
|
443
|
+
topicViolation: data.response_detected?.topic_violation ?? false,
|
|
218
444
|
};
|
|
219
445
|
|
|
220
446
|
// Build categories list
|
|
221
447
|
const categories: string[] = [];
|
|
448
|
+
// Prompt detections
|
|
222
449
|
if (promptDetected.injection) categories.push("prompt_injection");
|
|
223
450
|
if (promptDetected.dlp) categories.push("dlp_prompt");
|
|
224
451
|
if (promptDetected.urlCats) categories.push("url_filtering_prompt");
|
|
452
|
+
if (promptDetected.toxicContent) categories.push("toxic_content_prompt");
|
|
453
|
+
if (promptDetected.maliciousCode) categories.push("malicious_code_prompt");
|
|
454
|
+
if (promptDetected.agent) categories.push("agent_threat_prompt");
|
|
455
|
+
if (promptDetected.topicViolation) categories.push("topic_violation_prompt");
|
|
456
|
+
// Response detections
|
|
225
457
|
if (responseDetected.dlp) categories.push("dlp_response");
|
|
226
458
|
if (responseDetected.urlCats) categories.push("url_filtering_response");
|
|
459
|
+
if (responseDetected.dbSecurity) categories.push("db_security_response");
|
|
460
|
+
if (responseDetected.toxicContent) categories.push("toxic_content_response");
|
|
461
|
+
if (responseDetected.maliciousCode) categories.push("malicious_code_response");
|
|
462
|
+
if (responseDetected.agent) categories.push("agent_threat_response");
|
|
463
|
+
if (responseDetected.ungrounded) categories.push("ungrounded_response");
|
|
464
|
+
if (responseDetected.topicViolation) categories.push("topic_violation_response");
|
|
227
465
|
|
|
228
466
|
if (categories.length === 0) {
|
|
229
467
|
categories.push(category === "benign" ? "safe" : category);
|
|
230
468
|
}
|
|
231
469
|
|
|
232
470
|
// Determine severity
|
|
471
|
+
const anyDetected =
|
|
472
|
+
Object.values(promptDetected).some(Boolean) || Object.values(responseDetected).some(Boolean);
|
|
233
473
|
let severity: Severity;
|
|
234
474
|
if (category === "malicious" || actionStr === "block") {
|
|
235
475
|
severity = "CRITICAL";
|
|
236
476
|
} else if (category === "suspicious") {
|
|
237
477
|
severity = "HIGH";
|
|
238
|
-
} else if (
|
|
239
|
-
promptDetected.injection ||
|
|
240
|
-
promptDetected.dlp ||
|
|
241
|
-
promptDetected.urlCats ||
|
|
242
|
-
responseDetected.dlp ||
|
|
243
|
-
responseDetected.urlCats
|
|
244
|
-
) {
|
|
478
|
+
} else if (anyDetected) {
|
|
245
479
|
severity = "MEDIUM";
|
|
246
480
|
} else {
|
|
247
481
|
severity = "SAFE";
|
|
@@ -257,7 +491,28 @@ function parseResponse(
|
|
|
257
491
|
action = "allow";
|
|
258
492
|
}
|
|
259
493
|
|
|
260
|
-
|
|
494
|
+
// Extract detection details (optional)
|
|
495
|
+
const promptDetectionDetails = parseDetectionDetails(data.prompt_detection_details);
|
|
496
|
+
const responseDetectionDetails = parseDetectionDetails(data.response_detection_details);
|
|
497
|
+
|
|
498
|
+
// Extract masked data (optional)
|
|
499
|
+
const promptMaskedData = parseMaskedData(data.prompt_masked_data);
|
|
500
|
+
const responseMaskedData = parseMaskedData(data.response_masked_data);
|
|
501
|
+
|
|
502
|
+
// Extract timeout/error info
|
|
503
|
+
const isTimeout = data.timeout === true;
|
|
504
|
+
const hasError = data.error === true;
|
|
505
|
+
const contentErrors: ContentError[] = (data.errors ?? []).map((e) => ({
|
|
506
|
+
contentType: (e.content_type === "prompt" ? "prompt" : "response") as ContentErrorType,
|
|
507
|
+
feature: e.feature ?? "",
|
|
508
|
+
status: (e.status === "timeout" ? "timeout" : "error") as ErrorStatus,
|
|
509
|
+
}));
|
|
510
|
+
|
|
511
|
+
if (isTimeout && !categories.includes("partial_scan")) {
|
|
512
|
+
categories.push("partial_scan");
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
const result: ScanResult = {
|
|
261
516
|
action,
|
|
262
517
|
severity,
|
|
263
518
|
categories,
|
|
@@ -269,12 +524,88 @@ function parseResponse(
|
|
|
269
524
|
sessionId: request.sessionId,
|
|
270
525
|
trId: data.tr_id ?? request.trId,
|
|
271
526
|
latencyMs,
|
|
527
|
+
timeout: isTimeout,
|
|
528
|
+
hasError,
|
|
529
|
+
contentErrors,
|
|
530
|
+
};
|
|
531
|
+
|
|
532
|
+
if (promptDetectionDetails) result.promptDetectionDetails = promptDetectionDetails;
|
|
533
|
+
if (responseDetectionDetails) result.responseDetectionDetails = responseDetectionDetails;
|
|
534
|
+
if (promptMaskedData) result.promptMaskedData = promptMaskedData;
|
|
535
|
+
if (responseMaskedData) result.responseMaskedData = responseMaskedData;
|
|
536
|
+
|
|
537
|
+
// Extract tool detection (optional)
|
|
538
|
+
const toolDetected = parseToolDetected(data.tool_detected);
|
|
539
|
+
if (toolDetected) result.toolDetected = toolDetected;
|
|
540
|
+
|
|
541
|
+
// Extract timestamps and metadata (optional)
|
|
542
|
+
if (data.source) result.source = data.source;
|
|
543
|
+
if (data.profile_id) result.profileId = data.profile_id;
|
|
544
|
+
if (data.created_at) result.createdAt = data.created_at;
|
|
545
|
+
if (data.completed_at) result.completedAt = data.completed_at;
|
|
546
|
+
|
|
547
|
+
return result;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function parseDetectionDetails(raw?: AIRSDetectionDetails): DetectionDetails | undefined {
|
|
551
|
+
if (!raw) return undefined;
|
|
552
|
+
const details: DetectionDetails = {};
|
|
553
|
+
if (raw.topic_guardrails_details) {
|
|
554
|
+
details.topicGuardrailsDetails = {
|
|
555
|
+
allowedTopics: raw.topic_guardrails_details.allowed_topics ?? [],
|
|
556
|
+
blockedTopics: raw.topic_guardrails_details.blocked_topics ?? [],
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
return Object.keys(details).length > 0 ? details : undefined;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function parseMaskedData(raw?: AIRSMaskedData): MaskedData | undefined {
|
|
563
|
+
if (!raw) return undefined;
|
|
564
|
+
return {
|
|
565
|
+
data: raw.data,
|
|
566
|
+
patternDetections: (raw.pattern_detections ?? []).map((p) => ({
|
|
567
|
+
pattern: p.pattern ?? "",
|
|
568
|
+
locations: p.locations ?? [],
|
|
569
|
+
})),
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
function parseToolDetectionFlags(raw?: AIRSToolDetectionFlags): ToolDetectionFlags | undefined {
|
|
574
|
+
if (!raw) return undefined;
|
|
575
|
+
const flags: ToolDetectionFlags = {};
|
|
576
|
+
if (raw.injection != null) flags.injection = raw.injection;
|
|
577
|
+
if (raw.url_cats != null) flags.urlCats = raw.url_cats;
|
|
578
|
+
if (raw.dlp != null) flags.dlp = raw.dlp;
|
|
579
|
+
if (raw.db_security != null) flags.dbSecurity = raw.db_security;
|
|
580
|
+
if (raw.toxic_content != null) flags.toxicContent = raw.toxic_content;
|
|
581
|
+
if (raw.malicious_code != null) flags.maliciousCode = raw.malicious_code;
|
|
582
|
+
if (raw.agent != null) flags.agent = raw.agent;
|
|
583
|
+
if (raw.topic_violation != null) flags.topicViolation = raw.topic_violation;
|
|
584
|
+
return Object.keys(flags).length > 0 ? flags : undefined;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
function parseToolDetected(raw?: AIRSToolDetected): ToolDetected | undefined {
|
|
588
|
+
if (!raw || !raw.metadata) return undefined;
|
|
589
|
+
const result: ToolDetected = {
|
|
590
|
+
verdict: raw.verdict ?? "",
|
|
591
|
+
metadata: {
|
|
592
|
+
ecosystem: raw.metadata.ecosystem ?? "",
|
|
593
|
+
method: raw.metadata.method ?? "",
|
|
594
|
+
serverName: raw.metadata.server_name ?? "",
|
|
595
|
+
toolInvoked: raw.metadata.tool_invoked,
|
|
596
|
+
},
|
|
597
|
+
summary: raw.summary ?? "",
|
|
272
598
|
};
|
|
599
|
+
const inputDetected = parseToolDetectionFlags(raw.input_detected);
|
|
600
|
+
const outputDetected = parseToolDetectionFlags(raw.output_detected);
|
|
601
|
+
if (inputDetected) result.inputDetected = inputDetected;
|
|
602
|
+
if (outputDetected) result.outputDetected = outputDetected;
|
|
603
|
+
return result;
|
|
273
604
|
}
|
|
274
605
|
|
|
275
606
|
/**
|
|
276
607
|
* Check if API key is configured
|
|
277
608
|
*/
|
|
278
|
-
export function isConfigured(): boolean {
|
|
279
|
-
return !!
|
|
609
|
+
export function isConfigured(apiKey?: string): boolean {
|
|
610
|
+
return !!apiKey;
|
|
280
611
|
}
|