@cdot65/prisma-airs-cli 4.4.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 +13 -3
- package/dist/{chunk-LF6JPUA4.js → chunk-IRLIFMGQ.js} +781 -16
- package/dist/cli/index.js +433 -173
- package/dist/index.d.ts +141 -2
- package/dist/index.js +11 -3
- package/package.json +2 -2
package/dist/cli/index.js
CHANGED
|
@@ -9,18 +9,22 @@ import {
|
|
|
9
9
|
SdkPromptSetService,
|
|
10
10
|
SdkRedTeamService,
|
|
11
11
|
SdkRuntimeService,
|
|
12
|
+
collectRuntimeDailyReport,
|
|
12
13
|
computeMetrics,
|
|
13
14
|
getOrCreateManagementClient,
|
|
14
15
|
inspectConfig,
|
|
15
16
|
loadConfig,
|
|
16
17
|
readBackupDir,
|
|
17
18
|
readBackupFile,
|
|
19
|
+
renderRuntimeReportHtml,
|
|
20
|
+
renderRuntimeReportMarkdown,
|
|
18
21
|
resolveConfigFilePath,
|
|
19
22
|
resolveOutputDir,
|
|
20
23
|
sanitizeFilename,
|
|
21
24
|
validateTopic,
|
|
22
|
-
writeBackupFile
|
|
23
|
-
|
|
25
|
+
writeBackupFile,
|
|
26
|
+
writeReportFile
|
|
27
|
+
} from "../chunk-IRLIFMGQ.js";
|
|
24
28
|
|
|
25
29
|
// src/cli/index.ts
|
|
26
30
|
import "dotenv/config";
|
|
@@ -39,8 +43,8 @@ function installProcessGuards() {
|
|
|
39
43
|
}
|
|
40
44
|
|
|
41
45
|
// src/cli/program.ts
|
|
46
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
42
47
|
import { readFileSync as readFileSync4 } from "fs";
|
|
43
|
-
import { homedir } from "os";
|
|
44
48
|
import { dirname as dirname4, join as join4 } from "path";
|
|
45
49
|
import { fileURLToPath } from "url";
|
|
46
50
|
import { Command } from "commander";
|
|
@@ -222,6 +226,17 @@ var SdkAiGatewayService = class {
|
|
|
222
226
|
};
|
|
223
227
|
|
|
224
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
|
+
}
|
|
225
240
|
function runtimeInitOptions(config) {
|
|
226
241
|
return {
|
|
227
242
|
apiKey: config.airsApiKey,
|
|
@@ -331,8 +346,8 @@ function displayValue(value) {
|
|
|
331
346
|
return typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
332
347
|
}
|
|
333
348
|
function csvCell(value) {
|
|
334
|
-
const
|
|
335
|
-
return /[",\r\n]/.test(
|
|
349
|
+
const text2 = displayValue(value);
|
|
350
|
+
return /[",\r\n]/.test(text2) ? `"${text2.replace(/"/g, '""')}"` : text2;
|
|
336
351
|
}
|
|
337
352
|
function markdownCell(value) {
|
|
338
353
|
return displayValue(value).replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/\r?\n/g, "<br>");
|
|
@@ -1373,33 +1388,33 @@ function renderMsScanList(scans, format = "pretty") {
|
|
|
1373
1388
|
}
|
|
1374
1389
|
console.log();
|
|
1375
1390
|
}
|
|
1376
|
-
function renderMsScanDetail(
|
|
1391
|
+
function renderMsScanDetail(scan2, format = "pretty") {
|
|
1377
1392
|
if (format !== "pretty") {
|
|
1378
|
-
structuredDetail("scan",
|
|
1393
|
+
structuredDetail("scan", scan2, format, () => void 0);
|
|
1379
1394
|
return;
|
|
1380
1395
|
}
|
|
1381
1396
|
ui.section("Scan Detail:");
|
|
1382
1397
|
const pairs = [
|
|
1383
|
-
["UUID",
|
|
1384
|
-
["Outcome", stateColor(
|
|
1398
|
+
["UUID", scan2.uuid],
|
|
1399
|
+
["Outcome", stateColor(scan2.evalOutcome)(scan2.evalOutcome)]
|
|
1385
1400
|
];
|
|
1386
|
-
if (
|
|
1387
|
-
pairs.push(["Origin",
|
|
1388
|
-
pairs.push(["Source",
|
|
1389
|
-
pairs.push(["Group",
|
|
1390
|
-
pairs.push(["Created",
|
|
1391
|
-
pairs.push(["Updated",
|
|
1392
|
-
if (
|
|
1393
|
-
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;
|
|
1394
1409
|
pairs.push([
|
|
1395
1410
|
"Rules",
|
|
1396
1411
|
`${chalk7.green(`${rulesPassed} passed`)} ${chalk7.red(`${rulesFailed} failed`)} / ${totalRules} total`
|
|
1397
1412
|
]);
|
|
1398
1413
|
}
|
|
1399
1414
|
ui.keyValue(pairs);
|
|
1400
|
-
if (
|
|
1415
|
+
if (scan2.labels.length > 0) {
|
|
1401
1416
|
ui.section("Labels:");
|
|
1402
|
-
ui.keyValue(
|
|
1417
|
+
ui.keyValue(scan2.labels.map((l) => [l.key, l.value]));
|
|
1403
1418
|
}
|
|
1404
1419
|
console.log();
|
|
1405
1420
|
}
|
|
@@ -2084,9 +2099,9 @@ function renderPromptList(prompts, format = "pretty") {
|
|
|
2084
2099
|
ui.section("Prompts:");
|
|
2085
2100
|
for (const p of prompts) {
|
|
2086
2101
|
const status = p.active ? chalk8.green("active") : chalk8.dim("inactive");
|
|
2087
|
-
const
|
|
2102
|
+
const text2 = p.prompt.length > 80 ? `${p.prompt.substring(0, 77)}...` : p.prompt;
|
|
2088
2103
|
console.log(` ${chalk8.dim(p.uuid)} ${status}`);
|
|
2089
|
-
console.log(` ${
|
|
2104
|
+
console.log(` ${text2}`);
|
|
2090
2105
|
if (p.goal) console.log(` ${chalk8.dim(`Goal: ${p.goal}`)}`);
|
|
2091
2106
|
}
|
|
2092
2107
|
console.log();
|
|
@@ -2900,54 +2915,6 @@ function renderDeploymentProfileList(profiles, format = "pretty") {
|
|
|
2900
2915
|
}
|
|
2901
2916
|
console.log();
|
|
2902
2917
|
}
|
|
2903
|
-
function renderScanLogList(results, pageToken, format = "pretty") {
|
|
2904
|
-
if (results.length === 0) {
|
|
2905
|
-
if (format === "pretty") ui.emptyList("scan logs");
|
|
2906
|
-
else console.log(formatOutput([], [], format));
|
|
2907
|
-
return;
|
|
2908
|
-
}
|
|
2909
|
-
if (format !== "pretty") {
|
|
2910
|
-
const rows = results.map((r) => ({
|
|
2911
|
-
scanId: r.scan_id ?? "",
|
|
2912
|
-
timestamp: r.received_ts ?? r.timestamp ?? "",
|
|
2913
|
-
action: r.action ?? r.verdict ?? "",
|
|
2914
|
-
profile: r.profile_name ?? "",
|
|
2915
|
-
app: r.app_name ?? ""
|
|
2916
|
-
}));
|
|
2917
|
-
console.log(
|
|
2918
|
-
formatOutput(
|
|
2919
|
-
rows,
|
|
2920
|
-
[
|
|
2921
|
-
{ key: "scanId", label: "Scan ID" },
|
|
2922
|
-
{ key: "timestamp", label: "Timestamp" },
|
|
2923
|
-
{ key: "action", label: "Action" },
|
|
2924
|
-
{ key: "profile", label: "Profile" },
|
|
2925
|
-
{ key: "app", label: "App" }
|
|
2926
|
-
],
|
|
2927
|
-
format
|
|
2928
|
-
)
|
|
2929
|
-
);
|
|
2930
|
-
return;
|
|
2931
|
-
}
|
|
2932
|
-
ui.section(`Scan Logs (${results.length} results):`);
|
|
2933
|
-
for (const r of results) {
|
|
2934
|
-
const action = r.action ?? r.verdict;
|
|
2935
|
-
const app = r.app_name;
|
|
2936
|
-
const profile = r.profile_name;
|
|
2937
|
-
const ts2 = r.received_ts ?? r.timestamp;
|
|
2938
|
-
const scanId = r.scan_id;
|
|
2939
|
-
const actionColor = action === "block" ? chalk9.red : chalk9.green;
|
|
2940
|
-
if (scanId) ui.dim(scanId);
|
|
2941
|
-
console.log(
|
|
2942
|
-
` ${ts2 ? chalk9.dim(ts2) : ""} ${action ? actionColor(action) : ""} ${profile ? `[${profile}]` : ""} ${app ?? ""}`
|
|
2943
|
-
);
|
|
2944
|
-
}
|
|
2945
|
-
if (pageToken) {
|
|
2946
|
-
console.log();
|
|
2947
|
-
ui.dim(`Page token: ${pageToken}`);
|
|
2948
|
-
}
|
|
2949
|
-
console.log();
|
|
2950
|
-
}
|
|
2951
2918
|
|
|
2952
2919
|
// src/cli/confirm.ts
|
|
2953
2920
|
async function confirmOrAbort(message, force, options = {}) {
|
|
@@ -3085,8 +3052,8 @@ function registerAiGatewayInference(parent) {
|
|
|
3085
3052
|
process.once("SIGINT", interrupt);
|
|
3086
3053
|
process.once("SIGTERM", interrupt);
|
|
3087
3054
|
process.stdout.on("error", outputError);
|
|
3088
|
-
const write = (
|
|
3089
|
-
process.stdout.write(
|
|
3055
|
+
const write = (text2) => new Promise((resolve4, reject) => {
|
|
3056
|
+
process.stdout.write(text2, (error) => error ? reject(error) : resolve4());
|
|
3090
3057
|
});
|
|
3091
3058
|
const requestOptions = { signal: controller.signal };
|
|
3092
3059
|
const result = kind === "chat" ? await client.createChatCompletion(
|
|
@@ -3105,16 +3072,16 @@ function registerAiGatewayInference(parent) {
|
|
|
3105
3072
|
if (format === "json") await write(`${JSON.stringify(event)}
|
|
3106
3073
|
`);
|
|
3107
3074
|
else {
|
|
3108
|
-
const
|
|
3109
|
-
if (
|
|
3075
|
+
const text2 = responseText(event);
|
|
3076
|
+
if (text2) await write(text2);
|
|
3110
3077
|
}
|
|
3111
3078
|
if ("type" in event && (event.type === "response.failed" || event.type === "response.incomplete"))
|
|
3112
3079
|
throw new Error(`Gateway generation ended with ${event.type}`);
|
|
3113
3080
|
}
|
|
3114
3081
|
if (format === "pretty") await write("\n");
|
|
3115
3082
|
} else {
|
|
3116
|
-
const
|
|
3117
|
-
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}
|
|
3118
3085
|
`);
|
|
3119
3086
|
if ("status" in result && (result.status === "failed" || result.status === "incomplete"))
|
|
3120
3087
|
throw new Error(`Gateway generation ended with ${result.status}`);
|
|
@@ -3184,6 +3151,7 @@ import {
|
|
|
3184
3151
|
import { dirname, join } from "path";
|
|
3185
3152
|
var AIRS_DOMAINS = [
|
|
3186
3153
|
"api.sase.paloaltonetworks.com",
|
|
3154
|
+
"api.apps.paloaltonetworks.com",
|
|
3187
3155
|
"service.api.aisecurity.paloaltonetworks.com",
|
|
3188
3156
|
"auth.apps.paloaltonetworks.com",
|
|
3189
3157
|
"api.dlp.paloaltonetworks.com"
|
|
@@ -3197,7 +3165,7 @@ function isAirsUrl(url) {
|
|
|
3197
3165
|
}
|
|
3198
3166
|
}
|
|
3199
3167
|
var MASK = "***";
|
|
3200
|
-
var SENSITIVE_KEY_PATTERN = /token|secret|password|passwd|credential|authorization|cookie|api[-_]?key|client[-_]?auth|^key$/i;
|
|
3168
|
+
var SENSITIVE_KEY_PATTERN = /token|secret|password|passwd|credential|authorization|cookie|api[-_]?key|client[-_]?auth|auth[-_]?code|^key$/i;
|
|
3201
3169
|
function isSensitiveKey(key) {
|
|
3202
3170
|
return SENSITIVE_KEY_PATTERN.test(key);
|
|
3203
3171
|
}
|
|
@@ -3236,28 +3204,6 @@ function redactUrl(url) {
|
|
|
3236
3204
|
return url;
|
|
3237
3205
|
}
|
|
3238
3206
|
}
|
|
3239
|
-
function pruneDebugLogs(dir, keep) {
|
|
3240
|
-
let files;
|
|
3241
|
-
try {
|
|
3242
|
-
files = readdirSync(dir).filter((f) => f.startsWith("debug-api-") && f.endsWith(".jsonl"));
|
|
3243
|
-
} catch {
|
|
3244
|
-
return;
|
|
3245
|
-
}
|
|
3246
|
-
const byAge = files.map((f) => {
|
|
3247
|
-
const path3 = join(dir, f);
|
|
3248
|
-
try {
|
|
3249
|
-
return { path: path3, mtime: statSync(path3).mtimeMs };
|
|
3250
|
-
} catch {
|
|
3251
|
-
return null;
|
|
3252
|
-
}
|
|
3253
|
-
}).filter((e) => e !== null).sort((a, b) => b.mtime - a.mtime);
|
|
3254
|
-
for (const { path: path3 } of byAge.slice(keep)) {
|
|
3255
|
-
try {
|
|
3256
|
-
unlinkSync(path3);
|
|
3257
|
-
} catch {
|
|
3258
|
-
}
|
|
3259
|
-
}
|
|
3260
|
-
}
|
|
3261
3207
|
function headersToRecord(headers) {
|
|
3262
3208
|
if (!headers) return {};
|
|
3263
3209
|
if (typeof headers === "object" && "forEach" in headers && typeof headers.forEach === "function") {
|
|
@@ -3272,11 +3218,9 @@ function headersToRecord(headers) {
|
|
|
3272
3218
|
}
|
|
3273
3219
|
return headers;
|
|
3274
3220
|
}
|
|
3275
|
-
var KEEP_DEBUG_LOGS = 10;
|
|
3276
3221
|
function installDebugLogger(logPath) {
|
|
3277
3222
|
mkdirSync(dirname(logPath), { recursive: true, mode: 448 });
|
|
3278
|
-
writeFileSync(logPath, "", { encoding: "utf8", mode: 384 });
|
|
3279
|
-
pruneDebugLogs(dirname(logPath), KEEP_DEBUG_LOGS);
|
|
3223
|
+
writeFileSync(logPath, "", { encoding: "utf8", mode: 384, flag: "wx" });
|
|
3280
3224
|
const originalFetch = globalThis.fetch;
|
|
3281
3225
|
globalThis.fetch = async function debugFetch(input, init2) {
|
|
3282
3226
|
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
|
@@ -3289,16 +3233,18 @@ function installDebugLogger(logPath) {
|
|
|
3289
3233
|
if (!isAirsUrl(url) && !inference) {
|
|
3290
3234
|
return originalFetch(input, init2);
|
|
3291
3235
|
}
|
|
3236
|
+
const dashboard = /\/v1\/mgmt\/(dashboard\/|reports\/scancontent(?:\?|$))/.test(url);
|
|
3292
3237
|
const method = init2?.method ?? (input instanceof Request ? input.method : "GET");
|
|
3293
3238
|
const reqHeaders = redactHeaders(rawHeaders);
|
|
3294
3239
|
const loggedUrl = redactUrl(url);
|
|
3295
3240
|
let reqBody;
|
|
3296
|
-
if (inference) reqBody = "[BODY OMITTED]";
|
|
3241
|
+
if (inference || dashboard) reqBody = "[BODY OMITTED]";
|
|
3297
3242
|
else if (init2?.body) {
|
|
3298
3243
|
try {
|
|
3299
3244
|
reqBody = redactDeep(JSON.parse(String(init2.body)));
|
|
3300
3245
|
} catch {
|
|
3301
|
-
|
|
3246
|
+
const contentType = new Headers(rawHeaders).get("content-type") ?? "";
|
|
3247
|
+
reqBody = contentType.includes("application/x-www-form-urlencoded") ? redactDeep(Object.fromEntries(new URLSearchParams(String(init2.body)))) : "[NON-JSON BODY OMITTED]";
|
|
3302
3248
|
}
|
|
3303
3249
|
}
|
|
3304
3250
|
const ts2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -3309,7 +3255,7 @@ function installDebugLogger(logPath) {
|
|
|
3309
3255
|
try {
|
|
3310
3256
|
response = await originalFetch(input, init2);
|
|
3311
3257
|
} catch (err) {
|
|
3312
|
-
error = inference ? "Runtime request failed" :
|
|
3258
|
+
error = inference ? "Runtime request failed" : "Request failed";
|
|
3313
3259
|
const entry2 = JSON.stringify({
|
|
3314
3260
|
timestamp: ts2,
|
|
3315
3261
|
durationMs: Date.now() - startMs,
|
|
@@ -3325,16 +3271,16 @@ function installDebugLogger(logPath) {
|
|
|
3325
3271
|
response.headers.forEach((v, k) => {
|
|
3326
3272
|
resHeaders[k] = v;
|
|
3327
3273
|
});
|
|
3328
|
-
if (inference || response.headers.get("content-type")?.includes("text/event-stream")) {
|
|
3274
|
+
if (inference || dashboard || response.headers.get("content-type")?.includes("text/event-stream")) {
|
|
3329
3275
|
resBody = "[BODY OMITTED]";
|
|
3330
3276
|
} else {
|
|
3331
3277
|
const clone = response.clone();
|
|
3332
3278
|
try {
|
|
3333
|
-
const
|
|
3279
|
+
const text2 = await clone.text();
|
|
3334
3280
|
try {
|
|
3335
|
-
resBody = redactDeep(JSON.parse(
|
|
3281
|
+
resBody = redactDeep(JSON.parse(text2));
|
|
3336
3282
|
} catch {
|
|
3337
|
-
resBody =
|
|
3283
|
+
resBody = "[NON-JSON BODY OMITTED]";
|
|
3338
3284
|
}
|
|
3339
3285
|
} catch {
|
|
3340
3286
|
resBody = "<unreadable>";
|
|
@@ -3389,8 +3335,8 @@ function addWriteOutput(command) {
|
|
|
3389
3335
|
async function readGatewayRequest(path3) {
|
|
3390
3336
|
let parsed;
|
|
3391
3337
|
try {
|
|
3392
|
-
const
|
|
3393
|
-
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);
|
|
3394
3340
|
} catch (error) {
|
|
3395
3341
|
throw new CliUsageError(
|
|
3396
3342
|
`Unable to parse --file '${path3}' as JSON or YAML: ${error instanceof Error ? error.message : String(error)}`
|
|
@@ -3605,9 +3551,9 @@ function parseBooleanOption(value) {
|
|
|
3605
3551
|
throw new CliUsageError("Expected true or false");
|
|
3606
3552
|
}
|
|
3607
3553
|
function parseIntegerOption(value) {
|
|
3608
|
-
const
|
|
3609
|
-
if (!/^-?(0|[1-9]\d*)$/.test(
|
|
3610
|
-
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);
|
|
3611
3557
|
if (!Number.isSafeInteger(parsed)) throw new CliUsageError("Expected a safe integer");
|
|
3612
3558
|
return parsed;
|
|
3613
3559
|
}
|
|
@@ -3651,14 +3597,14 @@ function parseModelBindingsOption(value) {
|
|
|
3651
3597
|
}
|
|
3652
3598
|
function parseCapabilityBindingsOption(value) {
|
|
3653
3599
|
return repeatableValues(value).map((raw) => {
|
|
3654
|
-
const [
|
|
3655
|
-
const separator =
|
|
3656
|
-
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) {
|
|
3657
3603
|
throw new CliUsageError("Expected type:name=enabled");
|
|
3658
3604
|
}
|
|
3659
3605
|
return {
|
|
3660
|
-
type:
|
|
3661
|
-
name:
|
|
3606
|
+
type: identity2.slice(0, separator),
|
|
3607
|
+
name: identity2.slice(separator + 1),
|
|
3662
3608
|
enabled: parseBooleanOption(enabled)
|
|
3663
3609
|
};
|
|
3664
3610
|
});
|
|
@@ -3679,12 +3625,12 @@ function parseAssignment(raw, forceString) {
|
|
|
3679
3625
|
const separator = raw.indexOf("=");
|
|
3680
3626
|
if (separator <= 0) throw new CliUsageError("Expected path=value");
|
|
3681
3627
|
const path3 = raw.slice(0, separator);
|
|
3682
|
-
const
|
|
3683
|
-
if (forceString) return [path3,
|
|
3628
|
+
const text2 = raw.slice(separator + 1);
|
|
3629
|
+
if (forceString) return [path3, text2];
|
|
3684
3630
|
try {
|
|
3685
|
-
return [path3, JSON.parse(
|
|
3631
|
+
return [path3, JSON.parse(text2)];
|
|
3686
3632
|
} catch {
|
|
3687
|
-
return [path3,
|
|
3633
|
+
return [path3, text2];
|
|
3688
3634
|
}
|
|
3689
3635
|
}
|
|
3690
3636
|
function schemaMessage(error) {
|
|
@@ -4608,11 +4554,11 @@ function addWindowOptions(command) {
|
|
|
4608
4554
|
);
|
|
4609
4555
|
}
|
|
4610
4556
|
function windowFrom(opts) {
|
|
4611
|
-
const
|
|
4612
|
-
if (opts.start)
|
|
4613
|
-
else
|
|
4614
|
-
if (opts.end)
|
|
4615
|
-
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;
|
|
4616
4562
|
}
|
|
4617
4563
|
function parsePositiveInteger2(value, flag) {
|
|
4618
4564
|
try {
|
|
@@ -5350,8 +5296,8 @@ async function withTimeout(promise, ms) {
|
|
|
5350
5296
|
try {
|
|
5351
5297
|
return await Promise.race([
|
|
5352
5298
|
promise,
|
|
5353
|
-
new Promise((
|
|
5354
|
-
timer = setTimeout(() =>
|
|
5299
|
+
new Promise((resolve4) => {
|
|
5300
|
+
timer = setTimeout(() => resolve4(TIMED_OUT), ms);
|
|
5355
5301
|
})
|
|
5356
5302
|
]);
|
|
5357
5303
|
} finally {
|
|
@@ -5621,12 +5567,12 @@ function hasBin(bin) {
|
|
|
5621
5567
|
}
|
|
5622
5568
|
}
|
|
5623
5569
|
function run(bin, args, label) {
|
|
5624
|
-
return new Promise((
|
|
5570
|
+
return new Promise((resolve4, reject) => {
|
|
5625
5571
|
const child = spawn(bin, args, { stdio: "inherit" });
|
|
5626
5572
|
child.on("error", (err) => reject(new Error(`Failed to start ${bin}: ${err.message}`)));
|
|
5627
5573
|
child.on("close", (code) => {
|
|
5628
5574
|
if (code !== 0) reject(new Error(`${label} failed with exit code ${code}`));
|
|
5629
|
-
else
|
|
5575
|
+
else resolve4();
|
|
5630
5576
|
});
|
|
5631
5577
|
});
|
|
5632
5578
|
}
|
|
@@ -5947,8 +5893,8 @@ function registerModelSecurityCommand(program) {
|
|
|
5947
5893
|
const fmt = opts.output;
|
|
5948
5894
|
if (fmt === "pretty") renderModelSecurityHeader();
|
|
5949
5895
|
const service = await createService2();
|
|
5950
|
-
const
|
|
5951
|
-
renderMsScanDetail(
|
|
5896
|
+
const scan2 = await service.getScan(uuid);
|
|
5897
|
+
renderMsScanDetail(scan2, fmt);
|
|
5952
5898
|
} catch (err) {
|
|
5953
5899
|
fail(err);
|
|
5954
5900
|
}
|
|
@@ -5958,9 +5904,9 @@ function registerModelSecurityCommand(program) {
|
|
|
5958
5904
|
renderModelSecurityHeader();
|
|
5959
5905
|
const service = await createService2();
|
|
5960
5906
|
const config = JSON.parse(fs.readFileSync(opts.config, "utf-8"));
|
|
5961
|
-
const
|
|
5962
|
-
ui.success(`Scan created: ${
|
|
5963
|
-
renderMsScanDetail(
|
|
5907
|
+
const scan2 = await service.createScan(config);
|
|
5908
|
+
ui.success(`Scan created: ${scan2.uuid}`);
|
|
5909
|
+
renderMsScanDetail(scan2);
|
|
5964
5910
|
} catch (err) {
|
|
5965
5911
|
fail(err);
|
|
5966
5912
|
}
|
|
@@ -7439,7 +7385,7 @@ function registerRedteamCommand(program) {
|
|
|
7439
7385
|
}
|
|
7440
7386
|
|
|
7441
7387
|
// src/cli/commands/runtime.ts
|
|
7442
|
-
import { randomUUID as
|
|
7388
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
7443
7389
|
import * as fs5 from "fs";
|
|
7444
7390
|
import { readFile as readFile10 } from "fs/promises";
|
|
7445
7391
|
import { basename as basename3, dirname as dirname3, join as join3, resolve as resolvePath } from "path";
|
|
@@ -7507,7 +7453,7 @@ function buildDataProtection(flags) {
|
|
|
7507
7453
|
const dld = { action: flags.dlpAction };
|
|
7508
7454
|
const profiles = parseList(flags.dlpProfiles);
|
|
7509
7455
|
if (profiles) {
|
|
7510
|
-
dld.member = profiles.map((
|
|
7456
|
+
dld.member = profiles.map((text2) => ({ text: text2 }));
|
|
7511
7457
|
}
|
|
7512
7458
|
if (flags.maskDataInline != null) {
|
|
7513
7459
|
dld["mask-data-inline"] = flags.maskDataInline;
|
|
@@ -8994,6 +8940,320 @@ function registerCleanupCommand(parent) {
|
|
|
8994
8940
|
});
|
|
8995
8941
|
}
|
|
8996
8942
|
|
|
8943
|
+
// src/cli/commands/runtime-dashboard.ts
|
|
8944
|
+
import { lstat } from "fs/promises";
|
|
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";
|
|
9194
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
9195
|
+
function registerRuntimeReportCommand(runtime) {
|
|
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(
|
|
9197
|
+
"--output-file <path>",
|
|
9198
|
+
"Destination (default: new timestamped file in CWD); - for stdout; never overwrite"
|
|
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(
|
|
9200
|
+
"after",
|
|
9201
|
+
examples(
|
|
9202
|
+
"airs runtime report --output-file ./airs-daily.html",
|
|
9203
|
+
"airs runtime report --output markdown --output-file ./airs-daily.md",
|
|
9204
|
+
"airs runtime report",
|
|
9205
|
+
"airs runtime report --strict --max-pages 20 --output-file - > daily.html"
|
|
9206
|
+
)
|
|
9207
|
+
).action(async (opts) => {
|
|
9208
|
+
const format = opts.output ?? command.parent?.parent?.opts().output ?? "html";
|
|
9209
|
+
if (format !== "html" && format !== "markdown")
|
|
9210
|
+
usageError("Report output must be html or markdown");
|
|
9211
|
+
const maxPages = Number(opts.maxPages);
|
|
9212
|
+
if (!/^[1-9]\d*$/.test(opts.maxPages) || !Number.isSafeInteger(maxPages) || maxPages > 100)
|
|
9213
|
+
usageError("--max-pages must be an integer from 1 to 100");
|
|
9214
|
+
if (typeof opts.title !== "string" || !opts.title.trim() || opts.title.length > 240)
|
|
9215
|
+
usageError("--title must contain 1\u2013240 characters");
|
|
9216
|
+
if (opts.outputFile !== void 0 && !opts.outputFile.trim())
|
|
9217
|
+
usageError("--output-file cannot be empty");
|
|
9218
|
+
const defaultName = `airs-runtime-report-${(/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-")}-${randomUUID4().slice(0, 8)}.${format === "html" ? "html" : "md"}`;
|
|
9219
|
+
const outputPath = opts.outputFile === "-" ? void 0 : resolve3(opts.outputFile ?? defaultName);
|
|
9220
|
+
try {
|
|
9221
|
+
if (outputPath) {
|
|
9222
|
+
const existing = await lstat2(outputPath).catch((error) => {
|
|
9223
|
+
if (error.code !== "ENOENT") throw error;
|
|
9224
|
+
return void 0;
|
|
9225
|
+
});
|
|
9226
|
+
if (existing) throw new Error("Output already exists");
|
|
9227
|
+
}
|
|
9228
|
+
const config = await loadConfig();
|
|
9229
|
+
const client = getOrCreateManagementClient({
|
|
9230
|
+
...managementClientOptions(config),
|
|
9231
|
+
numRetries: 0
|
|
9232
|
+
});
|
|
9233
|
+
ui.status("Collecting daily runtime activity and current configuration (read-only)...");
|
|
9234
|
+
const report = await collectRuntimeDailyReport(client, { title: opts.title, maxPages });
|
|
9235
|
+
const rendered = format === "html" ? renderRuntimeReportHtml(report) : renderRuntimeReportMarkdown(report);
|
|
9236
|
+
if (outputPath) {
|
|
9237
|
+
await writeReportFile(outputPath, rendered);
|
|
9238
|
+
ui.status(`Report \u2192 ${outputPath.replace(/[\p{Cc}\p{Cf}]/gu, "_")} (mode 0600)`);
|
|
9239
|
+
} else {
|
|
9240
|
+
await new Promise(
|
|
9241
|
+
(done, reject) => process.stdout.write(rendered, (error) => error ? reject(error) : done())
|
|
9242
|
+
);
|
|
9243
|
+
}
|
|
9244
|
+
const incomplete = report.sources.filter((source) => source.status !== "complete").length;
|
|
9245
|
+
if (incomplete)
|
|
9246
|
+
ui.status(`${incomplete} source(s) incomplete; review the evidence section.`);
|
|
9247
|
+
if (report.health === "unknown" || opts.strict && incomplete > 0) process.exitCode = 1;
|
|
9248
|
+
} catch {
|
|
9249
|
+
ui.error(
|
|
9250
|
+
"Report generation failed. Verify Management API configuration and a writable, new output path. Existing files are never overwritten."
|
|
9251
|
+
);
|
|
9252
|
+
process.exitCode = 1;
|
|
9253
|
+
}
|
|
9254
|
+
});
|
|
9255
|
+
}
|
|
9256
|
+
|
|
8997
9257
|
// src/cli/commands/topics-apply.ts
|
|
8998
9258
|
async function applyTopicToProfile(mgmt, input) {
|
|
8999
9259
|
const allTopics = await mgmt.listTopics();
|
|
@@ -9242,14 +9502,14 @@ async function evalTopic(scanner, profileName, topicName, cases, concurrency = 5
|
|
|
9242
9502
|
throw new Error(`scanBatch returned ${scanResults.length} results for ${cases.length} prompts`);
|
|
9243
9503
|
}
|
|
9244
9504
|
const testResults = cases.map((testCase, i) => {
|
|
9245
|
-
const
|
|
9505
|
+
const scan2 = scanResults[i];
|
|
9246
9506
|
return {
|
|
9247
9507
|
testCase,
|
|
9248
|
-
actualTriggered:
|
|
9249
|
-
scanAction:
|
|
9250
|
-
scanId:
|
|
9251
|
-
reportId:
|
|
9252
|
-
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
|
|
9253
9513
|
};
|
|
9254
9514
|
});
|
|
9255
9515
|
const metrics = computeMetrics(testResults);
|
|
@@ -9446,7 +9706,7 @@ function completedBulkResults(state) {
|
|
|
9446
9706
|
}
|
|
9447
9707
|
async function writeBulkResults(outputPath, results) {
|
|
9448
9708
|
await fs5.promises.mkdir(dirname3(outputPath), { recursive: true });
|
|
9449
|
-
const temporary = `${outputPath}.tmp-${process.pid}-${
|
|
9709
|
+
const temporary = `${outputPath}.tmp-${process.pid}-${randomUUID5()}`;
|
|
9450
9710
|
try {
|
|
9451
9711
|
await fs5.promises.writeFile(temporary, SdkRuntimeService.formatResultsCsv(results), {
|
|
9452
9712
|
encoding: "utf-8",
|
|
@@ -9472,15 +9732,12 @@ function parsePositiveInteger3(value, optionName2) {
|
|
|
9472
9732
|
}
|
|
9473
9733
|
async function createMgmtService() {
|
|
9474
9734
|
const config = await loadConfig();
|
|
9475
|
-
return new SdkManagementService(
|
|
9476
|
-
clientId: config.mgmtClientId,
|
|
9477
|
-
clientSecret: config.mgmtClientSecret,
|
|
9478
|
-
tsgId: config.mgmtTsgId,
|
|
9479
|
-
tokenEndpoint: config.mgmtTokenEndpoint
|
|
9480
|
-
});
|
|
9735
|
+
return new SdkManagementService(managementClientOptions(config));
|
|
9481
9736
|
}
|
|
9482
9737
|
function registerRuntimeCommand(program) {
|
|
9483
9738
|
const runtime = program.command("runtime").description("Runtime prompt scanning against AIRS profiles");
|
|
9739
|
+
registerRuntimeReportCommand(runtime);
|
|
9740
|
+
registerRuntimeDashboardCommands(runtime);
|
|
9484
9741
|
const apiKeys = runtime.command("api-keys").description("Manage AIRS API keys");
|
|
9485
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) => {
|
|
9486
9743
|
try {
|
|
@@ -10139,26 +10396,14 @@ function registerRuntimeCommand(program) {
|
|
|
10139
10396
|
fail(err);
|
|
10140
10397
|
}
|
|
10141
10398
|
});
|
|
10142
|
-
const scanLogs = runtime.command("scan-logs").description("
|
|
10143
|
-
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");
|
|
10144
10401
|
registerPageAliases(scanLogsQuery, { sizeFlag: "--page-size", sizeKey: "pageSize" });
|
|
10145
|
-
scanLogsQuery.action(
|
|
10146
|
-
|
|
10147
|
-
|
|
10148
|
-
|
|
10149
|
-
|
|
10150
|
-
const service = await createMgmtService();
|
|
10151
|
-
const result = await service.queryScanLogs({
|
|
10152
|
-
timeInterval: Number.parseInt(opts.interval, 10),
|
|
10153
|
-
timeUnit: opts.unit,
|
|
10154
|
-
pageNumber: page ?? 1,
|
|
10155
|
-
pageSize: size ?? 50,
|
|
10156
|
-
filter: opts.filter
|
|
10157
|
-
});
|
|
10158
|
-
renderScanLogList(result.results, result.pageToken, fmt);
|
|
10159
|
-
} catch (err) {
|
|
10160
|
-
fail(err);
|
|
10161
|
-
}
|
|
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;
|
|
10162
10407
|
});
|
|
10163
10408
|
const topics = runtime.command("topics").description("Manage AIRS custom topics and guardrail generation");
|
|
10164
10409
|
registerApplyCommand(topics);
|
|
@@ -10306,7 +10551,12 @@ function buildProgram() {
|
|
|
10306
10551
|
program.hook("preAction", async (_thisCommand, actionCommand) => {
|
|
10307
10552
|
const root = actionCommand.optsWithGlobals?.() ?? _thisCommand.opts();
|
|
10308
10553
|
setQuiet(Boolean(root.quiet));
|
|
10309
|
-
|
|
10554
|
+
const isRuntimeReport = actionCommand.name() === "report" && actionCommand.parent?.name() === "runtime";
|
|
10555
|
+
if (isRuntimeReport && (root.debug || /^(1|true|yes|on)$/i.test(process.env.PANW_AI_SEC_DEBUG?.trim() ?? "")))
|
|
10556
|
+
usageError(
|
|
10557
|
+
"Disable --debug and PANW_AI_SEC_DEBUG for environment reports to avoid persisting sensitive traffic content"
|
|
10558
|
+
);
|
|
10559
|
+
if (!isRuntimeReport && READ_COMMAND_NAMES.has(actionCommand.name()) && actionCommand.options.some((option) => option.long === "--output")) {
|
|
10310
10560
|
try {
|
|
10311
10561
|
const format = await resolveOutput(actionCommand, actionCommand.opts());
|
|
10312
10562
|
actionCommand.setOptionValueWithSource("output", format, "implied");
|
|
@@ -10315,8 +10565,18 @@ function buildProgram() {
|
|
|
10315
10565
|
}
|
|
10316
10566
|
}
|
|
10317
10567
|
if (root.debug) {
|
|
10318
|
-
const logPath = join4(
|
|
10319
|
-
|
|
10568
|
+
const logPath = join4(
|
|
10569
|
+
process.cwd(),
|
|
10570
|
+
`debug-api-${Date.now()}-${randomUUID6().slice(0, 8)}.jsonl`
|
|
10571
|
+
);
|
|
10572
|
+
try {
|
|
10573
|
+
installDebugLogger(logPath);
|
|
10574
|
+
} catch {
|
|
10575
|
+
ui.error(
|
|
10576
|
+
"Cannot create the debug log in the current working directory. Run from a writable directory or omit --debug."
|
|
10577
|
+
);
|
|
10578
|
+
process.exit(1);
|
|
10579
|
+
}
|
|
10320
10580
|
ui.status(`Debug: API log \u2192 ${logPath}`);
|
|
10321
10581
|
}
|
|
10322
10582
|
});
|