@cdot65/prisma-airs-cli 3.0.1 → 3.2.0
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 +3 -1
- package/dist/{chunk-JXHYQFEK.js → chunk-TTBN7YHC.js} +558 -39
- package/dist/cli/index.js +984 -68
- package/dist/index.d.ts +264 -13
- package/dist/index.js +3 -1
- package/package.json +2 -2
|
@@ -456,6 +456,51 @@ function normalizeFile(raw) {
|
|
|
456
456
|
result: raw.result
|
|
457
457
|
};
|
|
458
458
|
}
|
|
459
|
+
function normalizeModel(raw) {
|
|
460
|
+
return {
|
|
461
|
+
uuid: raw.uuid,
|
|
462
|
+
tsgId: raw.tsg_id,
|
|
463
|
+
name: raw.name,
|
|
464
|
+
createdAt: raw.created_at,
|
|
465
|
+
updatedAt: raw.updated_at,
|
|
466
|
+
latestVersionUuid: raw.latest_version_uuid,
|
|
467
|
+
latestVersionFingerprint: raw.latest_version_fingerprint,
|
|
468
|
+
latestVersionRevision: raw.latest_version_revision,
|
|
469
|
+
latestVersionHfCommitSha: raw.latest_version_hf_commit_sha,
|
|
470
|
+
latestVersionOutcome: raw.latest_version_outcome,
|
|
471
|
+
latestVersionFormats: raw.latest_version_formats,
|
|
472
|
+
latestVersionSourceTypes: raw.latest_version_source_types,
|
|
473
|
+
latestVersionScanTime: raw.latest_version_scan_time
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
function normalizeModelVersion(raw) {
|
|
477
|
+
const summary = raw.last_eval_summary;
|
|
478
|
+
return {
|
|
479
|
+
uuid: raw.uuid,
|
|
480
|
+
tsgId: raw.tsg_id,
|
|
481
|
+
modelUuid: raw.model_uuid,
|
|
482
|
+
revision: raw.revision,
|
|
483
|
+
createdAt: raw.created_at,
|
|
484
|
+
updatedAt: raw.updated_at,
|
|
485
|
+
fingerprint: raw.fingerprint,
|
|
486
|
+
fileCount: raw.file_count,
|
|
487
|
+
license: raw.license,
|
|
488
|
+
latestScanTime: raw.latest_scan_time,
|
|
489
|
+
hfCommitSha: raw.hf_commit_sha,
|
|
490
|
+
hfCommitTitle: raw.hf_commit_title,
|
|
491
|
+
hfCommitAuthors: raw.hf_commit_authors,
|
|
492
|
+
hfModelName: raw.hf_model_name,
|
|
493
|
+
hfOrganization: raw.hf_organization,
|
|
494
|
+
modelFormats: raw.model_formats,
|
|
495
|
+
sourceTypes: raw.source_types,
|
|
496
|
+
lastEvalOutcome: raw.last_eval_outcome,
|
|
497
|
+
lastEvalSummary: summary ? {
|
|
498
|
+
rulesFailed: summary.rules_failed ?? 0,
|
|
499
|
+
rulesPassed: summary.rules_passed ?? 0,
|
|
500
|
+
totalRules: summary.total_rules ?? 0
|
|
501
|
+
} : summary
|
|
502
|
+
};
|
|
503
|
+
}
|
|
459
504
|
var SdkModelSecurityService = class {
|
|
460
505
|
client;
|
|
461
506
|
constructor(opts) {
|
|
@@ -680,6 +725,58 @@ var SdkModelSecurityService = class {
|
|
|
680
725
|
expiresAt: raw.expires_at
|
|
681
726
|
};
|
|
682
727
|
}
|
|
728
|
+
// -----------------------------------------------------------------------
|
|
729
|
+
// Models (read-only catalog)
|
|
730
|
+
// -----------------------------------------------------------------------
|
|
731
|
+
async listModels(opts) {
|
|
732
|
+
const sdkOpts = {};
|
|
733
|
+
if (opts?.search) sdkOpts.search = opts.search;
|
|
734
|
+
if (opts?.searchQuery) sdkOpts.search_query = opts.searchQuery;
|
|
735
|
+
if (opts?.sortField) sdkOpts.sort_field = opts.sortField;
|
|
736
|
+
if (opts?.sortOrder) sdkOpts.sort_order = opts.sortOrder;
|
|
737
|
+
if (opts?.skip !== void 0) sdkOpts.skip = opts.skip;
|
|
738
|
+
if (opts?.limit !== void 0) sdkOpts.limit = opts.limit;
|
|
739
|
+
const response = await this.client.models.listModels(sdkOpts);
|
|
740
|
+
const raw = response;
|
|
741
|
+
return {
|
|
742
|
+
totalItems: raw.pagination.total_items ?? 0,
|
|
743
|
+
models: raw.models.map(normalizeModel)
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
async getModel(uuid) {
|
|
747
|
+
const response = await this.client.models.getModel(uuid);
|
|
748
|
+
return normalizeModel(response);
|
|
749
|
+
}
|
|
750
|
+
async listModelVersions(modelUuid, opts) {
|
|
751
|
+
const sdkOpts = {};
|
|
752
|
+
if (opts?.sortOrder) sdkOpts.sort_order = opts.sortOrder;
|
|
753
|
+
if (opts?.skip !== void 0) sdkOpts.skip = opts.skip;
|
|
754
|
+
if (opts?.limit !== void 0) sdkOpts.limit = opts.limit;
|
|
755
|
+
const response = await this.client.models.listModelVersions(modelUuid, sdkOpts);
|
|
756
|
+
const raw = response;
|
|
757
|
+
return {
|
|
758
|
+
totalItems: raw.pagination.total_items ?? 0,
|
|
759
|
+
versions: raw.model_versions.map(normalizeModelVersion)
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
async getModelVersion(uuid) {
|
|
763
|
+
const response = await this.client.models.getModelVersion(uuid);
|
|
764
|
+
return normalizeModelVersion(response);
|
|
765
|
+
}
|
|
766
|
+
async listModelVersionFiles(modelVersionUuid, opts) {
|
|
767
|
+
const sdkOpts = {};
|
|
768
|
+
if (opts?.skip !== void 0) sdkOpts.skip = opts.skip;
|
|
769
|
+
if (opts?.limit !== void 0) sdkOpts.limit = opts.limit;
|
|
770
|
+
const response = await this.client.models.listModelVersionFiles(
|
|
771
|
+
modelVersionUuid,
|
|
772
|
+
sdkOpts
|
|
773
|
+
);
|
|
774
|
+
const raw = response;
|
|
775
|
+
return {
|
|
776
|
+
totalItems: raw.pagination.total_items ?? 0,
|
|
777
|
+
files: raw.files.map(normalizeFile)
|
|
778
|
+
};
|
|
779
|
+
}
|
|
683
780
|
};
|
|
684
781
|
|
|
685
782
|
// src/airs/promptsets.ts
|
|
@@ -862,6 +959,37 @@ function sanitizeTargetMetadata(metadata) {
|
|
|
862
959
|
}
|
|
863
960
|
return metadata;
|
|
864
961
|
}
|
|
962
|
+
function normalizeChannel(raw) {
|
|
963
|
+
return {
|
|
964
|
+
uuid: raw.uuid,
|
|
965
|
+
name: raw.name,
|
|
966
|
+
description: raw.description,
|
|
967
|
+
status: raw.status,
|
|
968
|
+
addedBy: raw.added_by,
|
|
969
|
+
createdAt: raw.created_at,
|
|
970
|
+
updatedAt: raw.updated_at,
|
|
971
|
+
lastOnlineAt: raw.last_online_at,
|
|
972
|
+
connectedClientsCount: raw.connected_clients_count,
|
|
973
|
+
outdatedClientsCount: raw.outdated_clients_count,
|
|
974
|
+
features: raw.features
|
|
975
|
+
};
|
|
976
|
+
}
|
|
977
|
+
function normalizeErrorLog(raw) {
|
|
978
|
+
return {
|
|
979
|
+
createdAt: raw.created_at,
|
|
980
|
+
updatedAt: raw.updated_at,
|
|
981
|
+
jobId: raw.job_id,
|
|
982
|
+
targetId: raw.target_id,
|
|
983
|
+
targetVersion: raw.target_version,
|
|
984
|
+
attackId: raw.attack_id,
|
|
985
|
+
errorType: raw.error_type,
|
|
986
|
+
errorSource: raw.error_source,
|
|
987
|
+
errorMessage: raw.error_message,
|
|
988
|
+
targetObject: raw.target_object,
|
|
989
|
+
extraInfo: raw.extra_info,
|
|
990
|
+
version: raw.version
|
|
991
|
+
};
|
|
992
|
+
}
|
|
865
993
|
function normalizeTargetDetail(raw) {
|
|
866
994
|
return {
|
|
867
995
|
uuid: raw.uuid,
|
|
@@ -1210,21 +1338,213 @@ var SdkRedTeamService = class {
|
|
|
1210
1338
|
await delay(intervalMs);
|
|
1211
1339
|
}
|
|
1212
1340
|
}
|
|
1341
|
+
async listChannels(opts) {
|
|
1342
|
+
const sdkOpts = {};
|
|
1343
|
+
if (opts?.limit != null) sdkOpts.limit = opts.limit;
|
|
1344
|
+
if (opts?.offset != null) sdkOpts.skip = opts.offset;
|
|
1345
|
+
if (opts?.search) sdkOpts.search = opts.search;
|
|
1346
|
+
if (opts?.status) sdkOpts.status = opts.status;
|
|
1347
|
+
const raw = await this.client.networkBroker.listChannels(sdkOpts);
|
|
1348
|
+
const pagination = raw.pagination;
|
|
1349
|
+
return {
|
|
1350
|
+
channels: (raw.data ?? []).map(normalizeChannel),
|
|
1351
|
+
totalItems: pagination?.total_items
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1354
|
+
async getChannel(channelId) {
|
|
1355
|
+
const raw = await this.client.networkBroker.getChannel(channelId);
|
|
1356
|
+
return normalizeChannel(raw);
|
|
1357
|
+
}
|
|
1358
|
+
async createChannel(request) {
|
|
1359
|
+
const body = { name: request.name };
|
|
1360
|
+
if (request.description !== void 0) body.description = request.description;
|
|
1361
|
+
const raw = await this.client.networkBroker.createChannel(
|
|
1362
|
+
body
|
|
1363
|
+
);
|
|
1364
|
+
return normalizeChannel(raw);
|
|
1365
|
+
}
|
|
1366
|
+
async updateChannel(channelId, request) {
|
|
1367
|
+
const body = {};
|
|
1368
|
+
if (request.name !== void 0) body.name = request.name;
|
|
1369
|
+
if (request.description !== void 0) body.description = request.description;
|
|
1370
|
+
const raw = await this.client.networkBroker.updateChannel(
|
|
1371
|
+
channelId,
|
|
1372
|
+
body
|
|
1373
|
+
);
|
|
1374
|
+
return normalizeChannel(raw);
|
|
1375
|
+
}
|
|
1376
|
+
async getChannelStats() {
|
|
1377
|
+
const raw = await this.client.networkBroker.getChannelStats();
|
|
1378
|
+
return {
|
|
1379
|
+
serverDomain: raw.network_channels_server_domain,
|
|
1380
|
+
dockerRegistry: raw.docker_registry,
|
|
1381
|
+
helmChart: raw.helm_chart,
|
|
1382
|
+
dockerImage: raw.docker_image,
|
|
1383
|
+
onlineChannels: raw.online_channels,
|
|
1384
|
+
totalChannels: raw.total_channels,
|
|
1385
|
+
clientVersion: raw.client_version
|
|
1386
|
+
};
|
|
1387
|
+
}
|
|
1388
|
+
async getLanguages(management = false) {
|
|
1389
|
+
const raw = await (management ? this.client.getManagementLanguages() : this.client.getLanguages());
|
|
1390
|
+
return {
|
|
1391
|
+
multilingualEnabled: Boolean(raw.multilingual_enabled),
|
|
1392
|
+
supportedJobTypes: raw.supported_job_types ?? [],
|
|
1393
|
+
languages: (raw.languages ?? []).map((l) => ({
|
|
1394
|
+
code: l.code,
|
|
1395
|
+
name: l.name
|
|
1396
|
+
}))
|
|
1397
|
+
};
|
|
1398
|
+
}
|
|
1399
|
+
async getTargetProfileErrorLogs(targetId, opts) {
|
|
1400
|
+
const sdkOpts = {};
|
|
1401
|
+
if (opts?.limit != null) sdkOpts.limit = opts.limit;
|
|
1402
|
+
if (opts?.offset != null) sdkOpts.skip = opts.offset;
|
|
1403
|
+
if (opts?.search) sdkOpts.search = opts.search;
|
|
1404
|
+
const raw = await this.client.getTargetProfileErrorLogs(targetId, sdkOpts);
|
|
1405
|
+
const pagination = raw.pagination;
|
|
1406
|
+
return {
|
|
1407
|
+
logs: (raw.data ?? []).map(normalizeErrorLog),
|
|
1408
|
+
totalItems: pagination?.total_items
|
|
1409
|
+
};
|
|
1410
|
+
}
|
|
1213
1411
|
};
|
|
1214
1412
|
|
|
1215
1413
|
// src/airs/runtime.ts
|
|
1216
|
-
import {
|
|
1217
|
-
|
|
1414
|
+
import {
|
|
1415
|
+
Content,
|
|
1416
|
+
init,
|
|
1417
|
+
MAX_NUMBER_OF_BATCH_SCAN_OBJECTS,
|
|
1418
|
+
Scanner
|
|
1419
|
+
} from "@cdot65/prisma-airs-sdk";
|
|
1420
|
+
var SDK_ASYNC_BATCH_SIZE = MAX_NUMBER_OF_BATCH_SCAN_OBJECTS;
|
|
1218
1421
|
var DEFAULT_POLL_INTERVAL_MS = 5e3;
|
|
1219
1422
|
var DEFAULT_MAX_RETRIES = 5;
|
|
1220
1423
|
var DEFAULT_BASE_DELAY_MS = 1e4;
|
|
1424
|
+
var DEFAULT_MAX_NO_PROGRESS_POLLS = 120;
|
|
1425
|
+
var NO_SDK_RETRIES = { numRetries: 0 };
|
|
1426
|
+
var RUNTIME_DETECTION_KEYS = [
|
|
1427
|
+
"topic_violation",
|
|
1428
|
+
"injection",
|
|
1429
|
+
"toxic_content",
|
|
1430
|
+
"dlp",
|
|
1431
|
+
"url_cats",
|
|
1432
|
+
"malicious_code",
|
|
1433
|
+
"source_code",
|
|
1434
|
+
"agent"
|
|
1435
|
+
];
|
|
1436
|
+
var REPORT_DETECTION_KEYS = {
|
|
1437
|
+
topic_guardrails: "topic_violation",
|
|
1438
|
+
topic_violation: "topic_violation",
|
|
1439
|
+
pi: "injection",
|
|
1440
|
+
prompt_injection: "injection",
|
|
1441
|
+
injection: "injection",
|
|
1442
|
+
tc: "toxic_content",
|
|
1443
|
+
toxic_content: "toxic_content",
|
|
1444
|
+
dlp: "dlp",
|
|
1445
|
+
uf: "url_cats",
|
|
1446
|
+
url_filtering: "url_cats",
|
|
1447
|
+
url_cats: "url_cats",
|
|
1448
|
+
mc: "malicious_code",
|
|
1449
|
+
malicious_code: "malicious_code",
|
|
1450
|
+
source_code: "source_code",
|
|
1451
|
+
agent: "agent"
|
|
1452
|
+
};
|
|
1221
1453
|
function isRateLimitError(err) {
|
|
1454
|
+
if (err?.statusCode === 429) return true;
|
|
1222
1455
|
if (err instanceof Error) {
|
|
1223
1456
|
const msg = err.message.toLowerCase();
|
|
1224
1457
|
return msg.includes("rate limit") || msg.includes("rate_limit") || msg.includes("429");
|
|
1225
1458
|
}
|
|
1226
1459
|
return false;
|
|
1227
1460
|
}
|
|
1461
|
+
function isDefiniteRateLimitError(err) {
|
|
1462
|
+
const metadata = err;
|
|
1463
|
+
return metadata?.failureKind === "http" && metadata.statusCode === 429;
|
|
1464
|
+
}
|
|
1465
|
+
function runtimeDetections(value) {
|
|
1466
|
+
const source = value ?? {};
|
|
1467
|
+
return Object.fromEntries(
|
|
1468
|
+
RUNTIME_DETECTION_KEYS.filter((key) => typeof source[key] === "boolean").map((key) => [
|
|
1469
|
+
key,
|
|
1470
|
+
source[key]
|
|
1471
|
+
])
|
|
1472
|
+
);
|
|
1473
|
+
}
|
|
1474
|
+
function runtimeAction(value) {
|
|
1475
|
+
const normalized = typeof value === "string" ? value.toLowerCase() : "";
|
|
1476
|
+
if (normalized === "allow" || normalized === "block") return normalized;
|
|
1477
|
+
return "failed";
|
|
1478
|
+
}
|
|
1479
|
+
function scanResponseToResult(response, prompt) {
|
|
1480
|
+
const detections = runtimeDetections(response.prompt_detected);
|
|
1481
|
+
const action = runtimeAction(response.action);
|
|
1482
|
+
const failed = response.error === true || response.timeout === true || action === "failed";
|
|
1483
|
+
const errors = Array.isArray(response.errors) ? response.errors.map((entry) => {
|
|
1484
|
+
const detail = entry;
|
|
1485
|
+
return [detail.feature, detail.status, detail.content_type].filter(Boolean).join(": ");
|
|
1486
|
+
}) : [];
|
|
1487
|
+
return {
|
|
1488
|
+
prompt,
|
|
1489
|
+
response: void 0,
|
|
1490
|
+
scanId: response.scan_id ?? "",
|
|
1491
|
+
reportId: response.report_id ?? "",
|
|
1492
|
+
action: failed ? "failed" : action,
|
|
1493
|
+
category: failed ? "error" : response.category ?? "unknown",
|
|
1494
|
+
triggered: RUNTIME_DETECTION_KEYS.some((key) => detections[key] === true),
|
|
1495
|
+
detections,
|
|
1496
|
+
...failed ? {
|
|
1497
|
+
error: errors.filter(Boolean).join("; ") || (response.timeout === true ? "AIRS scan timed out" : response.error === true ? "AIRS scan failed" : `Unknown AIRS action: ${String(response.action ?? "missing")}`)
|
|
1498
|
+
} : {}
|
|
1499
|
+
};
|
|
1500
|
+
}
|
|
1501
|
+
function threatReportToResult(report, entry) {
|
|
1502
|
+
const detections = {};
|
|
1503
|
+
let sawBlock = false;
|
|
1504
|
+
let unexpectedAction;
|
|
1505
|
+
const detectionResults = Array.isArray(report.detection_results) ? report.detection_results : [];
|
|
1506
|
+
for (const detection of detectionResults) {
|
|
1507
|
+
const service = String(detection.detection_service ?? "").toLowerCase();
|
|
1508
|
+
const detectorAction = String(detection.action ?? "").toLowerCase();
|
|
1509
|
+
const verdict = String(detection.verdict ?? "").toLowerCase();
|
|
1510
|
+
const fired = detectorAction === "block" || ["malicious", "unsafe", "violation", "detected"].includes(verdict);
|
|
1511
|
+
const namedKey = REPORT_DETECTION_KEYS[service] ?? service;
|
|
1512
|
+
const key = namedKey || (fired ? "unknown" : "");
|
|
1513
|
+
if (key) detections[key] = detections[key] === true || fired;
|
|
1514
|
+
if (detectorAction === "block") sawBlock = true;
|
|
1515
|
+
else if (detectorAction && detectorAction !== "allow") unexpectedAction = detectorAction;
|
|
1516
|
+
}
|
|
1517
|
+
const triggered = Object.values(detections).some(Boolean);
|
|
1518
|
+
const action = sawBlock ? "block" : unexpectedAction ? "failed" : "allow";
|
|
1519
|
+
return {
|
|
1520
|
+
index: entry.index,
|
|
1521
|
+
reqId: entry.reqId,
|
|
1522
|
+
prompt: entry.prompt,
|
|
1523
|
+
response: void 0,
|
|
1524
|
+
scanId: entry.scanId,
|
|
1525
|
+
reportId: report.report_id ?? "",
|
|
1526
|
+
action,
|
|
1527
|
+
category: action === "failed" ? "error" : triggered ? "malicious" : "benign",
|
|
1528
|
+
triggered,
|
|
1529
|
+
detections,
|
|
1530
|
+
...action === "failed" ? { error: `Unknown AIRS action in threat report: ${unexpectedAction}` } : {}
|
|
1531
|
+
};
|
|
1532
|
+
}
|
|
1533
|
+
function failedBulkResult(entry, error = "AIRS async scan failed") {
|
|
1534
|
+
return {
|
|
1535
|
+
index: entry.index,
|
|
1536
|
+
reqId: entry.reqId,
|
|
1537
|
+
prompt: entry.prompt,
|
|
1538
|
+
response: void 0,
|
|
1539
|
+
scanId: entry.scanId,
|
|
1540
|
+
reportId: "",
|
|
1541
|
+
action: "failed",
|
|
1542
|
+
category: "error",
|
|
1543
|
+
triggered: false,
|
|
1544
|
+
detections: {},
|
|
1545
|
+
error
|
|
1546
|
+
};
|
|
1547
|
+
}
|
|
1228
1548
|
var SdkRuntimeService = class {
|
|
1229
1549
|
scanner;
|
|
1230
1550
|
constructor(opts) {
|
|
@@ -1236,23 +1556,208 @@ var SdkRuntimeService = class {
|
|
|
1236
1556
|
if (response) contentOpts.response = response;
|
|
1237
1557
|
const content = new Content(contentOpts);
|
|
1238
1558
|
const res = await this.scanner.syncScan({ profile_name: profileName }, content, void 0);
|
|
1239
|
-
const
|
|
1240
|
-
const triggered = !!(detected.topic_violation || detected.injection || detected.toxic_content || detected.dlp || detected.url_cats || detected.malicious_code);
|
|
1559
|
+
const normalized = scanResponseToResult(res, prompt);
|
|
1241
1560
|
return {
|
|
1242
|
-
|
|
1561
|
+
...normalized,
|
|
1243
1562
|
response,
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1563
|
+
action: normalized.action === "block" ? "block" : "allow"
|
|
1564
|
+
};
|
|
1565
|
+
}
|
|
1566
|
+
async submitBatch(profileName, prompts, sessionId, retryOpts) {
|
|
1567
|
+
if (prompts.length < 1 || prompts.length > SDK_ASYNC_BATCH_SIZE) {
|
|
1568
|
+
throw new Error(`submitBatch requires between 1 and ${SDK_ASYNC_BATCH_SIZE} prompts`);
|
|
1569
|
+
}
|
|
1570
|
+
const promptIndices = /* @__PURE__ */ new Set();
|
|
1571
|
+
for (const prompt of prompts) {
|
|
1572
|
+
if (!Number.isSafeInteger(prompt.index) || prompt.index < 0 || promptIndices.has(prompt.index)) {
|
|
1573
|
+
throw new Error("submitBatch requires a unique nonnegative safe index for every prompt");
|
|
1574
|
+
}
|
|
1575
|
+
promptIndices.add(prompt.index);
|
|
1576
|
+
}
|
|
1577
|
+
const scanObjects = prompts.map(({ index, prompt }) => ({
|
|
1578
|
+
req_id: index,
|
|
1579
|
+
scan_req: {
|
|
1580
|
+
ai_profile: { profile_name: profileName },
|
|
1581
|
+
contents: [{ prompt }],
|
|
1582
|
+
...sessionId ? { session_id: sessionId } : {}
|
|
1583
|
+
}
|
|
1584
|
+
}));
|
|
1585
|
+
const maxRetries = retryOpts?.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
1586
|
+
const baseDelay = retryOpts?.baseDelayMs ?? DEFAULT_BASE_DELAY_MS;
|
|
1587
|
+
let retryAttempt = 0;
|
|
1588
|
+
let receipt;
|
|
1589
|
+
while (true) {
|
|
1590
|
+
try {
|
|
1591
|
+
receipt = await this.scanner.asyncScan(scanObjects, NO_SDK_RETRIES);
|
|
1592
|
+
break;
|
|
1593
|
+
} catch (error) {
|
|
1594
|
+
if (!isDefiniteRateLimitError(error) || retryAttempt >= maxRetries) throw error;
|
|
1595
|
+
retryAttempt++;
|
|
1596
|
+
const retryAfterMs = error.retryAfterMs;
|
|
1597
|
+
const delayMs = typeof retryAfterMs === "number" && Number.isFinite(retryAfterMs) && retryAfterMs >= 0 ? retryAfterMs : baseDelay * 2 ** (retryAttempt - 1);
|
|
1598
|
+
retryOpts?.onRetry?.(retryAttempt, delayMs);
|
|
1599
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
return {
|
|
1603
|
+
scanId: receipt.scan_id,
|
|
1604
|
+
reportId: receipt.report_id,
|
|
1605
|
+
entries: prompts.map(({ index, prompt }) => ({
|
|
1606
|
+
scanId: receipt.scan_id,
|
|
1607
|
+
reqId: index,
|
|
1608
|
+
index,
|
|
1609
|
+
prompt
|
|
1610
|
+
}))
|
|
1250
1611
|
};
|
|
1251
1612
|
}
|
|
1613
|
+
async pollBatch(batch, intervalMs = DEFAULT_POLL_INTERVAL_MS, retryOpts) {
|
|
1614
|
+
if (batch.entries.length < 1 || batch.entries.length > SDK_ASYNC_BATCH_SIZE) {
|
|
1615
|
+
throw new Error(`pollBatch requires between 1 and ${SDK_ASYNC_BATCH_SIZE} receipt entries`);
|
|
1616
|
+
}
|
|
1617
|
+
const receiptIds = /* @__PURE__ */ new Set();
|
|
1618
|
+
for (const entry of batch.entries) {
|
|
1619
|
+
if (entry.scanId !== batch.scanId) {
|
|
1620
|
+
throw new Error(`Receipt entry scan ID ${entry.scanId} does not match ${batch.scanId}`);
|
|
1621
|
+
}
|
|
1622
|
+
if (!Number.isSafeInteger(entry.reqId) || entry.reqId < 0 || entry.reqId !== entry.index || receiptIds.has(entry.reqId)) {
|
|
1623
|
+
throw new Error("pollBatch requires a unique request ID matching each prompt index");
|
|
1624
|
+
}
|
|
1625
|
+
receiptIds.add(entry.reqId);
|
|
1626
|
+
}
|
|
1627
|
+
const maxRetries = retryOpts?.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
1628
|
+
const baseDelay = retryOpts?.baseDelayMs ?? DEFAULT_BASE_DELAY_MS;
|
|
1629
|
+
const maxNoProgressPolls = retryOpts?.maxNoProgressPolls ?? DEFAULT_MAX_NO_PROGRESS_POLLS;
|
|
1630
|
+
const entries = new Map(batch.entries.map((entry) => [entry.reqId, entry]));
|
|
1631
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
1632
|
+
let retryLevel = 0;
|
|
1633
|
+
let noProgressPolls = 0;
|
|
1634
|
+
while (resolved.size < batch.entries.length) {
|
|
1635
|
+
let rows;
|
|
1636
|
+
try {
|
|
1637
|
+
rows = await this.scanner.queryByScanIds([batch.scanId], NO_SDK_RETRIES);
|
|
1638
|
+
} catch (err) {
|
|
1639
|
+
if (isRateLimitError(err) && retryLevel < maxRetries) {
|
|
1640
|
+
retryLevel++;
|
|
1641
|
+
const retryAfterMs = err.retryAfterMs;
|
|
1642
|
+
const delayMs = typeof retryAfterMs === "number" && Number.isFinite(retryAfterMs) && retryAfterMs >= 0 ? retryAfterMs : baseDelay * 2 ** (retryLevel - 1);
|
|
1643
|
+
retryOpts?.onRetry?.(retryLevel, delayMs);
|
|
1644
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
1645
|
+
continue;
|
|
1646
|
+
}
|
|
1647
|
+
throw err;
|
|
1648
|
+
}
|
|
1649
|
+
const resolvedBeforePoll = resolved.size;
|
|
1650
|
+
const fallbackReports = /* @__PURE__ */ new Map();
|
|
1651
|
+
for (const row of rows) {
|
|
1652
|
+
const scanId = row.scan_id ?? batch.scanId;
|
|
1653
|
+
const reqId = row.req_id;
|
|
1654
|
+
const status = (row.status ?? "").toLowerCase();
|
|
1655
|
+
const terminal = status === "failed" || status === "complete" || status === "completed";
|
|
1656
|
+
if (reqId === void 0 && terminal && scanId === batch.scanId) {
|
|
1657
|
+
if (status === "failed") {
|
|
1658
|
+
const newlyFailed = [];
|
|
1659
|
+
for (const entry2 of batch.entries) {
|
|
1660
|
+
if (!resolved.has(entry2.reqId)) {
|
|
1661
|
+
const result = failedBulkResult(entry2);
|
|
1662
|
+
resolved.set(entry2.reqId, result);
|
|
1663
|
+
newlyFailed.push(result);
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
if (newlyFailed.length > 0) await retryOpts?.onProgress?.(newlyFailed);
|
|
1667
|
+
continue;
|
|
1668
|
+
}
|
|
1669
|
+
const reportId = row.result?.report_id ?? row.report_id ?? batch.reportId;
|
|
1670
|
+
if (!reportId) {
|
|
1671
|
+
throw new Error(
|
|
1672
|
+
`AIRS result correlation failed for scan ${scanId}: terminal row has no request or report ID`
|
|
1673
|
+
);
|
|
1674
|
+
}
|
|
1675
|
+
fallbackReports.set(scanId, reportId);
|
|
1676
|
+
continue;
|
|
1677
|
+
}
|
|
1678
|
+
const entry = reqId === void 0 || scanId !== batch.scanId ? void 0 : entries.get(reqId);
|
|
1679
|
+
if (!entry || resolved.has(entry.reqId)) continue;
|
|
1680
|
+
if (status === "failed") {
|
|
1681
|
+
const result = failedBulkResult(entry);
|
|
1682
|
+
resolved.set(entry.reqId, result);
|
|
1683
|
+
await retryOpts?.onProgress?.([result]);
|
|
1684
|
+
continue;
|
|
1685
|
+
}
|
|
1686
|
+
if ((status === "complete" || status === "completed") && row.result) {
|
|
1687
|
+
const nestedResult = row.result;
|
|
1688
|
+
const nestedScanId = nestedResult.scan_id;
|
|
1689
|
+
if (nestedScanId && nestedScanId !== scanId) {
|
|
1690
|
+
throw new Error(
|
|
1691
|
+
`AIRS result correlation mismatch: nested scan ID ${nestedScanId} does not match ${scanId}`
|
|
1692
|
+
);
|
|
1693
|
+
}
|
|
1694
|
+
const result = {
|
|
1695
|
+
...scanResponseToResult(nestedResult, entry.prompt),
|
|
1696
|
+
scanId,
|
|
1697
|
+
index: entry.index,
|
|
1698
|
+
reqId: entry.reqId
|
|
1699
|
+
};
|
|
1700
|
+
resolved.set(entry.reqId, result);
|
|
1701
|
+
await retryOpts?.onProgress?.([result]);
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
if (fallbackReports.size > 0) {
|
|
1705
|
+
let reports;
|
|
1706
|
+
try {
|
|
1707
|
+
reports = await this.scanner.queryByReportIds(
|
|
1708
|
+
[...new Set(fallbackReports.values())],
|
|
1709
|
+
NO_SDK_RETRIES
|
|
1710
|
+
);
|
|
1711
|
+
} catch (err) {
|
|
1712
|
+
if (isRateLimitError(err) && retryLevel < maxRetries) {
|
|
1713
|
+
retryLevel++;
|
|
1714
|
+
const retryAfterMs = err.retryAfterMs;
|
|
1715
|
+
const delayMs = typeof retryAfterMs === "number" && Number.isFinite(retryAfterMs) && retryAfterMs >= 0 ? retryAfterMs : baseDelay * 2 ** (retryLevel - 1);
|
|
1716
|
+
retryOpts?.onRetry?.(retryLevel, delayMs);
|
|
1717
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
1718
|
+
continue;
|
|
1719
|
+
}
|
|
1720
|
+
throw err;
|
|
1721
|
+
}
|
|
1722
|
+
for (const report of reports) {
|
|
1723
|
+
const reqId = report.req_id;
|
|
1724
|
+
const reportScanId = report.scan_id ?? batch.scanId;
|
|
1725
|
+
const expectedReportId = fallbackReports.get(reportScanId);
|
|
1726
|
+
const reportId = report.report_id;
|
|
1727
|
+
if (reportScanId !== batch.scanId || reportId && reportId !== expectedReportId) {
|
|
1728
|
+
throw new Error(
|
|
1729
|
+
`AIRS report correlation mismatch for scan ${batch.scanId}, request ${String(reqId)}`
|
|
1730
|
+
);
|
|
1731
|
+
}
|
|
1732
|
+
const entry = reqId === void 0 ? void 0 : entries.get(reqId);
|
|
1733
|
+
if (!entry || resolved.has(entry.reqId)) continue;
|
|
1734
|
+
const result = threatReportToResult(report, entry);
|
|
1735
|
+
resolved.set(entry.reqId, result);
|
|
1736
|
+
await retryOpts?.onProgress?.([result]);
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
if (retryLevel > 0) retryLevel--;
|
|
1740
|
+
if (resolved.size === resolvedBeforePoll) {
|
|
1741
|
+
noProgressPolls++;
|
|
1742
|
+
} else {
|
|
1743
|
+
noProgressPolls = 0;
|
|
1744
|
+
}
|
|
1745
|
+
if (noProgressPolls >= maxNoProgressPolls) {
|
|
1746
|
+
throw new Error(
|
|
1747
|
+
`AIRS polling made no progress after ${noProgressPolls} polls for scan ${batch.scanId}`
|
|
1748
|
+
);
|
|
1749
|
+
}
|
|
1750
|
+
if (resolved.size < batch.entries.length) {
|
|
1751
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
return [...resolved.values()].sort((left, right) => left.index - right.index);
|
|
1755
|
+
}
|
|
1756
|
+
/** @deprecated Use submitBatch to preserve per-prompt request correlation. */
|
|
1252
1757
|
async submitBulkScan(profileName, prompts, sessionId) {
|
|
1253
1758
|
const scanIds = [];
|
|
1254
|
-
for (let i = 0; i < prompts.length; i +=
|
|
1255
|
-
const batch = prompts.slice(i, i +
|
|
1759
|
+
for (let i = 0; i < prompts.length; i += SDK_ASYNC_BATCH_SIZE) {
|
|
1760
|
+
const batch = prompts.slice(i, i + SDK_ASYNC_BATCH_SIZE);
|
|
1256
1761
|
const scanObjects = batch.map((prompt, idx) => ({
|
|
1257
1762
|
req_id: i + idx,
|
|
1258
1763
|
scan_req: {
|
|
@@ -1261,18 +1766,16 @@ var SdkRuntimeService = class {
|
|
|
1261
1766
|
...sessionId ? { session_id: sessionId } : {}
|
|
1262
1767
|
}
|
|
1263
1768
|
}));
|
|
1264
|
-
const res = await this.scanner.asyncScan(scanObjects);
|
|
1769
|
+
const res = await this.scanner.asyncScan(scanObjects, NO_SDK_RETRIES);
|
|
1265
1770
|
scanIds.push(res.scan_id);
|
|
1266
1771
|
}
|
|
1267
1772
|
return scanIds;
|
|
1268
1773
|
}
|
|
1269
1774
|
/**
|
|
1270
|
-
*
|
|
1271
|
-
*
|
|
1272
|
-
*
|
|
1273
|
-
*
|
|
1274
|
-
* defaults (`''`, `undefined`, `false`, `{}`) in the returned results.
|
|
1275
|
-
* Use `scanPrompt()` (sync API) when these fields are needed.
|
|
1775
|
+
* Compatibility poller for callers that retained only batch scan IDs.
|
|
1776
|
+
* Nested detection data is preserved, but prompt text and per-request fan-out
|
|
1777
|
+
* cannot be reconstructed from scan IDs alone.
|
|
1778
|
+
* @deprecated Use pollBatch to preserve `(scan_id, req_id)` correlation and prompt text.
|
|
1276
1779
|
*/
|
|
1277
1780
|
async pollResults(scanIds, intervalMs = DEFAULT_POLL_INTERVAL_MS, retryOpts) {
|
|
1278
1781
|
const maxRetries = retryOpts?.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
@@ -1321,19 +1824,16 @@ var SdkRuntimeService = class {
|
|
|
1321
1824
|
const status = (r.status ?? "").toLowerCase();
|
|
1322
1825
|
if ((status === "complete" || status === "completed") && r.result) {
|
|
1323
1826
|
const result = r.result;
|
|
1827
|
+
const nestedScanId = result.scan_id;
|
|
1828
|
+
if (nestedScanId && nestedScanId !== id) {
|
|
1829
|
+
throw new Error(
|
|
1830
|
+
`AIRS result correlation mismatch: nested scan ID ${nestedScanId} does not match ${id}`
|
|
1831
|
+
);
|
|
1832
|
+
}
|
|
1324
1833
|
completed.set(id, {
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
// not available from async API
|
|
1329
|
-
scanId: result.scan_id ?? id,
|
|
1330
|
-
reportId: result.report_id ?? "",
|
|
1331
|
-
action: result.action === "block" ? "block" : "allow",
|
|
1332
|
-
category: result.category ?? "unknown",
|
|
1333
|
-
triggered: false,
|
|
1334
|
-
// not available from async API — always false
|
|
1335
|
-
detections: {}
|
|
1336
|
-
// not available from async API
|
|
1834
|
+
...scanResponseToResult(result, ""),
|
|
1835
|
+
scanId: id,
|
|
1836
|
+
action: result.action === "block" ? "block" : "allow"
|
|
1337
1837
|
});
|
|
1338
1838
|
pending.delete(id);
|
|
1339
1839
|
} else if (status === "failed") {
|
|
@@ -1345,22 +1845,38 @@ var SdkRuntimeService = class {
|
|
|
1345
1845
|
scanId: id,
|
|
1346
1846
|
reportId: "",
|
|
1347
1847
|
action: "allow",
|
|
1348
|
-
// safe default for failed scans
|
|
1349
1848
|
category: "error",
|
|
1350
1849
|
triggered: false,
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
// not available from async API
|
|
1850
|
+
detections: {},
|
|
1851
|
+
error: "AIRS async scan failed"
|
|
1354
1852
|
});
|
|
1355
1853
|
pending.delete(id);
|
|
1356
1854
|
}
|
|
1357
1855
|
}
|
|
1358
1856
|
}
|
|
1359
1857
|
static formatResultsCsv(results) {
|
|
1360
|
-
const header =
|
|
1858
|
+
const header = [
|
|
1859
|
+
"prompt",
|
|
1860
|
+
"action",
|
|
1861
|
+
"category",
|
|
1862
|
+
"triggered",
|
|
1863
|
+
...RUNTIME_DETECTION_KEYS,
|
|
1864
|
+
"scan_id",
|
|
1865
|
+
"report_id",
|
|
1866
|
+
"error"
|
|
1867
|
+
].join(",");
|
|
1361
1868
|
const rows = results.map((r) => {
|
|
1362
|
-
const
|
|
1363
|
-
|
|
1869
|
+
const fields = [
|
|
1870
|
+
r.prompt,
|
|
1871
|
+
r.action,
|
|
1872
|
+
r.category,
|
|
1873
|
+
String(r.triggered),
|
|
1874
|
+
...RUNTIME_DETECTION_KEYS.map((key) => String(r.detections[key] === true)),
|
|
1875
|
+
r.scanId,
|
|
1876
|
+
r.reportId,
|
|
1877
|
+
r.error ?? ""
|
|
1878
|
+
];
|
|
1879
|
+
return fields.map((field) => `"${field.replace(/"/g, '""')}"`).join(",");
|
|
1364
1880
|
});
|
|
1365
1881
|
return [header, ...rows].join("\n");
|
|
1366
1882
|
}
|
|
@@ -1509,6 +2025,7 @@ var ConfigSchema = z.object({
|
|
|
1509
2025
|
redTeamDataEndpoint: z.string().optional(),
|
|
1510
2026
|
redTeamMgmtEndpoint: z.string().optional(),
|
|
1511
2027
|
redTeamTokenEndpoint: z.string().optional(),
|
|
2028
|
+
redTeamNetworkBrokerEndpoint: z.string().optional(),
|
|
1512
2029
|
// Model Security (endpoints only; creds shared with mgmt*)
|
|
1513
2030
|
modelSecDataEndpoint: z.string().optional(),
|
|
1514
2031
|
modelSecMgmtEndpoint: z.string().optional(),
|
|
@@ -1539,6 +2056,7 @@ function fromEnv() {
|
|
|
1539
2056
|
redTeamDataEndpoint: env.PANW_RED_TEAM_DATA_ENDPOINT,
|
|
1540
2057
|
redTeamMgmtEndpoint: env.PANW_RED_TEAM_MGMT_ENDPOINT,
|
|
1541
2058
|
redTeamTokenEndpoint: env.PANW_RED_TEAM_TOKEN_ENDPOINT,
|
|
2059
|
+
redTeamNetworkBrokerEndpoint: env.PANW_RED_TEAM_NETWORK_BROKER_ENDPOINT,
|
|
1542
2060
|
modelSecDataEndpoint: env.PANW_MODEL_SEC_DATA_ENDPOINT,
|
|
1543
2061
|
modelSecMgmtEndpoint: env.PANW_MODEL_SEC_MGMT_ENDPOINT,
|
|
1544
2062
|
modelSecTokenEndpoint: env.PANW_MODEL_SEC_TOKEN_ENDPOINT,
|
|
@@ -1731,6 +2249,7 @@ export {
|
|
|
1731
2249
|
SdkModelSecurityService,
|
|
1732
2250
|
SdkPromptSetService,
|
|
1733
2251
|
SdkRedTeamService,
|
|
2252
|
+
SDK_ASYNC_BATCH_SIZE,
|
|
1734
2253
|
SdkRuntimeService,
|
|
1735
2254
|
AirsScanService,
|
|
1736
2255
|
RateLimitedScanService,
|