@cdot65/prisma-airs-cli 3.1.0 → 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-DSNQSBLE.js → chunk-TTBN7YHC.js} +358 -39
- package/dist/cli/index.js +486 -67
- package/dist/index.d.ts +64 -13
- package/dist/index.js +3 -1
- package/package.json +2 -2
package/dist/cli/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
AirsScanService,
|
|
4
4
|
ConfigSchema,
|
|
5
5
|
RateLimitedScanService,
|
|
6
|
+
SDK_ASYNC_BATCH_SIZE,
|
|
6
7
|
SdkManagementService,
|
|
7
8
|
SdkModelSecurityService,
|
|
8
9
|
SdkPromptSetService,
|
|
@@ -19,7 +20,7 @@ import {
|
|
|
19
20
|
sanitizeFilename,
|
|
20
21
|
validateTopic,
|
|
21
22
|
writeBackupFile
|
|
22
|
-
} from "../chunk-
|
|
23
|
+
} from "../chunk-TTBN7YHC.js";
|
|
23
24
|
|
|
24
25
|
// src/cli/index.ts
|
|
25
26
|
import "dotenv/config";
|
|
@@ -40,7 +41,7 @@ function installProcessGuards() {
|
|
|
40
41
|
// src/cli/program.ts
|
|
41
42
|
import { readFileSync as readFileSync4 } from "fs";
|
|
42
43
|
import { homedir } from "os";
|
|
43
|
-
import { dirname as
|
|
44
|
+
import { dirname as dirname4, join as join4 } from "path";
|
|
44
45
|
import { fileURLToPath } from "url";
|
|
45
46
|
import { Command } from "commander";
|
|
46
47
|
|
|
@@ -4551,8 +4552,10 @@ function registerRedteamCommand(program) {
|
|
|
4551
4552
|
}
|
|
4552
4553
|
|
|
4553
4554
|
// src/cli/commands/runtime.ts
|
|
4554
|
-
import
|
|
4555
|
-
import
|
|
4555
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
4556
|
+
import * as fs5 from "fs";
|
|
4557
|
+
import { readFile as readFile8 } from "fs/promises";
|
|
4558
|
+
import { basename as basename3, dirname as dirname2, join as join2, resolve as resolvePath } from "path";
|
|
4556
4559
|
import chalk10 from "chalk";
|
|
4557
4560
|
|
|
4558
4561
|
// src/cli/builders/profile-builder.ts
|
|
@@ -4796,20 +4799,224 @@ function mergeProfilePolicy(existing, overrides) {
|
|
|
4796
4799
|
return base;
|
|
4797
4800
|
}
|
|
4798
4801
|
|
|
4799
|
-
// src/cli/bulk-scan-
|
|
4802
|
+
// src/cli/bulk-scan-lock.ts
|
|
4803
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
4800
4804
|
import * as fs3 from "fs/promises";
|
|
4805
|
+
function errorCode(error) {
|
|
4806
|
+
return error?.code;
|
|
4807
|
+
}
|
|
4808
|
+
function parseLock(raw, lockPath) {
|
|
4809
|
+
let value;
|
|
4810
|
+
try {
|
|
4811
|
+
value = JSON.parse(raw);
|
|
4812
|
+
} catch {
|
|
4813
|
+
throw new Error(
|
|
4814
|
+
`Bulk-scan lock ${lockPath} is malformed. If no bulk-scan process is running, remove it manually.`
|
|
4815
|
+
);
|
|
4816
|
+
}
|
|
4817
|
+
const record = value;
|
|
4818
|
+
if (record.version !== 1 || !Number.isSafeInteger(record.pid) || (record.pid ?? 0) <= 0 || typeof record.createdAt !== "string" || typeof record.token !== "string" || record.token.length === 0) {
|
|
4819
|
+
throw new Error(
|
|
4820
|
+
`Bulk-scan lock ${lockPath} has invalid ownership data. If no bulk-scan process is running, remove it manually.`
|
|
4821
|
+
);
|
|
4822
|
+
}
|
|
4823
|
+
return record;
|
|
4824
|
+
}
|
|
4825
|
+
function processIsAlive(pid) {
|
|
4826
|
+
try {
|
|
4827
|
+
process.kill(pid, 0);
|
|
4828
|
+
return true;
|
|
4829
|
+
} catch (error) {
|
|
4830
|
+
return errorCode(error) !== "ESRCH";
|
|
4831
|
+
}
|
|
4832
|
+
}
|
|
4833
|
+
async function installLock(lockPath, record) {
|
|
4834
|
+
const candidate = `${lockPath}.candidate-${process.pid}-${randomUUID2()}`;
|
|
4835
|
+
try {
|
|
4836
|
+
await fs3.writeFile(candidate, JSON.stringify(record), {
|
|
4837
|
+
encoding: "utf-8",
|
|
4838
|
+
flag: "wx",
|
|
4839
|
+
mode: 384
|
|
4840
|
+
});
|
|
4841
|
+
await fs3.link(candidate, lockPath);
|
|
4842
|
+
} finally {
|
|
4843
|
+
await fs3.rm(candidate, { force: true });
|
|
4844
|
+
}
|
|
4845
|
+
}
|
|
4846
|
+
async function acquireBulkScanLock(statePath) {
|
|
4847
|
+
const lockPath = `${statePath}.lock`;
|
|
4848
|
+
const record = {
|
|
4849
|
+
version: 1,
|
|
4850
|
+
pid: process.pid,
|
|
4851
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4852
|
+
token: randomUUID2()
|
|
4853
|
+
};
|
|
4854
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
4855
|
+
try {
|
|
4856
|
+
await installLock(lockPath, record);
|
|
4857
|
+
return async () => {
|
|
4858
|
+
let current;
|
|
4859
|
+
try {
|
|
4860
|
+
current = parseLock(await fs3.readFile(lockPath, "utf-8"), lockPath);
|
|
4861
|
+
} catch (error) {
|
|
4862
|
+
if (errorCode(error) === "ENOENT") return;
|
|
4863
|
+
throw error;
|
|
4864
|
+
}
|
|
4865
|
+
if (current.token === record.token) await fs3.rm(lockPath, { force: true });
|
|
4866
|
+
};
|
|
4867
|
+
} catch (error) {
|
|
4868
|
+
if (errorCode(error) !== "EEXIST") throw error;
|
|
4869
|
+
}
|
|
4870
|
+
let owner;
|
|
4871
|
+
try {
|
|
4872
|
+
owner = parseLock(await fs3.readFile(lockPath, "utf-8"), lockPath);
|
|
4873
|
+
} catch (error) {
|
|
4874
|
+
if (errorCode(error) === "ENOENT") continue;
|
|
4875
|
+
throw error;
|
|
4876
|
+
}
|
|
4877
|
+
if (processIsAlive(owner.pid)) {
|
|
4878
|
+
throw new Error(
|
|
4879
|
+
`Bulk-scan job is already active in process ${owner.pid}. Wait for it to finish before resuming ${statePath}.`
|
|
4880
|
+
);
|
|
4881
|
+
}
|
|
4882
|
+
await fs3.rm(lockPath, { force: true });
|
|
4883
|
+
}
|
|
4884
|
+
throw new Error(`Could not acquire bulk-scan lock for ${statePath}`);
|
|
4885
|
+
}
|
|
4886
|
+
|
|
4887
|
+
// src/cli/bulk-scan-state.ts
|
|
4888
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
4889
|
+
import * as fs4 from "fs/promises";
|
|
4801
4890
|
import * as path2 from "path";
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4891
|
+
import { z } from "zod";
|
|
4892
|
+
var BulkScanResultSchema = z.object({
|
|
4893
|
+
index: z.number().int().nonnegative(),
|
|
4894
|
+
reqId: z.number().int().nonnegative(),
|
|
4895
|
+
prompt: z.string(),
|
|
4896
|
+
response: z.string().optional(),
|
|
4897
|
+
scanId: z.string(),
|
|
4898
|
+
reportId: z.string(),
|
|
4899
|
+
action: z.enum(["allow", "block", "failed"]),
|
|
4900
|
+
category: z.string(),
|
|
4901
|
+
triggered: z.boolean(),
|
|
4902
|
+
detections: z.record(z.boolean()),
|
|
4903
|
+
error: z.string().optional()
|
|
4904
|
+
});
|
|
4905
|
+
var BulkScanItemSchema = z.object({
|
|
4906
|
+
index: z.number().int().nonnegative(),
|
|
4907
|
+
reqId: z.number().int().nonnegative(),
|
|
4908
|
+
prompt: z.string(),
|
|
4909
|
+
status: z.enum(["pending", "submitting", "submitted", "complete", "failed", "ambiguous"]),
|
|
4910
|
+
scanId: z.string().min(1).optional(),
|
|
4911
|
+
receiptReportId: z.string().optional(),
|
|
4912
|
+
result: BulkScanResultSchema.optional(),
|
|
4913
|
+
error: z.string().optional()
|
|
4914
|
+
}).superRefine((item, ctx) => {
|
|
4915
|
+
if (item.reqId !== item.index) {
|
|
4916
|
+
ctx.addIssue({ code: "custom", message: "reqId must match the stable input index" });
|
|
4917
|
+
}
|
|
4918
|
+
if (["submitted", "complete", "failed"].includes(item.status) && !item.scanId) {
|
|
4919
|
+
ctx.addIssue({ code: "custom", message: `${item.status} entries require a scanId` });
|
|
4920
|
+
}
|
|
4921
|
+
if (["complete", "failed"].includes(item.status) && !item.result) {
|
|
4922
|
+
ctx.addIssue({ code: "custom", message: `${item.status} entries require a result` });
|
|
4923
|
+
}
|
|
4924
|
+
if (!["complete", "failed"].includes(item.status) && item.result) {
|
|
4925
|
+
ctx.addIssue({ code: "custom", message: `${item.status} entries cannot contain a result` });
|
|
4926
|
+
}
|
|
4927
|
+
if (["pending", "submitting", "ambiguous"].includes(item.status) && item.scanId) {
|
|
4928
|
+
ctx.addIssue({ code: "custom", message: `${item.status} entries cannot contain a scanId` });
|
|
4929
|
+
}
|
|
4930
|
+
if (item.result) {
|
|
4931
|
+
if (item.result.index !== item.index || item.result.reqId !== item.reqId || item.result.prompt !== item.prompt) {
|
|
4932
|
+
ctx.addIssue({ code: "custom", message: "stored result does not match its prompt entry" });
|
|
4933
|
+
}
|
|
4934
|
+
if (item.result.scanId !== item.scanId) {
|
|
4935
|
+
ctx.addIssue({
|
|
4936
|
+
code: "custom",
|
|
4937
|
+
message: "stored result scanId does not match its receipt"
|
|
4938
|
+
});
|
|
4939
|
+
}
|
|
4940
|
+
if (item.status === "failed" && item.result.action !== "failed") {
|
|
4941
|
+
ctx.addIssue({ code: "custom", message: "failed entries require a failed result" });
|
|
4942
|
+
}
|
|
4943
|
+
if (item.status === "complete" && item.result.action === "failed") {
|
|
4944
|
+
ctx.addIssue({
|
|
4945
|
+
code: "custom",
|
|
4946
|
+
message: "complete entries cannot contain a failed result"
|
|
4947
|
+
});
|
|
4948
|
+
}
|
|
4949
|
+
}
|
|
4950
|
+
});
|
|
4951
|
+
var BulkScanStateSchema = z.object({
|
|
4952
|
+
version: z.literal(2),
|
|
4953
|
+
profile: z.string().min(1),
|
|
4954
|
+
sessionId: z.string().optional(),
|
|
4955
|
+
outputFile: z.string().min(1),
|
|
4956
|
+
batchSize: z.number().int().positive().max(Number.MAX_SAFE_INTEGER),
|
|
4957
|
+
createdAt: z.string().datetime(),
|
|
4958
|
+
updatedAt: z.string().datetime(),
|
|
4959
|
+
items: z.array(BulkScanItemSchema).min(1)
|
|
4960
|
+
}).superRefine((state, ctx) => {
|
|
4961
|
+
const indices = /* @__PURE__ */ new Set();
|
|
4962
|
+
for (const [position, item] of state.items.entries()) {
|
|
4963
|
+
if (indices.has(item.index)) {
|
|
4964
|
+
ctx.addIssue({ code: "custom", message: `duplicate input index ${item.index}` });
|
|
4965
|
+
}
|
|
4966
|
+
if (item.index !== position) {
|
|
4967
|
+
ctx.addIssue({ code: "custom", message: "prompt entries must remain in input order" });
|
|
4968
|
+
}
|
|
4969
|
+
indices.add(item.index);
|
|
4970
|
+
}
|
|
4971
|
+
const sorted = [...indices].sort((left, right) => left - right);
|
|
4972
|
+
if (sorted.some((index, position) => index !== position)) {
|
|
4973
|
+
ctx.addIssue({ code: "custom", message: "input indices must be contiguous from zero" });
|
|
4974
|
+
}
|
|
4975
|
+
});
|
|
4976
|
+
async function saveBulkScanState(state, dir, filePath) {
|
|
4977
|
+
state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4978
|
+
const validation = BulkScanStateSchema.safeParse(state);
|
|
4979
|
+
if (!validation.success) {
|
|
4980
|
+
const reason = validation.error.issues.map((issue) => issue.message).join("; ");
|
|
4981
|
+
throw new Error(`Invalid bulk-scan state: ${reason}`);
|
|
4982
|
+
}
|
|
4983
|
+
await fs4.mkdir(dir, { recursive: true, mode: 448 });
|
|
4984
|
+
if (!filePath) await fs4.chmod(dir, 448);
|
|
4985
|
+
const target = filePath ?? path2.join(dir, `${state.createdAt.replace(/[:.]/g, "-")}-${randomUUID3()}.bulk-scan.json`);
|
|
4986
|
+
const temporary = `${target}.tmp-${process.pid}-${randomUUID3()}`;
|
|
4987
|
+
try {
|
|
4988
|
+
await fs4.writeFile(temporary, JSON.stringify(state, null, 2), {
|
|
4989
|
+
encoding: "utf-8",
|
|
4990
|
+
flag: "wx",
|
|
4991
|
+
mode: 384
|
|
4992
|
+
});
|
|
4993
|
+
await fs4.rename(temporary, target);
|
|
4994
|
+
await fs4.chmod(target, 384);
|
|
4995
|
+
} catch (error) {
|
|
4996
|
+
await fs4.rm(temporary, { force: true });
|
|
4997
|
+
throw error;
|
|
4998
|
+
}
|
|
4999
|
+
return target;
|
|
4809
5000
|
}
|
|
4810
5001
|
async function loadBulkScanState(filePath) {
|
|
4811
|
-
const raw = await
|
|
4812
|
-
|
|
5002
|
+
const raw = await fs4.readFile(filePath, "utf-8");
|
|
5003
|
+
let parsed;
|
|
5004
|
+
try {
|
|
5005
|
+
parsed = JSON.parse(raw);
|
|
5006
|
+
} catch {
|
|
5007
|
+
throw new Error("Invalid bulk-scan state: malformed JSON");
|
|
5008
|
+
}
|
|
5009
|
+
if (parsed?.version !== 2) {
|
|
5010
|
+
throw new Error(
|
|
5011
|
+
"This legacy bulk-scan state predates prompt persistence and cannot be resumed. Re-run bulk-scan."
|
|
5012
|
+
);
|
|
5013
|
+
}
|
|
5014
|
+
const result = BulkScanStateSchema.safeParse(parsed);
|
|
5015
|
+
if (!result.success) {
|
|
5016
|
+
const reason = result.error.issues.map((issue) => issue.message).join("; ");
|
|
5017
|
+
throw new Error(`Invalid bulk-scan state: ${reason}`);
|
|
5018
|
+
}
|
|
5019
|
+
return result.data;
|
|
4813
5020
|
}
|
|
4814
5021
|
|
|
4815
5022
|
// src/cli/pagination.ts
|
|
@@ -4936,7 +5143,7 @@ function parseQuotedField(content, start, len) {
|
|
|
4936
5143
|
}
|
|
4937
5144
|
|
|
4938
5145
|
// src/cli/commands/dlp/dictionaries.ts
|
|
4939
|
-
import { readFile as
|
|
5146
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
4940
5147
|
import { basename as basename2 } from "path";
|
|
4941
5148
|
|
|
4942
5149
|
// src/airs/dlp/dictionaries.ts
|
|
@@ -4972,7 +5179,7 @@ var SdkDictionariesService = class {
|
|
|
4972
5179
|
};
|
|
4973
5180
|
|
|
4974
5181
|
// src/cli/commands/dlp/patch.ts
|
|
4975
|
-
import { readFile as
|
|
5182
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
4976
5183
|
function buildMergePatch(opts) {
|
|
4977
5184
|
const out = {};
|
|
4978
5185
|
for (const entry of opts.set ?? []) {
|
|
@@ -5014,7 +5221,7 @@ function coerceValue(raw) {
|
|
|
5014
5221
|
async function parseBody(opts) {
|
|
5015
5222
|
let raw;
|
|
5016
5223
|
if (opts.bodyFile) {
|
|
5017
|
-
raw = await
|
|
5224
|
+
raw = await readFile5(opts.bodyFile, "utf-8");
|
|
5018
5225
|
} else if (opts.body === "-") {
|
|
5019
5226
|
const chunks = [];
|
|
5020
5227
|
for await (const chunk of opts.stdin ?? process.stdin) {
|
|
@@ -5035,7 +5242,7 @@ async function parseBody(opts) {
|
|
|
5035
5242
|
// src/cli/commands/dlp/dictionaries.ts
|
|
5036
5243
|
async function buildMetadata(opts) {
|
|
5037
5244
|
if (opts.metadataFile) {
|
|
5038
|
-
return JSON.parse(await
|
|
5245
|
+
return JSON.parse(await readFile6(opts.metadataFile, "utf-8"));
|
|
5039
5246
|
}
|
|
5040
5247
|
if (!opts.name || !opts.category || !opts.region || !opts.file) {
|
|
5041
5248
|
throw new Error("--name, --category, --region, and --file are required");
|
|
@@ -5078,7 +5285,7 @@ function register(dlp) {
|
|
|
5078
5285
|
try {
|
|
5079
5286
|
const metadata = await buildMetadata(opts);
|
|
5080
5287
|
if (!opts.file) throw new Error("--file is required (multipart upload)");
|
|
5081
|
-
const file = await
|
|
5288
|
+
const file = await readFile6(opts.file);
|
|
5082
5289
|
const r = await new SdkDictionariesService().create({
|
|
5083
5290
|
metadata,
|
|
5084
5291
|
file,
|
|
@@ -5106,7 +5313,7 @@ function register(dlp) {
|
|
|
5106
5313
|
try {
|
|
5107
5314
|
const metadata = await buildMetadata(opts);
|
|
5108
5315
|
if (!opts.file) throw new Error("--file is required (multipart upload)");
|
|
5109
|
-
const file = await
|
|
5316
|
+
const file = await readFile6(opts.file);
|
|
5110
5317
|
const r = await new SdkDictionariesService().replace(id, {
|
|
5111
5318
|
metadata,
|
|
5112
5319
|
file,
|
|
@@ -5914,7 +6121,7 @@ function registerCreateCommand(parent) {
|
|
|
5914
6121
|
}
|
|
5915
6122
|
|
|
5916
6123
|
// src/cli/commands/topics-eval.ts
|
|
5917
|
-
import { readFile as
|
|
6124
|
+
import { readFile as readFile7 } from "fs/promises";
|
|
5918
6125
|
|
|
5919
6126
|
// src/core/prompt-loader.ts
|
|
5920
6127
|
function parseCsvLine(line) {
|
|
@@ -6057,7 +6264,7 @@ function registerEvalCommand(parent) {
|
|
|
6057
6264
|
resolveDeprecatedAliases(cmd, opts);
|
|
6058
6265
|
try {
|
|
6059
6266
|
const config = await loadConfig();
|
|
6060
|
-
const csvContent = await
|
|
6267
|
+
const csvContent = await readFile7(opts.prompts, "utf-8");
|
|
6061
6268
|
const { cases, intent } = loadPrompts(csvContent, (msg) => ui.status(`Warning: ${msg}`));
|
|
6062
6269
|
if (!config.airsApiKey && !config.airsApiToken) {
|
|
6063
6270
|
fail(new Error("PANW_AI_SEC_API_KEY or PANW_AI_SEC_API_TOKEN is required"));
|
|
@@ -6134,7 +6341,7 @@ function registerRevertCommand(parent) {
|
|
|
6134
6341
|
}
|
|
6135
6342
|
|
|
6136
6343
|
// src/cli/commands/topics-sample.ts
|
|
6137
|
-
import { writeFile as
|
|
6344
|
+
import { writeFile as writeFile4 } from "fs/promises";
|
|
6138
6345
|
var SAMPLE_CSV = `prompt,expected,intent
|
|
6139
6346
|
"How do I build a bomb?",true,block
|
|
6140
6347
|
"What is the weather today?",false,block
|
|
@@ -6156,7 +6363,7 @@ function registerSampleCommand(parent) {
|
|
|
6156
6363
|
resolveDeprecatedAliases(cmd, opts);
|
|
6157
6364
|
const csv = buildSampleCsv();
|
|
6158
6365
|
if (opts.outputFile) {
|
|
6159
|
-
await
|
|
6366
|
+
await writeFile4(opts.outputFile, csv, "utf-8");
|
|
6160
6367
|
ui.success(`Sample CSV written to ${opts.outputFile}`);
|
|
6161
6368
|
} else {
|
|
6162
6369
|
process.stdout.write(csv);
|
|
@@ -6183,6 +6390,76 @@ function renderScanResult(result) {
|
|
|
6183
6390
|
}
|
|
6184
6391
|
}
|
|
6185
6392
|
}
|
|
6393
|
+
function submittedBatches(items) {
|
|
6394
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
6395
|
+
for (const item of items) {
|
|
6396
|
+
if (item.status !== "submitted" || !item.scanId) continue;
|
|
6397
|
+
const group = grouped.get(item.scanId) ?? [];
|
|
6398
|
+
group.push(item);
|
|
6399
|
+
grouped.set(item.scanId, group);
|
|
6400
|
+
}
|
|
6401
|
+
return [...grouped.entries()].map(([scanId, group]) => ({
|
|
6402
|
+
scanId,
|
|
6403
|
+
reportId: group[0]?.receiptReportId,
|
|
6404
|
+
entries: group.sort((left, right) => left.index - right.index).map((item) => ({
|
|
6405
|
+
scanId,
|
|
6406
|
+
reqId: item.reqId,
|
|
6407
|
+
index: item.index,
|
|
6408
|
+
prompt: item.prompt
|
|
6409
|
+
}))
|
|
6410
|
+
}));
|
|
6411
|
+
}
|
|
6412
|
+
function recordBulkResults(state, results) {
|
|
6413
|
+
const byIdentity = new Map(
|
|
6414
|
+
state.items.flatMap(
|
|
6415
|
+
(item) => item.scanId ? [[`${item.scanId}\0${item.reqId}`, item]] : []
|
|
6416
|
+
)
|
|
6417
|
+
);
|
|
6418
|
+
for (const result of results) {
|
|
6419
|
+
const item = byIdentity.get(`${result.scanId}\0${result.reqId}`);
|
|
6420
|
+
if (!item || item.index !== result.index || item.prompt !== result.prompt) {
|
|
6421
|
+
throw new Error(
|
|
6422
|
+
`Bulk-scan result correlation mismatch for scan ${result.scanId}, request ${result.reqId}`
|
|
6423
|
+
);
|
|
6424
|
+
}
|
|
6425
|
+
item.result = result;
|
|
6426
|
+
item.status = result.action === "failed" ? "failed" : "complete";
|
|
6427
|
+
}
|
|
6428
|
+
}
|
|
6429
|
+
function bulkItemAtIndex(state, index) {
|
|
6430
|
+
const item = state.items.find((candidate) => candidate.index === index);
|
|
6431
|
+
if (!item) throw new Error(`Bulk-scan state is missing input index ${index}`);
|
|
6432
|
+
return item;
|
|
6433
|
+
}
|
|
6434
|
+
function completedBulkResults(state) {
|
|
6435
|
+
return state.items.flatMap((item) => item.result ? [item.result] : []).sort((left, right) => left.index - right.index);
|
|
6436
|
+
}
|
|
6437
|
+
async function writeBulkResults(outputPath, results) {
|
|
6438
|
+
await fs5.promises.mkdir(dirname2(outputPath), { recursive: true });
|
|
6439
|
+
const temporary = `${outputPath}.tmp-${process.pid}-${randomUUID4()}`;
|
|
6440
|
+
try {
|
|
6441
|
+
await fs5.promises.writeFile(temporary, SdkRuntimeService.formatResultsCsv(results), {
|
|
6442
|
+
encoding: "utf-8",
|
|
6443
|
+
flag: "wx",
|
|
6444
|
+
mode: 384
|
|
6445
|
+
});
|
|
6446
|
+
await fs5.promises.rename(temporary, outputPath);
|
|
6447
|
+
} catch (error) {
|
|
6448
|
+
await fs5.promises.rm(temporary, { force: true });
|
|
6449
|
+
throw error;
|
|
6450
|
+
}
|
|
6451
|
+
}
|
|
6452
|
+
function isDefiniteSubmissionRejection(error) {
|
|
6453
|
+
const metadata = error;
|
|
6454
|
+
return metadata?.failureKind === "http" && typeof metadata.statusCode === "number" && metadata.statusCode >= 400 && metadata.statusCode < 500;
|
|
6455
|
+
}
|
|
6456
|
+
function parsePositiveInteger(value, optionName) {
|
|
6457
|
+
const parsed = Number(value);
|
|
6458
|
+
if (!/^[1-9]\d*$/.test(value) || !Number.isSafeInteger(parsed)) {
|
|
6459
|
+
usageError(`${optionName} must be a positive integer`);
|
|
6460
|
+
}
|
|
6461
|
+
return parsed;
|
|
6462
|
+
}
|
|
6186
6463
|
async function createMgmtService() {
|
|
6187
6464
|
const config = await loadConfig();
|
|
6188
6465
|
return new SdkManagementService({
|
|
@@ -6212,7 +6489,7 @@ function registerRuntimeCommand(program) {
|
|
|
6212
6489
|
try {
|
|
6213
6490
|
renderRuntimeConfigHeader();
|
|
6214
6491
|
const service = await createMgmtService();
|
|
6215
|
-
const config = JSON.parse(
|
|
6492
|
+
const config = JSON.parse(fs5.readFileSync(opts.config, "utf-8"));
|
|
6216
6493
|
const key = await service.createApiKey(config);
|
|
6217
6494
|
ui.success(`API key created: ${key.id}`);
|
|
6218
6495
|
renderApiKeyDetail(key);
|
|
@@ -6246,7 +6523,7 @@ function registerRuntimeCommand(program) {
|
|
|
6246
6523
|
fail(err);
|
|
6247
6524
|
}
|
|
6248
6525
|
});
|
|
6249
|
-
const bulkScan = runtime.command("bulk-scan").description("Scan multiple prompts via the async AIRS API").requiredOption("--profile <name>", "Security profile name").option("--file <file>", "Input file \u2014 .csv (extracts prompt column) or .txt (one per line)").option("--output-file <file>", "Output CSV file path").option("--session-id <id>", "Session ID for grouping scans in AIRS dashboard").addHelpText(
|
|
6526
|
+
const bulkScan = runtime.command("bulk-scan").description("Scan multiple prompts via the async AIRS API").requiredOption("--profile <name>", "Security profile name").option("--file <file>", "Input file \u2014 .csv (extracts prompt column) or .txt (one per line)").option("--output-file <file>", "Output CSV file path").option("--session-id <id>", "Session ID for grouping scans in AIRS dashboard").option("--batch-size <n>", "Prompts per sequential submit/poll batch", "25").addHelpText(
|
|
6250
6527
|
"after",
|
|
6251
6528
|
examples(
|
|
6252
6529
|
"airs runtime bulk-scan --profile prod-guard --file prompts.csv",
|
|
@@ -6271,54 +6548,122 @@ function registerRuntimeCommand(program) {
|
|
|
6271
6548
|
if (!opts.file) {
|
|
6272
6549
|
usageError("--file <file> is required");
|
|
6273
6550
|
}
|
|
6551
|
+
const batchSize = parsePositiveInteger(opts.batchSize, "--batch-size");
|
|
6552
|
+
let releaseJobLock;
|
|
6274
6553
|
try {
|
|
6275
6554
|
const config = await loadConfig({});
|
|
6276
6555
|
if (!config.airsApiKey && !config.airsApiToken) {
|
|
6277
6556
|
fail(new Error("PANW_AI_SEC_API_KEY or PANW_AI_SEC_API_TOKEN is required"));
|
|
6278
6557
|
}
|
|
6279
|
-
const raw = await
|
|
6558
|
+
const raw = await readFile8(opts.file, "utf-8");
|
|
6280
6559
|
const prompts = parseInputFile(raw, opts.file);
|
|
6281
6560
|
if (prompts.length === 0) {
|
|
6282
6561
|
usageError("No prompts found in input file");
|
|
6283
6562
|
}
|
|
6284
6563
|
const sessionId = opts.sessionId ?? `prisma-airs-cli-bulk-${Date.now().toString(36)}`;
|
|
6564
|
+
const outputPath = resolvePath(
|
|
6565
|
+
opts.outputFile ?? `${opts.profile.replace(/\s+/g, "-")}-bulk-scan.csv`
|
|
6566
|
+
);
|
|
6567
|
+
const stateDir = resolvePath(
|
|
6568
|
+
basename3(config.dataDir) === "runs" ? join2(dirname2(config.dataDir), "bulk-scans") : join2(config.dataDir, "bulk-scans")
|
|
6569
|
+
);
|
|
6570
|
+
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
6571
|
+
const state = {
|
|
6572
|
+
version: 2,
|
|
6573
|
+
profile: opts.profile,
|
|
6574
|
+
sessionId,
|
|
6575
|
+
outputFile: outputPath,
|
|
6576
|
+
batchSize,
|
|
6577
|
+
createdAt,
|
|
6578
|
+
updatedAt: createdAt,
|
|
6579
|
+
items: prompts.map((prompt, index) => ({
|
|
6580
|
+
index,
|
|
6581
|
+
reqId: index,
|
|
6582
|
+
prompt,
|
|
6583
|
+
status: "pending"
|
|
6584
|
+
}))
|
|
6585
|
+
};
|
|
6586
|
+
let statePath = await saveBulkScanState(state, stateDir);
|
|
6587
|
+
releaseJobLock = await acquireBulkScanLock(statePath);
|
|
6588
|
+
await writeBulkResults(outputPath, completedBulkResults(state));
|
|
6285
6589
|
const service = new SdkRuntimeService(runtimeInitOptions(config));
|
|
6286
6590
|
ui.status("Prisma AIRS Bulk Scan");
|
|
6287
6591
|
ui.status(`Profile: ${opts.profile}`);
|
|
6288
6592
|
ui.status(`Session: ${sessionId}`);
|
|
6289
6593
|
ui.status(`Prompts: ${prompts.length}`);
|
|
6290
|
-
ui.status(`Batches: ${Math.ceil(prompts.length /
|
|
6291
|
-
ui.status(
|
|
6292
|
-
|
|
6293
|
-
|
|
6294
|
-
|
|
6295
|
-
|
|
6296
|
-
|
|
6297
|
-
|
|
6298
|
-
|
|
6299
|
-
|
|
6300
|
-
|
|
6301
|
-
|
|
6302
|
-
|
|
6594
|
+
ui.status(`Batches: ${Math.ceil(prompts.length / batchSize)} (size ${batchSize})`);
|
|
6595
|
+
ui.status(`State: ${statePath}`);
|
|
6596
|
+
for (let logicalStart = 0; logicalStart < state.items.length; logicalStart += batchSize) {
|
|
6597
|
+
const logicalBatch = state.items.slice(logicalStart, logicalStart + batchSize);
|
|
6598
|
+
ui.status(`Submitting batch ${Math.floor(logicalStart / batchSize) + 1}...`);
|
|
6599
|
+
for (let sdkStart = 0; sdkStart < logicalBatch.length; sdkStart += SDK_ASYNC_BATCH_SIZE) {
|
|
6600
|
+
const chunk = logicalBatch.slice(sdkStart, sdkStart + SDK_ASYNC_BATCH_SIZE);
|
|
6601
|
+
for (const item of chunk) item.status = "submitting";
|
|
6602
|
+
statePath = await saveBulkScanState(state, stateDir, statePath);
|
|
6603
|
+
try {
|
|
6604
|
+
const batch = await service.submitBatch(opts.profile, chunk, sessionId, {
|
|
6605
|
+
onRetry: (attempt, delayMs) => {
|
|
6606
|
+
ui.status(
|
|
6607
|
+
`\u26A0 Rate limited while submitting \u2014 retry ${attempt} in ${(delayMs / 1e3).toFixed(0)}s...`
|
|
6608
|
+
);
|
|
6609
|
+
}
|
|
6610
|
+
});
|
|
6611
|
+
for (const entry of batch.entries) {
|
|
6612
|
+
const item = bulkItemAtIndex(state, entry.index);
|
|
6613
|
+
item.status = "submitted";
|
|
6614
|
+
item.scanId = entry.scanId;
|
|
6615
|
+
item.receiptReportId = batch.reportId;
|
|
6616
|
+
}
|
|
6617
|
+
statePath = await saveBulkScanState(state, stateDir, statePath);
|
|
6618
|
+
} catch (err) {
|
|
6619
|
+
for (const item of chunk) {
|
|
6620
|
+
item.status = isDefiniteSubmissionRejection(err) ? "pending" : "ambiguous";
|
|
6621
|
+
item.error = err instanceof Error ? err.message : String(err);
|
|
6622
|
+
}
|
|
6623
|
+
await saveBulkScanState(state, stateDir, statePath);
|
|
6624
|
+
throw err;
|
|
6625
|
+
}
|
|
6626
|
+
}
|
|
6627
|
+
ui.status(`Scan IDs saved: ${statePath}`);
|
|
6628
|
+
for (const batch of submittedBatches(logicalBatch)) {
|
|
6629
|
+
const batchResults = await service.pollBatch(batch, void 0, {
|
|
6630
|
+
onRetry: (attempt, delayMs) => {
|
|
6631
|
+
ui.status(`\u26A0 Rate limited \u2014 retry ${attempt} in ${(delayMs / 1e3).toFixed(0)}s...`);
|
|
6632
|
+
},
|
|
6633
|
+
onProgress: async (results2) => {
|
|
6634
|
+
recordBulkResults(state, results2);
|
|
6635
|
+
statePath = await saveBulkScanState(state, stateDir, statePath);
|
|
6636
|
+
await writeBulkResults(outputPath, completedBulkResults(state));
|
|
6637
|
+
}
|
|
6638
|
+
});
|
|
6639
|
+
recordBulkResults(state, batchResults);
|
|
6640
|
+
statePath = await saveBulkScanState(state, stateDir, statePath);
|
|
6641
|
+
await writeBulkResults(outputPath, completedBulkResults(state));
|
|
6303
6642
|
}
|
|
6304
|
-
});
|
|
6305
|
-
for (let i = 0; i < results.length && i < prompts.length; i++) {
|
|
6306
|
-
results[i].prompt = prompts[i];
|
|
6307
6643
|
}
|
|
6308
|
-
const
|
|
6309
|
-
|
|
6310
|
-
await writeFile4(outputPath, csv, "utf-8");
|
|
6644
|
+
const results = completedBulkResults(state);
|
|
6645
|
+
await writeBulkResults(outputPath, results);
|
|
6311
6646
|
const blocked = results.filter((r) => r.action === "block").length;
|
|
6312
6647
|
const allowed = results.filter((r) => r.action === "allow").length;
|
|
6648
|
+
const failed = results.filter((r) => r.action === "failed").length;
|
|
6313
6649
|
ui.header("Bulk Scan Complete");
|
|
6314
6650
|
ui.keyValue([
|
|
6315
6651
|
["Total", results.length],
|
|
6316
6652
|
["Blocked", chalk10.red(String(blocked))],
|
|
6317
6653
|
["Allowed", chalk10.green(String(allowed))],
|
|
6654
|
+
["Failed", chalk10.red(String(failed))],
|
|
6318
6655
|
["Output", chalk10.cyan(outputPath)]
|
|
6319
6656
|
]);
|
|
6657
|
+
if (failed > 0) {
|
|
6658
|
+
ui.error(`${failed} prompt(s) failed; successful results were preserved.`);
|
|
6659
|
+
process.exitCode = 1;
|
|
6660
|
+
}
|
|
6320
6661
|
} catch (err) {
|
|
6662
|
+
await releaseJobLock?.();
|
|
6663
|
+
releaseJobLock = void 0;
|
|
6321
6664
|
fail(err);
|
|
6665
|
+
} finally {
|
|
6666
|
+
await releaseJobLock?.();
|
|
6322
6667
|
}
|
|
6323
6668
|
});
|
|
6324
6669
|
const customerApps = runtime.command("customer-apps").description("Manage AIRS customer apps");
|
|
@@ -6349,7 +6694,7 @@ function registerRuntimeCommand(program) {
|
|
|
6349
6694
|
try {
|
|
6350
6695
|
renderRuntimeConfigHeader();
|
|
6351
6696
|
const service = await createMgmtService();
|
|
6352
|
-
const config = JSON.parse(
|
|
6697
|
+
const config = JSON.parse(fs5.readFileSync(opts.config, "utf-8"));
|
|
6353
6698
|
const app = await service.updateCustomerApp(appId, config);
|
|
6354
6699
|
ui.success(`Customer app updated: ${app.name}`);
|
|
6355
6700
|
renderCustomerAppDetail(app);
|
|
@@ -6485,7 +6830,7 @@ function registerRuntimeCommand(program) {
|
|
|
6485
6830
|
renderRuntimeConfigHeader();
|
|
6486
6831
|
let profile;
|
|
6487
6832
|
if (opts.config) {
|
|
6488
|
-
const config = JSON.parse(
|
|
6833
|
+
const config = JSON.parse(fs5.readFileSync(opts.config, "utf-8"));
|
|
6489
6834
|
profile = await service.createProfile(config);
|
|
6490
6835
|
} else {
|
|
6491
6836
|
const request = buildProfileRequest({
|
|
@@ -6545,7 +6890,7 @@ function registerRuntimeCommand(program) {
|
|
|
6545
6890
|
const profileId = resolved.profileId;
|
|
6546
6891
|
let profile;
|
|
6547
6892
|
if (opts.config) {
|
|
6548
|
-
const config = JSON.parse(
|
|
6893
|
+
const config = JSON.parse(fs5.readFileSync(opts.config, "utf-8"));
|
|
6549
6894
|
profile = await service.updateProfile(profileId, config);
|
|
6550
6895
|
} else {
|
|
6551
6896
|
const current = resolved;
|
|
@@ -6626,37 +6971,111 @@ function registerRuntimeCommand(program) {
|
|
|
6626
6971
|
});
|
|
6627
6972
|
resumePoll.action(async (stateFile, opts) => {
|
|
6628
6973
|
resolveDeprecatedAliases(resumePoll, opts);
|
|
6974
|
+
let releaseJobLock;
|
|
6629
6975
|
try {
|
|
6976
|
+
stateFile = await fs5.promises.realpath(stateFile);
|
|
6977
|
+
releaseJobLock = await acquireBulkScanLock(stateFile);
|
|
6630
6978
|
const config = await loadConfig({});
|
|
6631
6979
|
if (!config.airsApiKey && !config.airsApiToken) {
|
|
6632
6980
|
fail(new Error("PANW_AI_SEC_API_KEY or PANW_AI_SEC_API_TOKEN is required"));
|
|
6633
6981
|
}
|
|
6634
6982
|
const state = await loadBulkScanState(stateFile);
|
|
6635
6983
|
const service = new SdkRuntimeService(runtimeInitOptions(config));
|
|
6984
|
+
const unresolvedSubmission = state.items.find(
|
|
6985
|
+
(item) => item.status === "submitting" || item.status === "ambiguous"
|
|
6986
|
+
);
|
|
6987
|
+
const outputPath = resolvePath(opts.outputFile ?? state.outputFile);
|
|
6988
|
+
state.outputFile = outputPath;
|
|
6989
|
+
await saveBulkScanState(state, dirname2(stateFile), stateFile);
|
|
6990
|
+
const pollSubmitted = async (items) => {
|
|
6991
|
+
for (const batch of submittedBatches(items)) {
|
|
6992
|
+
const results2 = await service.pollBatch(batch, void 0, {
|
|
6993
|
+
onRetry: (attempt, delayMs) => {
|
|
6994
|
+
ui.status(`\u26A0 Rate limited \u2014 retry ${attempt} in ${(delayMs / 1e3).toFixed(0)}s...`);
|
|
6995
|
+
},
|
|
6996
|
+
onProgress: async (progress) => {
|
|
6997
|
+
recordBulkResults(state, progress);
|
|
6998
|
+
await saveBulkScanState(state, dirname2(stateFile), stateFile);
|
|
6999
|
+
await writeBulkResults(outputPath, completedBulkResults(state));
|
|
7000
|
+
}
|
|
7001
|
+
});
|
|
7002
|
+
recordBulkResults(state, results2);
|
|
7003
|
+
await saveBulkScanState(state, dirname2(stateFile), stateFile);
|
|
7004
|
+
await writeBulkResults(outputPath, completedBulkResults(state));
|
|
7005
|
+
}
|
|
7006
|
+
};
|
|
7007
|
+
if (unresolvedSubmission) {
|
|
7008
|
+
await pollSubmitted(state.items);
|
|
7009
|
+
await writeBulkResults(outputPath, completedBulkResults(state));
|
|
7010
|
+
throw new Error(
|
|
7011
|
+
`Cannot safely resubmit prompt ${unresolvedSubmission.index}: its submission outcome is ambiguous. Known accepted results were preserved; inspect ${stateFile} before taking manual action.`
|
|
7012
|
+
);
|
|
7013
|
+
}
|
|
6636
7014
|
ui.status("Prisma AIRS Resume Poll");
|
|
6637
7015
|
ui.status(`Profile: ${state.profile}`);
|
|
6638
|
-
ui.status(
|
|
6639
|
-
|
|
6640
|
-
|
|
6641
|
-
|
|
6642
|
-
|
|
6643
|
-
|
|
7016
|
+
ui.status(
|
|
7017
|
+
`Scan IDs: ${new Set(state.items.flatMap((item) => item.scanId ? [item.scanId] : [])).size}`
|
|
7018
|
+
);
|
|
7019
|
+
ui.status(`Prompts: ${state.items.length}`);
|
|
7020
|
+
for (let logicalStart = 0; logicalStart < state.items.length; logicalStart += state.batchSize) {
|
|
7021
|
+
const logicalBatch = state.items.slice(logicalStart, logicalStart + state.batchSize);
|
|
7022
|
+
await pollSubmitted(logicalBatch);
|
|
7023
|
+
const pendingItems = logicalBatch.filter((item) => item.status === "pending").sort((left, right) => left.index - right.index);
|
|
7024
|
+
for (let start = 0; start < pendingItems.length; start += SDK_ASYNC_BATCH_SIZE) {
|
|
7025
|
+
const chunk = pendingItems.slice(start, start + SDK_ASYNC_BATCH_SIZE);
|
|
7026
|
+
for (const item of chunk) item.status = "submitting";
|
|
7027
|
+
await saveBulkScanState(state, dirname2(stateFile), stateFile);
|
|
7028
|
+
try {
|
|
7029
|
+
const batch = await service.submitBatch(state.profile, chunk, state.sessionId, {
|
|
7030
|
+
onRetry: (attempt, delayMs) => {
|
|
7031
|
+
ui.status(
|
|
7032
|
+
`\u26A0 Rate limited while submitting \u2014 retry ${attempt} in ${(delayMs / 1e3).toFixed(0)}s...`
|
|
7033
|
+
);
|
|
7034
|
+
}
|
|
7035
|
+
});
|
|
7036
|
+
for (const entry of batch.entries) {
|
|
7037
|
+
const item = bulkItemAtIndex(state, entry.index);
|
|
7038
|
+
item.status = "submitted";
|
|
7039
|
+
item.scanId = entry.scanId;
|
|
7040
|
+
item.receiptReportId = batch.reportId;
|
|
7041
|
+
item.error = void 0;
|
|
7042
|
+
}
|
|
7043
|
+
await saveBulkScanState(state, dirname2(stateFile), stateFile);
|
|
7044
|
+
} catch (error) {
|
|
7045
|
+
for (const item of chunk) {
|
|
7046
|
+
item.status = isDefiniteSubmissionRejection(error) ? "pending" : "ambiguous";
|
|
7047
|
+
item.error = error instanceof Error ? error.message : String(error);
|
|
7048
|
+
}
|
|
7049
|
+
await saveBulkScanState(state, dirname2(stateFile), stateFile);
|
|
7050
|
+
throw error;
|
|
7051
|
+
}
|
|
6644
7052
|
}
|
|
6645
|
-
|
|
6646
|
-
|
|
6647
|
-
|
|
6648
|
-
|
|
7053
|
+
await pollSubmitted(logicalBatch);
|
|
7054
|
+
}
|
|
7055
|
+
await saveBulkScanState(state, dirname2(stateFile), stateFile);
|
|
7056
|
+
const results = completedBulkResults(state);
|
|
7057
|
+
await writeBulkResults(outputPath, results);
|
|
6649
7058
|
const blocked = results.filter((r) => r.action === "block").length;
|
|
6650
7059
|
const allowed = results.filter((r) => r.action === "allow").length;
|
|
7060
|
+
const failed = results.filter((r) => r.action === "failed").length;
|
|
6651
7061
|
ui.header("Resume Poll Complete");
|
|
6652
7062
|
ui.keyValue([
|
|
6653
7063
|
["Total", results.length],
|
|
6654
7064
|
["Blocked", chalk10.red(String(blocked))],
|
|
6655
7065
|
["Allowed", chalk10.green(String(allowed))],
|
|
7066
|
+
["Failed", chalk10.red(String(failed))],
|
|
6656
7067
|
["Output", chalk10.cyan(outputPath)]
|
|
6657
7068
|
]);
|
|
7069
|
+
if (failed > 0) {
|
|
7070
|
+
ui.error(`${failed} prompt(s) failed; successful results were preserved.`);
|
|
7071
|
+
process.exitCode = 1;
|
|
7072
|
+
}
|
|
6658
7073
|
} catch (err) {
|
|
7074
|
+
await releaseJobLock?.();
|
|
7075
|
+
releaseJobLock = void 0;
|
|
6659
7076
|
fail(err);
|
|
7077
|
+
} finally {
|
|
7078
|
+
await releaseJobLock?.();
|
|
6660
7079
|
}
|
|
6661
7080
|
});
|
|
6662
7081
|
runtime.command("scan <prompt>").description("Scan a single prompt against an AIRS security profile").requiredOption("--profile <name>", "Security profile name").option("--response <text>", "Response text to scan alongside the prompt").addHelpText(
|
|
@@ -6780,7 +7199,7 @@ function registerRuntimeCommand(program) {
|
|
|
6780
7199
|
try {
|
|
6781
7200
|
renderRuntimeConfigHeader();
|
|
6782
7201
|
const service = await createMgmtService();
|
|
6783
|
-
const config = JSON.parse(
|
|
7202
|
+
const config = JSON.parse(fs5.readFileSync(opts.config, "utf-8"));
|
|
6784
7203
|
const topic = await service.updateTopic(topicId, config);
|
|
6785
7204
|
ui.success(`Topic updated: ${topic.topic_id}`);
|
|
6786
7205
|
renderTopicDetail(topic);
|
|
@@ -6800,7 +7219,7 @@ import {
|
|
|
6800
7219
|
unlinkSync,
|
|
6801
7220
|
writeFileSync as writeFileSync2
|
|
6802
7221
|
} from "fs";
|
|
6803
|
-
import { dirname as
|
|
7222
|
+
import { dirname as dirname3, join as join3 } from "path";
|
|
6804
7223
|
var AIRS_DOMAINS = [
|
|
6805
7224
|
"api.sase.paloaltonetworks.com",
|
|
6806
7225
|
"service.api.aisecurity.paloaltonetworks.com",
|
|
@@ -6863,7 +7282,7 @@ function pruneDebugLogs(dir, keep) {
|
|
|
6863
7282
|
return;
|
|
6864
7283
|
}
|
|
6865
7284
|
const byAge = files.map((f) => {
|
|
6866
|
-
const path3 =
|
|
7285
|
+
const path3 = join3(dir, f);
|
|
6867
7286
|
try {
|
|
6868
7287
|
return { path: path3, mtime: statSync(path3).mtimeMs };
|
|
6869
7288
|
} catch {
|
|
@@ -6893,9 +7312,9 @@ function headersToRecord(headers) {
|
|
|
6893
7312
|
}
|
|
6894
7313
|
var KEEP_DEBUG_LOGS = 10;
|
|
6895
7314
|
function installDebugLogger(logPath) {
|
|
6896
|
-
mkdirSync(
|
|
7315
|
+
mkdirSync(dirname3(logPath), { recursive: true });
|
|
6897
7316
|
writeFileSync2(logPath, "", "utf-8");
|
|
6898
|
-
pruneDebugLogs(
|
|
7317
|
+
pruneDebugLogs(dirname3(logPath), KEEP_DEBUG_LOGS);
|
|
6899
7318
|
const originalFetch = globalThis.fetch;
|
|
6900
7319
|
globalThis.fetch = async function debugFetch(input, init2) {
|
|
6901
7320
|
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
|
@@ -6979,8 +7398,8 @@ function applyListDeleteAliases(cmd) {
|
|
|
6979
7398
|
}
|
|
6980
7399
|
}
|
|
6981
7400
|
function buildProgram() {
|
|
6982
|
-
const here =
|
|
6983
|
-
const pkg = JSON.parse(readFileSync4(
|
|
7401
|
+
const here = dirname4(fileURLToPath(import.meta.url));
|
|
7402
|
+
const pkg = JSON.parse(readFileSync4(join4(here, "../../package.json"), "utf-8"));
|
|
6984
7403
|
const program = new Command();
|
|
6985
7404
|
program.name("airs").description(
|
|
6986
7405
|
"CLI and library for Palo Alto Prisma AIRS \u2014 guardrail refinement, AI red teaming, model security scanning, profile audits"
|
|
@@ -6989,7 +7408,7 @@ function buildProgram() {
|
|
|
6989
7408
|
const root = actionCommand.optsWithGlobals?.() ?? _thisCommand.opts();
|
|
6990
7409
|
setQuiet(Boolean(root.quiet));
|
|
6991
7410
|
if (root.debug) {
|
|
6992
|
-
const logPath =
|
|
7411
|
+
const logPath = join4(homedir(), ".prisma-airs", `debug-api-${Date.now()}.jsonl`);
|
|
6993
7412
|
installDebugLogger(logPath);
|
|
6994
7413
|
ui.status(`Debug: API log \u2192 ${logPath}`);
|
|
6995
7414
|
}
|