@cdot65/prisma-airs-sdk 0.13.0 → 0.13.2
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/index.cjs +141 -34
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +87 -30
- package/dist/index.d.ts +87 -30
- package/dist/index.js +141 -34
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -25,11 +25,11 @@ var MAX_REPORT_ID_STR_LENGTH = 40;
|
|
|
25
25
|
var MAX_AI_PROFILE_NAME_LENGTH = 100;
|
|
26
26
|
var MAX_NUMBER_OF_SCAN_IDS = 5;
|
|
27
27
|
var MAX_NUMBER_OF_REPORT_IDS = 5;
|
|
28
|
-
var MAX_NUMBER_OF_BATCH_SCAN_OBJECTS =
|
|
28
|
+
var MAX_NUMBER_OF_BATCH_SCAN_OBJECTS = 20;
|
|
29
29
|
var MAX_CONNECTION_POOL_SIZE = 100;
|
|
30
30
|
var MAX_NUMBER_OF_RETRIES = 5;
|
|
31
31
|
var HTTP_FORCE_RETRY_STATUS_CODES = [500, 502, 503, 504];
|
|
32
|
-
var SDK_VERSION = "0.13.
|
|
32
|
+
var SDK_VERSION = "0.13.2";
|
|
33
33
|
var USER_AGENT = `PAN-AIRS/${SDK_VERSION}-typescript-sdk`;
|
|
34
34
|
var DEFAULT_MGMT_ENDPOINT = "https://api.sase.paloaltonetworks.com/aisec";
|
|
35
35
|
var DEFAULT_TOKEN_ENDPOINT = "https://auth.apps.paloaltonetworks.com/oauth2/access_token";
|
|
@@ -129,11 +129,15 @@ var AISecSDKException = class _AISecSDKException extends Error {
|
|
|
129
129
|
/**
|
|
130
130
|
* @param message - Human-readable error description.
|
|
131
131
|
* @param errorType - Classification of the error.
|
|
132
|
+
* @param metadata - Optional transport failure metadata.
|
|
132
133
|
*/
|
|
133
|
-
constructor(message, errorType) {
|
|
134
|
+
constructor(message, errorType, metadata = {}) {
|
|
134
135
|
super(errorType ? `${errorType}:${message}` : message);
|
|
135
136
|
this.name = "AISecSDKException";
|
|
136
137
|
this.errorType = errorType;
|
|
138
|
+
if (metadata.failureKind !== void 0) this.failureKind = metadata.failureKind;
|
|
139
|
+
if (metadata.statusCode !== void 0) this.statusCode = metadata.statusCode;
|
|
140
|
+
if (metadata.retryAfterMs !== void 0) this.retryAfterMs = metadata.retryAfterMs;
|
|
137
141
|
if (Error.captureStackTrace) {
|
|
138
142
|
Error.captureStackTrace(this, _AISecSDKException);
|
|
139
143
|
}
|
|
@@ -275,6 +279,59 @@ function extractErrorMessage(body, status) {
|
|
|
275
279
|
return body ? `API error ${status}: ${body}` : `API error ${status}`;
|
|
276
280
|
}
|
|
277
281
|
}
|
|
282
|
+
function parseRetryAfterHeader(value) {
|
|
283
|
+
if (value === null) return void 0;
|
|
284
|
+
const normalized = value.trim();
|
|
285
|
+
if (/^\d+$/.test(normalized)) {
|
|
286
|
+
const milliseconds = Number(normalized) * 1e3;
|
|
287
|
+
return Number.isFinite(milliseconds) ? milliseconds : void 0;
|
|
288
|
+
}
|
|
289
|
+
const weekday = "(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)";
|
|
290
|
+
const longWeekday = "(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)";
|
|
291
|
+
const month = "(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)";
|
|
292
|
+
const httpDatePatterns = [
|
|
293
|
+
new RegExp(`^${weekday}, \\d{2} ${month} \\d{4} \\d{2}:\\d{2}:\\d{2} GMT$`),
|
|
294
|
+
new RegExp(`^${longWeekday}, \\d{2}-${month}-\\d{2} \\d{2}:\\d{2}:\\d{2} GMT$`),
|
|
295
|
+
new RegExp(`^${weekday} ${month} [ \\d]\\d \\d{2}:\\d{2}:\\d{2} \\d{4}$`)
|
|
296
|
+
];
|
|
297
|
+
if (!httpDatePatterns.some((pattern) => pattern.test(normalized))) return void 0;
|
|
298
|
+
const timestamp = Date.parse(normalized);
|
|
299
|
+
if (!Number.isFinite(timestamp)) return void 0;
|
|
300
|
+
return Math.max(0, timestamp - Date.now());
|
|
301
|
+
}
|
|
302
|
+
function parseRetryAfterBody(body) {
|
|
303
|
+
try {
|
|
304
|
+
const parsed = JSON.parse(body);
|
|
305
|
+
const interval = parsed.retry_after?.interval;
|
|
306
|
+
const unit = parsed.retry_after?.unit;
|
|
307
|
+
if (typeof interval !== "number" || !Number.isFinite(interval) || interval < 0)
|
|
308
|
+
return void 0;
|
|
309
|
+
if (typeof unit !== "string") return void 0;
|
|
310
|
+
const unitMultipliers = {
|
|
311
|
+
ms: 1,
|
|
312
|
+
msec: 1,
|
|
313
|
+
msecs: 1,
|
|
314
|
+
millisecond: 1,
|
|
315
|
+
milliseconds: 1,
|
|
316
|
+
s: 1e3,
|
|
317
|
+
sec: 1e3,
|
|
318
|
+
secs: 1e3,
|
|
319
|
+
second: 1e3,
|
|
320
|
+
seconds: 1e3,
|
|
321
|
+
m: 6e4,
|
|
322
|
+
min: 6e4,
|
|
323
|
+
mins: 6e4,
|
|
324
|
+
minute: 6e4,
|
|
325
|
+
minutes: 6e4
|
|
326
|
+
};
|
|
327
|
+
const multiplier = unitMultipliers[unit.toLowerCase()];
|
|
328
|
+
if (multiplier === void 0) return void 0;
|
|
329
|
+
const milliseconds = interval * multiplier;
|
|
330
|
+
return Number.isFinite(milliseconds) ? milliseconds : void 0;
|
|
331
|
+
} catch {
|
|
332
|
+
return void 0;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
278
335
|
async function executeWithRetry(opts) {
|
|
279
336
|
const { maxRetries, execute, onRetryableFailure } = opts;
|
|
280
337
|
let lastError;
|
|
@@ -291,7 +348,8 @@ async function executeWithRetry(opts) {
|
|
|
291
348
|
}
|
|
292
349
|
throw new AISecSDKException(
|
|
293
350
|
lastError.message ?? "Network error",
|
|
294
|
-
"AISEC_CLIENT_SIDE_ERROR" /* CLIENT_SIDE_ERROR
|
|
351
|
+
"AISEC_CLIENT_SIDE_ERROR" /* CLIENT_SIDE_ERROR */,
|
|
352
|
+
{ failureKind: "network" }
|
|
295
353
|
);
|
|
296
354
|
}
|
|
297
355
|
if (response.ok) return response;
|
|
@@ -308,11 +366,18 @@ async function executeWithRetry(opts) {
|
|
|
308
366
|
}
|
|
309
367
|
const errorText = await response.text();
|
|
310
368
|
const errorMessage = extractErrorMessage(errorText, response.status);
|
|
311
|
-
|
|
369
|
+
const retryAfterHeader = response.headers?.get?.("Retry-After") ?? null;
|
|
370
|
+
const headerRetryAfterMs = parseRetryAfterHeader(retryAfterHeader);
|
|
371
|
+
throw new AISecSDKException(errorMessage, classifyErrorType(response.status), {
|
|
372
|
+
failureKind: "http",
|
|
373
|
+
statusCode: response.status,
|
|
374
|
+
retryAfterMs: headerRetryAfterMs ?? parseRetryAfterBody(errorText)
|
|
375
|
+
});
|
|
312
376
|
}
|
|
313
377
|
throw new AISecSDKException(
|
|
314
378
|
lastError?.message ?? "Max retries exceeded",
|
|
315
|
-
"AISEC_CLIENT_SIDE_ERROR" /* CLIENT_SIDE_ERROR
|
|
379
|
+
"AISEC_CLIENT_SIDE_ERROR" /* CLIENT_SIDE_ERROR */,
|
|
380
|
+
lastError ? { failureKind: "network" } : {}
|
|
316
381
|
);
|
|
317
382
|
}
|
|
318
383
|
|
|
@@ -445,6 +510,7 @@ var PromptDetectedSchema = z.object({
|
|
|
445
510
|
injection: z.boolean().optional(),
|
|
446
511
|
toxic_content: z.boolean().optional(),
|
|
447
512
|
malicious_code: z.boolean().optional(),
|
|
513
|
+
source_code: z.boolean().optional(),
|
|
448
514
|
agent: z.boolean().optional(),
|
|
449
515
|
topic_violation: z.boolean().optional()
|
|
450
516
|
}).passthrough();
|
|
@@ -749,6 +815,16 @@ var ThreatScanReportSchema = z14.object({
|
|
|
749
815
|
}).passthrough();
|
|
750
816
|
|
|
751
817
|
// src/scan/scanner.ts
|
|
818
|
+
function resolveNumRetries(opts) {
|
|
819
|
+
const numRetries = opts.numRetries ?? globalConfiguration.numRetries;
|
|
820
|
+
if (!Number.isFinite(numRetries) || !Number.isInteger(numRetries) || numRetries < 0 || numRetries > MAX_NUMBER_OF_RETRIES) {
|
|
821
|
+
throw new AISecSDKException(
|
|
822
|
+
`numRetries must be a finite integer between 0 and ${MAX_NUMBER_OF_RETRIES}`,
|
|
823
|
+
"AISEC_USER_REQUEST_PAYLOAD_ERROR" /* USER_REQUEST_PAYLOAD_ERROR */
|
|
824
|
+
);
|
|
825
|
+
}
|
|
826
|
+
return numRetries;
|
|
827
|
+
}
|
|
752
828
|
var Scanner = class {
|
|
753
829
|
/**
|
|
754
830
|
* @internal
|
|
@@ -769,7 +845,7 @@ var Scanner = class {
|
|
|
769
845
|
* Perform a synchronous content scan.
|
|
770
846
|
* @param aiProfile - AI security profile to scan against.
|
|
771
847
|
* @param content - Content to scan.
|
|
772
|
-
* @param opts - Optional transaction/session IDs and
|
|
848
|
+
* @param opts - Optional transaction/session IDs, metadata, and per-call retry override.
|
|
773
849
|
* @returns Scan response with verdict, action, and detection details.
|
|
774
850
|
* @example
|
|
775
851
|
* ```ts
|
|
@@ -788,6 +864,7 @@ var Scanner = class {
|
|
|
788
864
|
* ```
|
|
789
865
|
*/
|
|
790
866
|
async syncScan(aiProfile, content, opts = {}) {
|
|
867
|
+
const numRetries = resolveNumRetries(opts);
|
|
791
868
|
if (opts.trId && opts.trId.length > MAX_TRANSACTION_ID_STR_LENGTH) {
|
|
792
869
|
throw new AISecSDKException(
|
|
793
870
|
`trId exceeds max length of ${MAX_TRANSACTION_ID_STR_LENGTH}`,
|
|
@@ -814,33 +891,52 @@ var Scanner = class {
|
|
|
814
891
|
body,
|
|
815
892
|
responseSchema: ScanResponseSchema,
|
|
816
893
|
auth: this.buildAuth(),
|
|
817
|
-
numRetries
|
|
894
|
+
numRetries
|
|
818
895
|
});
|
|
819
896
|
}
|
|
820
897
|
/**
|
|
821
|
-
* Submit content for asynchronous scanning.
|
|
822
|
-
*
|
|
823
|
-
*
|
|
898
|
+
* Submit one batch of content for asynchronous scanning.
|
|
899
|
+
*
|
|
900
|
+
* The call accepts 1–20 request objects and returns one batch receipt. A batch `scan_id` can fan
|
|
901
|
+
* out to several unordered result rows. Correlate each row by `(scan_id, req_id)`, never array
|
|
902
|
+
* position or `scan_id` alone. The SDK preserves the server's row order and cardinality.
|
|
903
|
+
*
|
|
904
|
+
* Setting `numRetries: 0` guarantees one SDK fetch attempt, but cannot guarantee exactly-once
|
|
905
|
+
* server submission after an ambiguous network or 5xx failure.
|
|
906
|
+
* @param scanObjects - Array of scan objects (1–20 items), each with its own `req_id`.
|
|
907
|
+
* @param opts - Optional per-call retry override.
|
|
908
|
+
* @returns One batch receipt containing the shared scan ID for later querying.
|
|
824
909
|
* @example
|
|
825
910
|
* ```ts
|
|
826
911
|
* import { init, Scanner } from '@cdot65/prisma-airs-sdk';
|
|
827
912
|
* init();
|
|
828
913
|
* const scanner = new Scanner();
|
|
829
914
|
*
|
|
830
|
-
* const
|
|
831
|
-
*
|
|
832
|
-
*
|
|
833
|
-
*
|
|
834
|
-
*
|
|
835
|
-
*
|
|
915
|
+
* const receipt = await scanner.asyncScan(
|
|
916
|
+
* [
|
|
917
|
+
* {
|
|
918
|
+
* req_id: 1,
|
|
919
|
+
* scan_req: {
|
|
920
|
+
* ai_profile: { profile_name: 'my-profile' },
|
|
921
|
+
* contents: [{ prompt: 'Tell me about machine learning.' }],
|
|
922
|
+
* },
|
|
836
923
|
* },
|
|
837
|
-
*
|
|
838
|
-
*
|
|
839
|
-
*
|
|
924
|
+
* {
|
|
925
|
+
* req_id: 2,
|
|
926
|
+
* scan_req: {
|
|
927
|
+
* ai_profile: { profile_name: 'my-profile' },
|
|
928
|
+
* contents: [{ prompt: 'What are neural networks?' }],
|
|
929
|
+
* },
|
|
930
|
+
* },
|
|
931
|
+
* ],
|
|
932
|
+
* { numRetries: 0 },
|
|
933
|
+
* );
|
|
934
|
+
* // receipt =>
|
|
840
935
|
* // { received: '2024-01-01T00:00:00Z', scan_id: '550e...' }
|
|
841
936
|
* ```
|
|
842
937
|
*/
|
|
843
|
-
async asyncScan(scanObjects) {
|
|
938
|
+
async asyncScan(scanObjects, opts = {}) {
|
|
939
|
+
const numRetries = resolveNumRetries(opts);
|
|
844
940
|
if (scanObjects.length < 1) {
|
|
845
941
|
throw new AISecSDKException(
|
|
846
942
|
"At least 1 scan object is required",
|
|
@@ -860,13 +956,17 @@ var Scanner = class {
|
|
|
860
956
|
body: scanObjects,
|
|
861
957
|
responseSchema: AsyncScanResponseSchema,
|
|
862
958
|
auth: this.buildAuth(),
|
|
863
|
-
numRetries
|
|
959
|
+
numRetries
|
|
864
960
|
});
|
|
865
961
|
}
|
|
866
962
|
/**
|
|
867
963
|
* Query scan results by scan IDs.
|
|
964
|
+
*
|
|
965
|
+
* One scan ID can return several unordered rows. Correlate them using `(scan_id, req_id)`. The
|
|
966
|
+
* SDK does not sort, deduplicate, or collapse the response.
|
|
868
967
|
* @param scanIds - Array of scan UUIDs (1–5 items).
|
|
869
|
-
* @
|
|
968
|
+
* @param opts - Optional per-call retry override.
|
|
969
|
+
* @returns Every scan-result row in server order, with status and response data.
|
|
870
970
|
* @example
|
|
871
971
|
* ```ts
|
|
872
972
|
* import { init, Scanner } from '@cdot65/prisma-airs-sdk';
|
|
@@ -876,12 +976,14 @@ var Scanner = class {
|
|
|
876
976
|
* const results = await scanner.queryByScanIds([
|
|
877
977
|
* '550e8400-e29b-41d4-a716-446655440000',
|
|
878
978
|
* ]);
|
|
879
|
-
* //
|
|
880
|
-
*
|
|
881
|
-
*
|
|
979
|
+
* // A single scan ID may produce multiple rows, for example req_id 2 then req_id 1.
|
|
980
|
+
* for (const row of results) {
|
|
981
|
+
* console.log(`${row.scan_id}:${row.req_id}`, row.status, row.result?.action);
|
|
982
|
+
* }
|
|
882
983
|
* ```
|
|
883
984
|
*/
|
|
884
|
-
async queryByScanIds(scanIds) {
|
|
985
|
+
async queryByScanIds(scanIds, opts = {}) {
|
|
986
|
+
const numRetries = resolveNumRetries(opts);
|
|
885
987
|
if (scanIds.length < 1) {
|
|
886
988
|
throw new AISecSDKException(
|
|
887
989
|
"At least 1 scan_id is required",
|
|
@@ -906,13 +1008,17 @@ var Scanner = class {
|
|
|
906
1008
|
params: { scan_ids: scanIds.join(",") },
|
|
907
1009
|
responseSchema: z15.array(ScanIdResultSchema),
|
|
908
1010
|
auth: this.buildAuth(),
|
|
909
|
-
numRetries
|
|
1011
|
+
numRetries
|
|
910
1012
|
});
|
|
911
1013
|
}
|
|
912
1014
|
/**
|
|
913
1015
|
* Query detailed threat reports by report IDs.
|
|
1016
|
+
*
|
|
1017
|
+
* One report ID can return several rows. Correlate them using `(report_id, req_id)`. The SDK
|
|
1018
|
+
* preserves server order and cardinality without sorting or deduplicating.
|
|
914
1019
|
* @param reportIds - Array of report IDs (1–5 items).
|
|
915
|
-
* @
|
|
1020
|
+
* @param opts - Optional per-call retry override.
|
|
1021
|
+
* @returns Every report row in server order, with detection details.
|
|
916
1022
|
* @example
|
|
917
1023
|
* ```ts
|
|
918
1024
|
* import { init, Scanner } from '@cdot65/prisma-airs-sdk';
|
|
@@ -920,12 +1026,13 @@ var Scanner = class {
|
|
|
920
1026
|
* const scanner = new Scanner();
|
|
921
1027
|
*
|
|
922
1028
|
* const reports = await scanner.queryByReportIds(['R000...']);
|
|
923
|
-
*
|
|
924
|
-
*
|
|
925
|
-
*
|
|
1029
|
+
* for (const report of reports) {
|
|
1030
|
+
* console.log(`${report.report_id}:${report.req_id}`, report.detection_results);
|
|
1031
|
+
* }
|
|
926
1032
|
* ```
|
|
927
1033
|
*/
|
|
928
|
-
async queryByReportIds(reportIds) {
|
|
1034
|
+
async queryByReportIds(reportIds, opts = {}) {
|
|
1035
|
+
const numRetries = resolveNumRetries(opts);
|
|
929
1036
|
if (reportIds.length < 1) {
|
|
930
1037
|
throw new AISecSDKException(
|
|
931
1038
|
"At least 1 report_id is required",
|
|
@@ -945,7 +1052,7 @@ var Scanner = class {
|
|
|
945
1052
|
params: { report_ids: reportIds.join(",") },
|
|
946
1053
|
responseSchema: z15.array(ThreatScanReportSchema),
|
|
947
1054
|
auth: this.buildAuth(),
|
|
948
|
-
numRetries
|
|
1055
|
+
numRetries
|
|
949
1056
|
});
|
|
950
1057
|
}
|
|
951
1058
|
};
|