@cdot65/prisma-airs-cli 4.5.0 → 5.0.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 +6 -1
- package/dist/{chunk-NWK7RG2N.js → chunk-IRLIFMGQ.js} +206 -76
- package/dist/cli/index.js +340 -147
- package/dist/index.d.ts +45 -15
- package/dist/index.js +1 -1
- package/package.json +2 -2
package/dist/cli/index.js
CHANGED
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
validateTopic,
|
|
25
25
|
writeBackupFile,
|
|
26
26
|
writeReportFile
|
|
27
|
-
} from "../chunk-
|
|
27
|
+
} from "../chunk-IRLIFMGQ.js";
|
|
28
28
|
|
|
29
29
|
// src/cli/index.ts
|
|
30
30
|
import "dotenv/config";
|
|
@@ -226,6 +226,17 @@ var SdkAiGatewayService = class {
|
|
|
226
226
|
};
|
|
227
227
|
|
|
228
228
|
// src/config/client-options.ts
|
|
229
|
+
function managementClientOptions(config) {
|
|
230
|
+
return {
|
|
231
|
+
clientId: config.mgmtClientId,
|
|
232
|
+
clientSecret: config.mgmtClientSecret,
|
|
233
|
+
tsgId: config.mgmtTsgId,
|
|
234
|
+
apiEndpoint: config.mgmtEndpoint,
|
|
235
|
+
dashboardEndpoint: config.mgmtDashboardEndpoint ?? "https://api.apps.paloaltonetworks.com/aisec",
|
|
236
|
+
tokenEndpoint: config.mgmtTokenEndpoint,
|
|
237
|
+
dlpEndpoint: config.dlpEndpoint
|
|
238
|
+
};
|
|
239
|
+
}
|
|
229
240
|
function runtimeInitOptions(config) {
|
|
230
241
|
return {
|
|
231
242
|
apiKey: config.airsApiKey,
|
|
@@ -335,8 +346,8 @@ function displayValue(value) {
|
|
|
335
346
|
return typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
336
347
|
}
|
|
337
348
|
function csvCell(value) {
|
|
338
|
-
const
|
|
339
|
-
return /[",\r\n]/.test(
|
|
349
|
+
const text2 = displayValue(value);
|
|
350
|
+
return /[",\r\n]/.test(text2) ? `"${text2.replace(/"/g, '""')}"` : text2;
|
|
340
351
|
}
|
|
341
352
|
function markdownCell(value) {
|
|
342
353
|
return displayValue(value).replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/\r?\n/g, "<br>");
|
|
@@ -1377,33 +1388,33 @@ function renderMsScanList(scans, format = "pretty") {
|
|
|
1377
1388
|
}
|
|
1378
1389
|
console.log();
|
|
1379
1390
|
}
|
|
1380
|
-
function renderMsScanDetail(
|
|
1391
|
+
function renderMsScanDetail(scan2, format = "pretty") {
|
|
1381
1392
|
if (format !== "pretty") {
|
|
1382
|
-
structuredDetail("scan",
|
|
1393
|
+
structuredDetail("scan", scan2, format, () => void 0);
|
|
1383
1394
|
return;
|
|
1384
1395
|
}
|
|
1385
1396
|
ui.section("Scan Detail:");
|
|
1386
1397
|
const pairs = [
|
|
1387
|
-
["UUID",
|
|
1388
|
-
["Outcome", stateColor(
|
|
1398
|
+
["UUID", scan2.uuid],
|
|
1399
|
+
["Outcome", stateColor(scan2.evalOutcome)(scan2.evalOutcome)]
|
|
1389
1400
|
];
|
|
1390
|
-
if (
|
|
1391
|
-
pairs.push(["Origin",
|
|
1392
|
-
pairs.push(["Source",
|
|
1393
|
-
pairs.push(["Group",
|
|
1394
|
-
pairs.push(["Created",
|
|
1395
|
-
pairs.push(["Updated",
|
|
1396
|
-
if (
|
|
1397
|
-
const { rulesPassed, rulesFailed, totalRules } =
|
|
1401
|
+
if (scan2.modelUri) pairs.push(["Model URI", scan2.modelUri]);
|
|
1402
|
+
pairs.push(["Origin", scan2.scanOrigin]);
|
|
1403
|
+
pairs.push(["Source", scan2.sourceType]);
|
|
1404
|
+
pairs.push(["Group", scan2.securityGroupName]);
|
|
1405
|
+
pairs.push(["Created", scan2.createdAt]);
|
|
1406
|
+
pairs.push(["Updated", scan2.updatedAt]);
|
|
1407
|
+
if (scan2.evalSummary) {
|
|
1408
|
+
const { rulesPassed, rulesFailed, totalRules } = scan2.evalSummary;
|
|
1398
1409
|
pairs.push([
|
|
1399
1410
|
"Rules",
|
|
1400
1411
|
`${chalk7.green(`${rulesPassed} passed`)} ${chalk7.red(`${rulesFailed} failed`)} / ${totalRules} total`
|
|
1401
1412
|
]);
|
|
1402
1413
|
}
|
|
1403
1414
|
ui.keyValue(pairs);
|
|
1404
|
-
if (
|
|
1415
|
+
if (scan2.labels.length > 0) {
|
|
1405
1416
|
ui.section("Labels:");
|
|
1406
|
-
ui.keyValue(
|
|
1417
|
+
ui.keyValue(scan2.labels.map((l) => [l.key, l.value]));
|
|
1407
1418
|
}
|
|
1408
1419
|
console.log();
|
|
1409
1420
|
}
|
|
@@ -2088,9 +2099,9 @@ function renderPromptList(prompts, format = "pretty") {
|
|
|
2088
2099
|
ui.section("Prompts:");
|
|
2089
2100
|
for (const p of prompts) {
|
|
2090
2101
|
const status = p.active ? chalk8.green("active") : chalk8.dim("inactive");
|
|
2091
|
-
const
|
|
2102
|
+
const text2 = p.prompt.length > 80 ? `${p.prompt.substring(0, 77)}...` : p.prompt;
|
|
2092
2103
|
console.log(` ${chalk8.dim(p.uuid)} ${status}`);
|
|
2093
|
-
console.log(` ${
|
|
2104
|
+
console.log(` ${text2}`);
|
|
2094
2105
|
if (p.goal) console.log(` ${chalk8.dim(`Goal: ${p.goal}`)}`);
|
|
2095
2106
|
}
|
|
2096
2107
|
console.log();
|
|
@@ -2904,54 +2915,6 @@ function renderDeploymentProfileList(profiles, format = "pretty") {
|
|
|
2904
2915
|
}
|
|
2905
2916
|
console.log();
|
|
2906
2917
|
}
|
|
2907
|
-
function renderScanLogList(results, pageToken, format = "pretty") {
|
|
2908
|
-
if (results.length === 0) {
|
|
2909
|
-
if (format === "pretty") ui.emptyList("scan logs");
|
|
2910
|
-
else console.log(formatOutput([], [], format));
|
|
2911
|
-
return;
|
|
2912
|
-
}
|
|
2913
|
-
if (format !== "pretty") {
|
|
2914
|
-
const rows = results.map((r) => ({
|
|
2915
|
-
scanId: r.scan_id ?? "",
|
|
2916
|
-
timestamp: r.received_ts ?? r.timestamp ?? "",
|
|
2917
|
-
action: r.action ?? r.verdict ?? "",
|
|
2918
|
-
profile: r.profile_name ?? "",
|
|
2919
|
-
app: r.app_name ?? ""
|
|
2920
|
-
}));
|
|
2921
|
-
console.log(
|
|
2922
|
-
formatOutput(
|
|
2923
|
-
rows,
|
|
2924
|
-
[
|
|
2925
|
-
{ key: "scanId", label: "Scan ID" },
|
|
2926
|
-
{ key: "timestamp", label: "Timestamp" },
|
|
2927
|
-
{ key: "action", label: "Action" },
|
|
2928
|
-
{ key: "profile", label: "Profile" },
|
|
2929
|
-
{ key: "app", label: "App" }
|
|
2930
|
-
],
|
|
2931
|
-
format
|
|
2932
|
-
)
|
|
2933
|
-
);
|
|
2934
|
-
return;
|
|
2935
|
-
}
|
|
2936
|
-
ui.section(`Scan Logs (${results.length} results):`);
|
|
2937
|
-
for (const r of results) {
|
|
2938
|
-
const action = r.action ?? r.verdict;
|
|
2939
|
-
const app = r.app_name;
|
|
2940
|
-
const profile = r.profile_name;
|
|
2941
|
-
const ts2 = r.received_ts ?? r.timestamp;
|
|
2942
|
-
const scanId = r.scan_id;
|
|
2943
|
-
const actionColor = action === "block" ? chalk9.red : chalk9.green;
|
|
2944
|
-
if (scanId) ui.dim(scanId);
|
|
2945
|
-
console.log(
|
|
2946
|
-
` ${ts2 ? chalk9.dim(ts2) : ""} ${action ? actionColor(action) : ""} ${profile ? `[${profile}]` : ""} ${app ?? ""}`
|
|
2947
|
-
);
|
|
2948
|
-
}
|
|
2949
|
-
if (pageToken) {
|
|
2950
|
-
console.log();
|
|
2951
|
-
ui.dim(`Page token: ${pageToken}`);
|
|
2952
|
-
}
|
|
2953
|
-
console.log();
|
|
2954
|
-
}
|
|
2955
2918
|
|
|
2956
2919
|
// src/cli/confirm.ts
|
|
2957
2920
|
async function confirmOrAbort(message, force, options = {}) {
|
|
@@ -3089,8 +3052,8 @@ function registerAiGatewayInference(parent) {
|
|
|
3089
3052
|
process.once("SIGINT", interrupt);
|
|
3090
3053
|
process.once("SIGTERM", interrupt);
|
|
3091
3054
|
process.stdout.on("error", outputError);
|
|
3092
|
-
const write = (
|
|
3093
|
-
process.stdout.write(
|
|
3055
|
+
const write = (text2) => new Promise((resolve4, reject) => {
|
|
3056
|
+
process.stdout.write(text2, (error) => error ? reject(error) : resolve4());
|
|
3094
3057
|
});
|
|
3095
3058
|
const requestOptions = { signal: controller.signal };
|
|
3096
3059
|
const result = kind === "chat" ? await client.createChatCompletion(
|
|
@@ -3109,16 +3072,16 @@ function registerAiGatewayInference(parent) {
|
|
|
3109
3072
|
if (format === "json") await write(`${JSON.stringify(event)}
|
|
3110
3073
|
`);
|
|
3111
3074
|
else {
|
|
3112
|
-
const
|
|
3113
|
-
if (
|
|
3075
|
+
const text2 = responseText(event);
|
|
3076
|
+
if (text2) await write(text2);
|
|
3114
3077
|
}
|
|
3115
3078
|
if ("type" in event && (event.type === "response.failed" || event.type === "response.incomplete"))
|
|
3116
3079
|
throw new Error(`Gateway generation ended with ${event.type}`);
|
|
3117
3080
|
}
|
|
3118
3081
|
if (format === "pretty") await write("\n");
|
|
3119
3082
|
} else {
|
|
3120
|
-
const
|
|
3121
|
-
await write(`${
|
|
3083
|
+
const text2 = format === "yaml" ? dump3(result, { noRefs: true }).trimEnd() : format === "pretty" && kind !== "embeddings" ? responseText(result) : JSON.stringify(result, null, 2);
|
|
3084
|
+
await write(`${text2}
|
|
3122
3085
|
`);
|
|
3123
3086
|
if ("status" in result && (result.status === "failed" || result.status === "incomplete"))
|
|
3124
3087
|
throw new Error(`Gateway generation ended with ${result.status}`);
|
|
@@ -3188,6 +3151,7 @@ import {
|
|
|
3188
3151
|
import { dirname, join } from "path";
|
|
3189
3152
|
var AIRS_DOMAINS = [
|
|
3190
3153
|
"api.sase.paloaltonetworks.com",
|
|
3154
|
+
"api.apps.paloaltonetworks.com",
|
|
3191
3155
|
"service.api.aisecurity.paloaltonetworks.com",
|
|
3192
3156
|
"auth.apps.paloaltonetworks.com",
|
|
3193
3157
|
"api.dlp.paloaltonetworks.com"
|
|
@@ -3269,11 +3233,12 @@ function installDebugLogger(logPath) {
|
|
|
3269
3233
|
if (!isAirsUrl(url) && !inference) {
|
|
3270
3234
|
return originalFetch(input, init2);
|
|
3271
3235
|
}
|
|
3236
|
+
const dashboard = /\/v1\/mgmt\/(dashboard\/|reports\/scancontent(?:\?|$))/.test(url);
|
|
3272
3237
|
const method = init2?.method ?? (input instanceof Request ? input.method : "GET");
|
|
3273
3238
|
const reqHeaders = redactHeaders(rawHeaders);
|
|
3274
3239
|
const loggedUrl = redactUrl(url);
|
|
3275
3240
|
let reqBody;
|
|
3276
|
-
if (inference) reqBody = "[BODY OMITTED]";
|
|
3241
|
+
if (inference || dashboard) reqBody = "[BODY OMITTED]";
|
|
3277
3242
|
else if (init2?.body) {
|
|
3278
3243
|
try {
|
|
3279
3244
|
reqBody = redactDeep(JSON.parse(String(init2.body)));
|
|
@@ -3306,14 +3271,14 @@ function installDebugLogger(logPath) {
|
|
|
3306
3271
|
response.headers.forEach((v, k) => {
|
|
3307
3272
|
resHeaders[k] = v;
|
|
3308
3273
|
});
|
|
3309
|
-
if (inference || response.headers.get("content-type")?.includes("text/event-stream")) {
|
|
3274
|
+
if (inference || dashboard || response.headers.get("content-type")?.includes("text/event-stream")) {
|
|
3310
3275
|
resBody = "[BODY OMITTED]";
|
|
3311
3276
|
} else {
|
|
3312
3277
|
const clone = response.clone();
|
|
3313
3278
|
try {
|
|
3314
|
-
const
|
|
3279
|
+
const text2 = await clone.text();
|
|
3315
3280
|
try {
|
|
3316
|
-
resBody = redactDeep(JSON.parse(
|
|
3281
|
+
resBody = redactDeep(JSON.parse(text2));
|
|
3317
3282
|
} catch {
|
|
3318
3283
|
resBody = "[NON-JSON BODY OMITTED]";
|
|
3319
3284
|
}
|
|
@@ -3370,8 +3335,8 @@ function addWriteOutput(command) {
|
|
|
3370
3335
|
async function readGatewayRequest(path3) {
|
|
3371
3336
|
let parsed;
|
|
3372
3337
|
try {
|
|
3373
|
-
const
|
|
3374
|
-
parsed = path3.endsWith(".yaml") || path3.endsWith(".yml") ? load(
|
|
3338
|
+
const text2 = await readFile2(path3, "utf8");
|
|
3339
|
+
parsed = path3.endsWith(".yaml") || path3.endsWith(".yml") ? load(text2) : JSON.parse(text2);
|
|
3375
3340
|
} catch (error) {
|
|
3376
3341
|
throw new CliUsageError(
|
|
3377
3342
|
`Unable to parse --file '${path3}' as JSON or YAML: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -3586,9 +3551,9 @@ function parseBooleanOption(value) {
|
|
|
3586
3551
|
throw new CliUsageError("Expected true or false");
|
|
3587
3552
|
}
|
|
3588
3553
|
function parseIntegerOption(value) {
|
|
3589
|
-
const
|
|
3590
|
-
if (!/^-?(0|[1-9]\d*)$/.test(
|
|
3591
|
-
const parsed = Number(
|
|
3554
|
+
const text2 = String(value);
|
|
3555
|
+
if (!/^-?(0|[1-9]\d*)$/.test(text2)) throw new CliUsageError("Expected an integer");
|
|
3556
|
+
const parsed = Number(text2);
|
|
3592
3557
|
if (!Number.isSafeInteger(parsed)) throw new CliUsageError("Expected a safe integer");
|
|
3593
3558
|
return parsed;
|
|
3594
3559
|
}
|
|
@@ -3632,14 +3597,14 @@ function parseModelBindingsOption(value) {
|
|
|
3632
3597
|
}
|
|
3633
3598
|
function parseCapabilityBindingsOption(value) {
|
|
3634
3599
|
return repeatableValues(value).map((raw) => {
|
|
3635
|
-
const [
|
|
3636
|
-
const separator =
|
|
3637
|
-
if (separator <= 0 || separator ===
|
|
3600
|
+
const [identity2, enabled] = splitKeyValue(raw, "type:name");
|
|
3601
|
+
const separator = identity2.indexOf(":");
|
|
3602
|
+
if (separator <= 0 || separator === identity2.length - 1) {
|
|
3638
3603
|
throw new CliUsageError("Expected type:name=enabled");
|
|
3639
3604
|
}
|
|
3640
3605
|
return {
|
|
3641
|
-
type:
|
|
3642
|
-
name:
|
|
3606
|
+
type: identity2.slice(0, separator),
|
|
3607
|
+
name: identity2.slice(separator + 1),
|
|
3643
3608
|
enabled: parseBooleanOption(enabled)
|
|
3644
3609
|
};
|
|
3645
3610
|
});
|
|
@@ -3660,12 +3625,12 @@ function parseAssignment(raw, forceString) {
|
|
|
3660
3625
|
const separator = raw.indexOf("=");
|
|
3661
3626
|
if (separator <= 0) throw new CliUsageError("Expected path=value");
|
|
3662
3627
|
const path3 = raw.slice(0, separator);
|
|
3663
|
-
const
|
|
3664
|
-
if (forceString) return [path3,
|
|
3628
|
+
const text2 = raw.slice(separator + 1);
|
|
3629
|
+
if (forceString) return [path3, text2];
|
|
3665
3630
|
try {
|
|
3666
|
-
return [path3, JSON.parse(
|
|
3631
|
+
return [path3, JSON.parse(text2)];
|
|
3667
3632
|
} catch {
|
|
3668
|
-
return [path3,
|
|
3633
|
+
return [path3, text2];
|
|
3669
3634
|
}
|
|
3670
3635
|
}
|
|
3671
3636
|
function schemaMessage(error) {
|
|
@@ -4589,11 +4554,11 @@ function addWindowOptions(command) {
|
|
|
4589
4554
|
);
|
|
4590
4555
|
}
|
|
4591
4556
|
function windowFrom(opts) {
|
|
4592
|
-
const
|
|
4593
|
-
if (opts.start)
|
|
4594
|
-
else
|
|
4595
|
-
if (opts.end)
|
|
4596
|
-
return
|
|
4557
|
+
const window2 = { workspaceSlug: opts.workspace };
|
|
4558
|
+
if (opts.start) window2.start = parseNamedDate2(opts.start, "--start");
|
|
4559
|
+
else window2.days = parsePositiveInteger2(opts.days ?? "7", "--days");
|
|
4560
|
+
if (opts.end) window2.end = parseNamedDate2(opts.end, "--end");
|
|
4561
|
+
return window2;
|
|
4597
4562
|
}
|
|
4598
4563
|
function parsePositiveInteger2(value, flag) {
|
|
4599
4564
|
try {
|
|
@@ -5331,8 +5296,8 @@ async function withTimeout(promise, ms) {
|
|
|
5331
5296
|
try {
|
|
5332
5297
|
return await Promise.race([
|
|
5333
5298
|
promise,
|
|
5334
|
-
new Promise((
|
|
5335
|
-
timer = setTimeout(() =>
|
|
5299
|
+
new Promise((resolve4) => {
|
|
5300
|
+
timer = setTimeout(() => resolve4(TIMED_OUT), ms);
|
|
5336
5301
|
})
|
|
5337
5302
|
]);
|
|
5338
5303
|
} finally {
|
|
@@ -5602,12 +5567,12 @@ function hasBin(bin) {
|
|
|
5602
5567
|
}
|
|
5603
5568
|
}
|
|
5604
5569
|
function run(bin, args, label) {
|
|
5605
|
-
return new Promise((
|
|
5570
|
+
return new Promise((resolve4, reject) => {
|
|
5606
5571
|
const child = spawn(bin, args, { stdio: "inherit" });
|
|
5607
5572
|
child.on("error", (err) => reject(new Error(`Failed to start ${bin}: ${err.message}`)));
|
|
5608
5573
|
child.on("close", (code) => {
|
|
5609
5574
|
if (code !== 0) reject(new Error(`${label} failed with exit code ${code}`));
|
|
5610
|
-
else
|
|
5575
|
+
else resolve4();
|
|
5611
5576
|
});
|
|
5612
5577
|
});
|
|
5613
5578
|
}
|
|
@@ -5928,8 +5893,8 @@ function registerModelSecurityCommand(program) {
|
|
|
5928
5893
|
const fmt = opts.output;
|
|
5929
5894
|
if (fmt === "pretty") renderModelSecurityHeader();
|
|
5930
5895
|
const service = await createService2();
|
|
5931
|
-
const
|
|
5932
|
-
renderMsScanDetail(
|
|
5896
|
+
const scan2 = await service.getScan(uuid);
|
|
5897
|
+
renderMsScanDetail(scan2, fmt);
|
|
5933
5898
|
} catch (err) {
|
|
5934
5899
|
fail(err);
|
|
5935
5900
|
}
|
|
@@ -5939,9 +5904,9 @@ function registerModelSecurityCommand(program) {
|
|
|
5939
5904
|
renderModelSecurityHeader();
|
|
5940
5905
|
const service = await createService2();
|
|
5941
5906
|
const config = JSON.parse(fs.readFileSync(opts.config, "utf-8"));
|
|
5942
|
-
const
|
|
5943
|
-
ui.success(`Scan created: ${
|
|
5944
|
-
renderMsScanDetail(
|
|
5907
|
+
const scan2 = await service.createScan(config);
|
|
5908
|
+
ui.success(`Scan created: ${scan2.uuid}`);
|
|
5909
|
+
renderMsScanDetail(scan2);
|
|
5945
5910
|
} catch (err) {
|
|
5946
5911
|
fail(err);
|
|
5947
5912
|
}
|
|
@@ -7488,7 +7453,7 @@ function buildDataProtection(flags) {
|
|
|
7488
7453
|
const dld = { action: flags.dlpAction };
|
|
7489
7454
|
const profiles = parseList(flags.dlpProfiles);
|
|
7490
7455
|
if (profiles) {
|
|
7491
|
-
dld.member = profiles.map((
|
|
7456
|
+
dld.member = profiles.map((text2) => ({ text: text2 }));
|
|
7492
7457
|
}
|
|
7493
7458
|
if (flags.maskDataInline != null) {
|
|
7494
7459
|
dld["mask-data-inline"] = flags.maskDataInline;
|
|
@@ -8975,15 +8940,263 @@ function registerCleanupCommand(parent) {
|
|
|
8975
8940
|
});
|
|
8976
8941
|
}
|
|
8977
8942
|
|
|
8978
|
-
// src/cli/commands/runtime-
|
|
8943
|
+
// src/cli/commands/runtime-dashboard.ts
|
|
8979
8944
|
import { lstat } from "fs/promises";
|
|
8980
8945
|
import { resolve as resolve2 } from "path";
|
|
8946
|
+
import { dump as dump5 } from "js-yaml";
|
|
8947
|
+
function integer2(value, name, minimum = 0) {
|
|
8948
|
+
const n = Number(value);
|
|
8949
|
+
if (!/^\d+$/.test(String(value)) || !Number.isSafeInteger(n) || n < minimum)
|
|
8950
|
+
throw new CliUsageError(`${name} must be a safe integer >= ${minimum}`);
|
|
8951
|
+
return n;
|
|
8952
|
+
}
|
|
8953
|
+
function text(value, name) {
|
|
8954
|
+
if (typeof value !== "string" || !value.trim()) throw new CliUsageError(`${name} is required`);
|
|
8955
|
+
return value;
|
|
8956
|
+
}
|
|
8957
|
+
function window(opts) {
|
|
8958
|
+
return {
|
|
8959
|
+
timeInterval: integer2(opts.interval, "--interval", 1),
|
|
8960
|
+
timeUnit: text(opts.unit, "--unit")
|
|
8961
|
+
};
|
|
8962
|
+
}
|
|
8963
|
+
function identity(opts) {
|
|
8964
|
+
return {
|
|
8965
|
+
sessionId: text(opts.sessionId, "--session-id"),
|
|
8966
|
+
appId: text(opts.appId, "--app-id"),
|
|
8967
|
+
appName: text(opts.appName, "--app-name"),
|
|
8968
|
+
...window(opts)
|
|
8969
|
+
};
|
|
8970
|
+
}
|
|
8971
|
+
function scan(opts) {
|
|
8972
|
+
return {
|
|
8973
|
+
scanId: text(opts.scanId, "--scan-id"),
|
|
8974
|
+
scanSubReqId: integer2(opts.scanSubReqId, "--scan-sub-req-id")
|
|
8975
|
+
};
|
|
8976
|
+
}
|
|
8977
|
+
function timeOptions(command, interval = "1", unit = "day") {
|
|
8978
|
+
return command.option("--interval <n>", "Look-back interval", interval).option("--unit <unit>", "API time unit (windows are deployment-dependent)", unit);
|
|
8979
|
+
}
|
|
8980
|
+
function pageOptions(command) {
|
|
8981
|
+
return command.option("--limit <n>", "Items per page", "25").option("--offset <n>", "Item offset", "0");
|
|
8982
|
+
}
|
|
8983
|
+
function sessionOptions(command) {
|
|
8984
|
+
return timeOptions(
|
|
8985
|
+
command.requiredOption("--session-id <id>", "Session identity").requiredOption("--app-id <id>", "Application identity").requiredOption("--app-name <name>", "Exact dashboard application name"),
|
|
8986
|
+
"30",
|
|
8987
|
+
"days"
|
|
8988
|
+
);
|
|
8989
|
+
}
|
|
8990
|
+
function scanOptions(command) {
|
|
8991
|
+
return command.requiredOption("--scan-id <id>", "Scan identity").requiredOption("--scan-sub-req-id <n>", "Sub-request index, including zero");
|
|
8992
|
+
}
|
|
8993
|
+
function registerRuntimeDashboardCommands(runtime) {
|
|
8994
|
+
const dashboard = runtime.command("dashboard").description("SCM application summaries and daily telemetry (OAuth)");
|
|
8995
|
+
const sessions = runtime.command("sessions").description("Verified SCM session inventory and drill-down (OAuth)");
|
|
8996
|
+
function register6(command, prepare) {
|
|
8997
|
+
command.option("--output <format>", "Output format: pretty, json, yaml").action(async (opts) => {
|
|
8998
|
+
try {
|
|
8999
|
+
const read = prepare(opts);
|
|
9000
|
+
const format = await resolveOutput(command, opts, {
|
|
9001
|
+
allowed: ["pretty", "json", "yaml"]
|
|
9002
|
+
});
|
|
9003
|
+
if (command.name() === "scan-content") {
|
|
9004
|
+
if (Boolean(opts.showContent) === Boolean(opts.outputFile))
|
|
9005
|
+
throw new CliUsageError(
|
|
9006
|
+
"Scan content requires exactly one of --show-content or --output-file <new-path>"
|
|
9007
|
+
);
|
|
9008
|
+
if (opts.outputFile) {
|
|
9009
|
+
const path3 = resolve2(text(opts.outputFile, "--output-file"));
|
|
9010
|
+
const existing = await lstat(path3).catch((error) => {
|
|
9011
|
+
if (error.code !== "ENOENT") throw error;
|
|
9012
|
+
return void 0;
|
|
9013
|
+
});
|
|
9014
|
+
if (existing) throw new Error("Destination exists");
|
|
9015
|
+
}
|
|
9016
|
+
}
|
|
9017
|
+
const config = await loadConfig();
|
|
9018
|
+
const client = getOrCreateManagementClient({
|
|
9019
|
+
...managementClientOptions(config),
|
|
9020
|
+
numRetries: 0
|
|
9021
|
+
});
|
|
9022
|
+
const result = await read(client.dashboard);
|
|
9023
|
+
const output = format === "yaml" ? dump5(result, { noRefs: true, lineWidth: -1 }).trimEnd() : JSON.stringify(result, null, 2);
|
|
9024
|
+
if (command.name() === "scan-content" && opts.outputFile) {
|
|
9025
|
+
await writeReportFile(resolve2(String(opts.outputFile)), `${output}
|
|
9026
|
+
`);
|
|
9027
|
+
ui.status("Scan content exported to the requested private file (0600).");
|
|
9028
|
+
} else console.log(output);
|
|
9029
|
+
} catch (error) {
|
|
9030
|
+
if (error instanceof CliUsageError) usageError(error.message);
|
|
9031
|
+
const status = error?.statusCode;
|
|
9032
|
+
ui.error(
|
|
9033
|
+
`Dashboard operation failed${typeof status === "number" && status >= 400 && status <= 599 ? ` (HTTP ${status})` : ""}. Check credentials, query options, and any output destination. No existing file is overwritten.`
|
|
9034
|
+
);
|
|
9035
|
+
process.exitCode = 1;
|
|
9036
|
+
}
|
|
9037
|
+
});
|
|
9038
|
+
}
|
|
9039
|
+
register6(
|
|
9040
|
+
pageOptions(
|
|
9041
|
+
timeOptions(
|
|
9042
|
+
dashboard.command("applications").description("One page of application activity")
|
|
9043
|
+
)
|
|
9044
|
+
),
|
|
9045
|
+
(opts) => {
|
|
9046
|
+
const interval = integer2(opts.interval, "--interval", 1);
|
|
9047
|
+
const unit = text(opts.unit, "--unit");
|
|
9048
|
+
if (![1, 7, 30, 60].includes(interval) || !["day", "days", "hour"].includes(unit))
|
|
9049
|
+
throw new CliUsageError(
|
|
9050
|
+
"Application overview supports interval 1/7/30/60 and unit day/days/hour"
|
|
9051
|
+
);
|
|
9052
|
+
const query = {
|
|
9053
|
+
timeInterval: interval,
|
|
9054
|
+
timeUnit: unit,
|
|
9055
|
+
limit: integer2(opts.limit, "--limit", 1),
|
|
9056
|
+
offset: integer2(opts.offset, "--offset")
|
|
9057
|
+
};
|
|
9058
|
+
return (client) => client.applicationsOverview(query);
|
|
9059
|
+
}
|
|
9060
|
+
);
|
|
9061
|
+
for (const [name, method] of [
|
|
9062
|
+
["application", "application"],
|
|
9063
|
+
["application-violations", "applicationViolationBreakdown"]
|
|
9064
|
+
]) {
|
|
9065
|
+
register6(
|
|
9066
|
+
timeOptions(
|
|
9067
|
+
dashboard.command(name).requiredOption("--app-id <id>", "Application identity").requiredOption("--app-name <name>", "Exact dashboard application name"),
|
|
9068
|
+
"30",
|
|
9069
|
+
"days"
|
|
9070
|
+
),
|
|
9071
|
+
(opts) => {
|
|
9072
|
+
const interval = integer2(opts.interval, "--interval", 1);
|
|
9073
|
+
if (![7, 30, 60].includes(interval) || opts.unit !== "days")
|
|
9074
|
+
throw new CliUsageError("Application detail requires 7, 30, or 60 days");
|
|
9075
|
+
const query = {
|
|
9076
|
+
appId: text(opts.appId, "--app-id"),
|
|
9077
|
+
appName: text(opts.appName, "--app-name"),
|
|
9078
|
+
timeInterval: interval,
|
|
9079
|
+
timeUnit: "days"
|
|
9080
|
+
};
|
|
9081
|
+
return (client) => client[method](query);
|
|
9082
|
+
}
|
|
9083
|
+
);
|
|
9084
|
+
}
|
|
9085
|
+
for (const [name, method, days] of [
|
|
9086
|
+
["top-applications", "topApplicationsViolations", false],
|
|
9087
|
+
["violations-trend", "applicationsViolationsTrend", false],
|
|
9088
|
+
["apps-list", "appsList", true]
|
|
9089
|
+
]) {
|
|
9090
|
+
register6(
|
|
9091
|
+
timeOptions(dashboard.command(name), days ? "30" : "1", days ? "days" : "day"),
|
|
9092
|
+
(opts) => {
|
|
9093
|
+
const query = window(opts);
|
|
9094
|
+
return (client) => client[method](query);
|
|
9095
|
+
}
|
|
9096
|
+
);
|
|
9097
|
+
}
|
|
9098
|
+
register6(timeOptions(sessions.command("chart")), (opts) => {
|
|
9099
|
+
const query = window(opts);
|
|
9100
|
+
return (client) => client.sessionsChart(query);
|
|
9101
|
+
});
|
|
9102
|
+
register6(
|
|
9103
|
+
pageOptions(
|
|
9104
|
+
timeOptions(
|
|
9105
|
+
sessions.command("list").description("Session entries; --all walks pages with completeness checks")
|
|
9106
|
+
)
|
|
9107
|
+
).option("--all", "Walk all pages (up to 100 pages)").option("--max <n>", "Item cap with --all; 0 removes item cap", "10000"),
|
|
9108
|
+
(opts) => {
|
|
9109
|
+
const query = {
|
|
9110
|
+
...window(opts),
|
|
9111
|
+
limit: integer2(opts.limit, "--limit", 1),
|
|
9112
|
+
offset: integer2(opts.offset, "--offset")
|
|
9113
|
+
};
|
|
9114
|
+
const maximum = integer2(opts.max, "--max");
|
|
9115
|
+
return async (client) => {
|
|
9116
|
+
const items = [];
|
|
9117
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9118
|
+
let expectedTotal;
|
|
9119
|
+
let offset = query.offset;
|
|
9120
|
+
for (let pageIndex = 0; pageIndex < 100; pageIndex++) {
|
|
9121
|
+
const page = await client.sessionsOverview({ ...query, offset });
|
|
9122
|
+
if (!opts.all) {
|
|
9123
|
+
ui.status(
|
|
9124
|
+
`Returned ${page.items.length} of ${page.pagination.total_items ?? "unknown"} sessions; this is one page.`
|
|
9125
|
+
);
|
|
9126
|
+
return page.items;
|
|
9127
|
+
}
|
|
9128
|
+
const total = page.pagination.total_items;
|
|
9129
|
+
if (!Number.isSafeInteger(total) || total < 0 || page.pagination.skip !== offset || page.pagination.limit !== query.limit || page.items.length > query.limit || expectedTotal !== void 0 && total !== expectedTotal)
|
|
9130
|
+
throw new Error("Unstable pagination");
|
|
9131
|
+
expectedTotal = total;
|
|
9132
|
+
for (const item of page.items) {
|
|
9133
|
+
const key = JSON.stringify([
|
|
9134
|
+
item.application_id,
|
|
9135
|
+
item.application_name,
|
|
9136
|
+
item.session_id
|
|
9137
|
+
]);
|
|
9138
|
+
if (seen.has(key)) throw new Error("Overlapping session pages");
|
|
9139
|
+
seen.add(key);
|
|
9140
|
+
items.push(item);
|
|
9141
|
+
}
|
|
9142
|
+
offset += page.items.length;
|
|
9143
|
+
if (offset > total || page.items.length === 0 && offset < total)
|
|
9144
|
+
throw new Error("Incomplete inventory");
|
|
9145
|
+
if (maximum > 0 && items.length > maximum) {
|
|
9146
|
+
ui.status("Session item cap reached; output is partial.");
|
|
9147
|
+
process.exitCode = 1;
|
|
9148
|
+
return items.slice(0, maximum);
|
|
9149
|
+
}
|
|
9150
|
+
if (offset === total) {
|
|
9151
|
+
ui.status(
|
|
9152
|
+
`Retrieved ${items.length} sessions from offset ${query.offset}; pagination complete.`
|
|
9153
|
+
);
|
|
9154
|
+
return items;
|
|
9155
|
+
}
|
|
9156
|
+
if (maximum > 0 && items.length === maximum) {
|
|
9157
|
+
ui.status("Session item cap reached; output is partial.");
|
|
9158
|
+
process.exitCode = 1;
|
|
9159
|
+
return items;
|
|
9160
|
+
}
|
|
9161
|
+
}
|
|
9162
|
+
ui.status("Session page cap reached; output is partial.");
|
|
9163
|
+
process.exitCode = 1;
|
|
9164
|
+
return items;
|
|
9165
|
+
};
|
|
9166
|
+
}
|
|
9167
|
+
);
|
|
9168
|
+
register6(pageOptions(sessionOptions(sessions.command("get"))), (opts) => {
|
|
9169
|
+
const query = {
|
|
9170
|
+
...identity(opts),
|
|
9171
|
+
limit: integer2(opts.limit, "--limit", 1),
|
|
9172
|
+
offset: integer2(opts.offset, "--offset")
|
|
9173
|
+
};
|
|
9174
|
+
return (client) => client.session(query);
|
|
9175
|
+
});
|
|
9176
|
+
register6(scanOptions(sessionOptions(sessions.command("transaction"))), (opts) => {
|
|
9177
|
+
const query = { ...identity(opts), ...scan(opts) };
|
|
9178
|
+
return (client) => client.sessionTransaction(query);
|
|
9179
|
+
});
|
|
9180
|
+
register6(
|
|
9181
|
+
scanOptions(
|
|
9182
|
+
sessions.command("scan-content").description("Explicit sensitive scan-content read; never automatically fetched")
|
|
9183
|
+
).option("--show-content", "Explicitly display stored content on stdout").option("--output-file <path>", "Private new file for stored content (no overwrite)"),
|
|
9184
|
+
(opts) => {
|
|
9185
|
+
const query = scan(opts);
|
|
9186
|
+
return (client) => client.scanContent(query);
|
|
9187
|
+
}
|
|
9188
|
+
);
|
|
9189
|
+
}
|
|
9190
|
+
|
|
9191
|
+
// src/cli/commands/runtime-report.ts
|
|
9192
|
+
import { lstat as lstat2 } from "fs/promises";
|
|
9193
|
+
import { resolve as resolve3 } from "path";
|
|
8981
9194
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
8982
9195
|
function registerRuntimeReportCommand(runtime) {
|
|
8983
9196
|
const command = runtime.command("report").description("Generate a daily AI Runtime Security environment dashboard (read-only)").option("--output <format>", "Deliverable format: html or markdown (default: html)").option(
|
|
8984
9197
|
"--output-file <path>",
|
|
8985
9198
|
"Destination (default: new timestamped file in CWD); - for stdout; never overwrite"
|
|
8986
|
-
).option("--title <text>", "Report title", "Daily environment report").option("--max-pages <n>", "Page budget per source, 1\u2013100 (
|
|
9199
|
+
).option("--title <text>", "Report title", "Daily environment report").option("--max-pages <n>", "Page budget per source, 1\u2013100 (25 sessions/page)", "40").option("--strict", "Exit 1 after writing if any source is incomplete").addHelpText(
|
|
8987
9200
|
"after",
|
|
8988
9201
|
examples(
|
|
8989
9202
|
"airs runtime report --output-file ./airs-daily.html",
|
|
@@ -9003,10 +9216,10 @@ function registerRuntimeReportCommand(runtime) {
|
|
|
9003
9216
|
if (opts.outputFile !== void 0 && !opts.outputFile.trim())
|
|
9004
9217
|
usageError("--output-file cannot be empty");
|
|
9005
9218
|
const defaultName = `airs-runtime-report-${(/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-")}-${randomUUID4().slice(0, 8)}.${format === "html" ? "html" : "md"}`;
|
|
9006
|
-
const outputPath = opts.outputFile === "-" ? void 0 :
|
|
9219
|
+
const outputPath = opts.outputFile === "-" ? void 0 : resolve3(opts.outputFile ?? defaultName);
|
|
9007
9220
|
try {
|
|
9008
9221
|
if (outputPath) {
|
|
9009
|
-
const existing = await
|
|
9222
|
+
const existing = await lstat2(outputPath).catch((error) => {
|
|
9010
9223
|
if (error.code !== "ENOENT") throw error;
|
|
9011
9224
|
return void 0;
|
|
9012
9225
|
});
|
|
@@ -9014,11 +9227,7 @@ function registerRuntimeReportCommand(runtime) {
|
|
|
9014
9227
|
}
|
|
9015
9228
|
const config = await loadConfig();
|
|
9016
9229
|
const client = getOrCreateManagementClient({
|
|
9017
|
-
|
|
9018
|
-
clientSecret: config.mgmtClientSecret,
|
|
9019
|
-
tsgId: config.mgmtTsgId,
|
|
9020
|
-
apiEndpoint: config.mgmtEndpoint,
|
|
9021
|
-
tokenEndpoint: config.mgmtTokenEndpoint,
|
|
9230
|
+
...managementClientOptions(config),
|
|
9022
9231
|
numRetries: 0
|
|
9023
9232
|
});
|
|
9024
9233
|
ui.status("Collecting daily runtime activity and current configuration (read-only)...");
|
|
@@ -9293,14 +9502,14 @@ async function evalTopic(scanner, profileName, topicName, cases, concurrency = 5
|
|
|
9293
9502
|
throw new Error(`scanBatch returned ${scanResults.length} results for ${cases.length} prompts`);
|
|
9294
9503
|
}
|
|
9295
9504
|
const testResults = cases.map((testCase, i) => {
|
|
9296
|
-
const
|
|
9505
|
+
const scan2 = scanResults[i];
|
|
9297
9506
|
return {
|
|
9298
9507
|
testCase,
|
|
9299
|
-
actualTriggered:
|
|
9300
|
-
scanAction:
|
|
9301
|
-
scanId:
|
|
9302
|
-
reportId:
|
|
9303
|
-
correct: testCase.expectedTriggered ===
|
|
9508
|
+
actualTriggered: scan2.triggered,
|
|
9509
|
+
scanAction: scan2.action,
|
|
9510
|
+
scanId: scan2.scanId,
|
|
9511
|
+
reportId: scan2.reportId,
|
|
9512
|
+
correct: testCase.expectedTriggered === scan2.triggered
|
|
9304
9513
|
};
|
|
9305
9514
|
});
|
|
9306
9515
|
const metrics = computeMetrics(testResults);
|
|
@@ -9523,16 +9732,12 @@ function parsePositiveInteger3(value, optionName2) {
|
|
|
9523
9732
|
}
|
|
9524
9733
|
async function createMgmtService() {
|
|
9525
9734
|
const config = await loadConfig();
|
|
9526
|
-
return new SdkManagementService(
|
|
9527
|
-
clientId: config.mgmtClientId,
|
|
9528
|
-
clientSecret: config.mgmtClientSecret,
|
|
9529
|
-
tsgId: config.mgmtTsgId,
|
|
9530
|
-
tokenEndpoint: config.mgmtTokenEndpoint
|
|
9531
|
-
});
|
|
9735
|
+
return new SdkManagementService(managementClientOptions(config));
|
|
9532
9736
|
}
|
|
9533
9737
|
function registerRuntimeCommand(program) {
|
|
9534
9738
|
const runtime = program.command("runtime").description("Runtime prompt scanning against AIRS profiles");
|
|
9535
9739
|
registerRuntimeReportCommand(runtime);
|
|
9740
|
+
registerRuntimeDashboardCommands(runtime);
|
|
9536
9741
|
const apiKeys = runtime.command("api-keys").description("Manage AIRS API keys");
|
|
9537
9742
|
const apiKeysList = registerListFlags(apiKeys.command("list"), { dialect: "offset" }).description("List API keys").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml").action(async (opts) => {
|
|
9538
9743
|
try {
|
|
@@ -10191,26 +10396,14 @@ function registerRuntimeCommand(program) {
|
|
|
10191
10396
|
fail(err);
|
|
10192
10397
|
}
|
|
10193
10398
|
});
|
|
10194
|
-
const scanLogs = runtime.command("scan-logs").description("
|
|
10195
|
-
const scanLogsQuery = scanLogs.command("query").description("
|
|
10399
|
+
const scanLogs = runtime.command("scan-logs").description("BROKEN legacy retrieval \u2014 under refactor; use runtime sessions");
|
|
10400
|
+
const scanLogsQuery = scanLogs.command("query").description("Unavailable legacy query; use runtime sessions list").requiredOption("--interval <n>", "Time interval").requiredOption("--unit <unit>", "Time unit (hours)").option("--filter <filter>", "Filter: all, benign, threat", "all").option("--limit <n>", "Max results per page (API page size)", "50").option("--offset <n>", "Starting offset \u2014 rounds down to a page boundary", "0").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml");
|
|
10196
10401
|
registerPageAliases(scanLogsQuery, { sizeFlag: "--page-size", sizeKey: "pageSize" });
|
|
10197
|
-
scanLogsQuery.action(
|
|
10198
|
-
|
|
10199
|
-
|
|
10200
|
-
|
|
10201
|
-
|
|
10202
|
-
const service = await createMgmtService();
|
|
10203
|
-
const result = await service.queryScanLogs({
|
|
10204
|
-
timeInterval: Number.parseInt(opts.interval, 10),
|
|
10205
|
-
timeUnit: opts.unit,
|
|
10206
|
-
pageNumber: page ?? 1,
|
|
10207
|
-
pageSize: size ?? 50,
|
|
10208
|
-
filter: opts.filter
|
|
10209
|
-
});
|
|
10210
|
-
renderScanLogList(result.results, result.pageToken, fmt);
|
|
10211
|
-
} catch (err) {
|
|
10212
|
-
fail(err);
|
|
10213
|
-
}
|
|
10402
|
+
scanLogsQuery.action(() => {
|
|
10403
|
+
ui.error(
|
|
10404
|
+
"Legacy scan-logs retrieval is broken and under refactor. Empty results do not mean no activity. Use airs runtime sessions list --interval 1 --unit day --output json, or airs runtime report."
|
|
10405
|
+
);
|
|
10406
|
+
process.exitCode = 1;
|
|
10214
10407
|
});
|
|
10215
10408
|
const topics = runtime.command("topics").description("Manage AIRS custom topics and guardrail generation");
|
|
10216
10409
|
registerApplyCommand(topics);
|
|
@@ -10361,7 +10554,7 @@ function buildProgram() {
|
|
|
10361
10554
|
const isRuntimeReport = actionCommand.name() === "report" && actionCommand.parent?.name() === "runtime";
|
|
10362
10555
|
if (isRuntimeReport && (root.debug || /^(1|true|yes|on)$/i.test(process.env.PANW_AI_SEC_DEBUG?.trim() ?? "")))
|
|
10363
10556
|
usageError(
|
|
10364
|
-
"Disable --debug and PANW_AI_SEC_DEBUG for environment reports to avoid persisting
|
|
10557
|
+
"Disable --debug and PANW_AI_SEC_DEBUG for environment reports to avoid persisting sensitive traffic content"
|
|
10365
10558
|
);
|
|
10366
10559
|
if (!isRuntimeReport && READ_COMMAND_NAMES.has(actionCommand.name()) && actionCommand.options.some((option) => option.long === "--output")) {
|
|
10367
10560
|
try {
|