@cdot65/prisma-airs-cli 5.1.0 → 5.3.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 +5 -4
- package/dist/{chunk-DR6MZG4E.js → chunk-7Z76VCB3.js} +464 -126
- package/dist/cli/index.js +228 -82
- package/dist/index.d.ts +52 -9
- package/dist/index.js +7 -1
- package/package.json +2 -2
package/dist/cli/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
SdkPromptSetService,
|
|
10
10
|
SdkRedTeamService,
|
|
11
11
|
SdkRuntimeService,
|
|
12
|
+
collectGatewayEnvironmentReport,
|
|
12
13
|
collectRedTeamEnvironmentReport,
|
|
13
14
|
collectRuntimeDailyReport,
|
|
14
15
|
computeMetrics,
|
|
@@ -17,6 +18,8 @@ import {
|
|
|
17
18
|
loadConfig,
|
|
18
19
|
readBackupDir,
|
|
19
20
|
readBackupFile,
|
|
21
|
+
renderEnvironmentReportHtml,
|
|
22
|
+
renderEnvironmentReportMarkdown,
|
|
20
23
|
renderRedTeamReportHtml,
|
|
21
24
|
renderRedTeamReportMarkdown,
|
|
22
25
|
renderRuntimeReportHtml,
|
|
@@ -27,7 +30,7 @@ import {
|
|
|
27
30
|
validateTopic,
|
|
28
31
|
writeBackupFile,
|
|
29
32
|
writeReportFile
|
|
30
|
-
} from "../chunk-
|
|
33
|
+
} from "../chunk-7Z76VCB3.js";
|
|
31
34
|
|
|
32
35
|
// src/cli/index.ts
|
|
33
36
|
import "dotenv/config";
|
|
@@ -46,7 +49,7 @@ function installProcessGuards() {
|
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
// src/cli/program.ts
|
|
49
|
-
import { randomUUID as
|
|
52
|
+
import { randomUUID as randomUUID8 } from "crypto";
|
|
50
53
|
import { readFileSync as readFileSync4 } from "fs";
|
|
51
54
|
import { dirname as dirname4, join as join4 } from "path";
|
|
52
55
|
import { fileURLToPath } from "url";
|
|
@@ -3055,8 +3058,8 @@ function registerAiGatewayInference(parent) {
|
|
|
3055
3058
|
process.once("SIGINT", interrupt);
|
|
3056
3059
|
process.once("SIGTERM", interrupt);
|
|
3057
3060
|
process.stdout.on("error", outputError);
|
|
3058
|
-
const write = (text2) => new Promise((
|
|
3059
|
-
process.stdout.write(text2, (error) => error ? reject(error) :
|
|
3061
|
+
const write = (text2) => new Promise((resolve6, reject) => {
|
|
3062
|
+
process.stdout.write(text2, (error) => error ? reject(error) : resolve6());
|
|
3060
3063
|
});
|
|
3061
3064
|
const requestOptions = { signal: controller.signal };
|
|
3062
3065
|
const result = kind === "chat" ? await client.createChatCompletion(
|
|
@@ -4341,6 +4344,20 @@ function registerOrganisations(root) {
|
|
|
4341
4344
|
const self = showHelpOnEmpty(
|
|
4342
4345
|
group.command("self").description("Manage the current organisation")
|
|
4343
4346
|
);
|
|
4347
|
+
const info = addReadOutput(
|
|
4348
|
+
group.command("info").description("Get organisation dashboard info (settings redacted)").requiredOption("--tsg-id <id>", "Tenant service group ID")
|
|
4349
|
+
);
|
|
4350
|
+
info.action(
|
|
4351
|
+
(opts) => runDetail(
|
|
4352
|
+
info,
|
|
4353
|
+
opts,
|
|
4354
|
+
async (client) => redactAIGatewaySecrets(
|
|
4355
|
+
"organisations.getInfo",
|
|
4356
|
+
await client.organisations.getInfo(opts.tsgId),
|
|
4357
|
+
"response"
|
|
4358
|
+
)
|
|
4359
|
+
)
|
|
4360
|
+
);
|
|
4344
4361
|
const selfGet = addReadOutput(self.command("get").description("Get the current organisation"));
|
|
4345
4362
|
selfGet.action((opts) => runDetail(selfGet, opts, (client) => client.organisations.getSelf()));
|
|
4346
4363
|
const selfUpdate = addWriteOutput(
|
|
@@ -4431,6 +4448,10 @@ function registerAiGatewayInventory(root) {
|
|
|
4431
4448
|
{ option: "name", path: "name" },
|
|
4432
4449
|
{ option: "workspace", path: "workspace_id" }
|
|
4433
4450
|
];
|
|
4451
|
+
const catalog = addReadOutput(
|
|
4452
|
+
guardrails.command("catalog").description("Get available guardrail evaluator schemas (not enabled policies)")
|
|
4453
|
+
);
|
|
4454
|
+
catalog.action((opts) => runDetail(catalog, opts, (client) => client.guardrails.getCatalog()));
|
|
4434
4455
|
const addGuardrailFields = (command) => command.option("--actions <json>", "Guardrail actions object").option("--checks <json>", "Guardrail checks array").option("--name <name>", "Guardrail name").option("--workspace <uuid>", "Workspace UUID");
|
|
4435
4456
|
const guardrailUpdateFields = guardrailFields.filter((field) => field.option !== "workspace");
|
|
4436
4457
|
const addGuardrailUpdateFields = (command) => command.option("--actions <json>", "Guardrail actions object").option("--checks <json>", "Guardrail checks array").option("--name <name>", "Guardrail name");
|
|
@@ -4501,7 +4522,8 @@ function registerAiGatewayInventory(root) {
|
|
|
4501
4522
|
// src/cli/commands/aigateway/telemetry.ts
|
|
4502
4523
|
import {
|
|
4503
4524
|
AI_GW_GROUP_COLUMNS,
|
|
4504
|
-
AI_GW_GROUP_DIMENSIONS
|
|
4525
|
+
AI_GW_GROUP_DIMENSIONS,
|
|
4526
|
+
redactAIGatewaySecrets as redactAIGatewaySecrets2
|
|
4505
4527
|
} from "@cdot65/prisma-airs-sdk";
|
|
4506
4528
|
|
|
4507
4529
|
// src/cli/commands/aigateway/telemetry-filters.ts
|
|
@@ -4586,7 +4608,10 @@ function parseNamedDate2(value, flag) {
|
|
|
4586
4608
|
function registerMetric(telemetry, name, description, method) {
|
|
4587
4609
|
const command = addWindowOptions(telemetry.command(name).description(description));
|
|
4588
4610
|
command.action(
|
|
4589
|
-
(opts) => runDetail(command, opts, (client) =>
|
|
4611
|
+
(opts) => runDetail(command, opts, async (client) => {
|
|
4612
|
+
const result = await client.telemetry[method](windowFrom(opts));
|
|
4613
|
+
return method === "filterBoundaries" ? redactAIGatewaySecrets2("telemetry.filterBoundaries", result, "response") : result;
|
|
4614
|
+
})
|
|
4590
4615
|
);
|
|
4591
4616
|
}
|
|
4592
4617
|
function registerFilteredMetric(telemetry, name, description) {
|
|
@@ -4634,6 +4659,24 @@ function registerAiGatewayTelemetryReads(telemetry) {
|
|
|
4634
4659
|
);
|
|
4635
4660
|
registerMetric(telemetry, "error-trends", "Get error trends", "errorTrends");
|
|
4636
4661
|
registerMetric(telemetry, "errors", "Get error count", "errors");
|
|
4662
|
+
registerMetric(
|
|
4663
|
+
telemetry,
|
|
4664
|
+
"error-category-trends",
|
|
4665
|
+
"Get errors by HTTP status",
|
|
4666
|
+
"errorCategoryTrends"
|
|
4667
|
+
);
|
|
4668
|
+
registerMetric(
|
|
4669
|
+
telemetry,
|
|
4670
|
+
"grouped-errors",
|
|
4671
|
+
"Get HTTP error counts by time bucket",
|
|
4672
|
+
"groupedErrors"
|
|
4673
|
+
);
|
|
4674
|
+
registerMetric(
|
|
4675
|
+
telemetry,
|
|
4676
|
+
"filter-boundaries",
|
|
4677
|
+
"Get available analytics filters and bounds",
|
|
4678
|
+
"filterBoundaries"
|
|
4679
|
+
);
|
|
4637
4680
|
const feedback = showHelpOnEmpty(
|
|
4638
4681
|
telemetry.command("feedback").description("Inspect model feedback telemetry")
|
|
4639
4682
|
);
|
|
@@ -4676,7 +4719,7 @@ function registerAiGatewayTelemetryReads(telemetry) {
|
|
|
4676
4719
|
registerFilteredMetric(telemetry, "latency", "Get latency telemetry");
|
|
4677
4720
|
const logs = showHelpOnEmpty(telemetry.command("logs").description("Inspect request logs"));
|
|
4678
4721
|
const logsList = addWindowOptions(
|
|
4679
|
-
logs.command("list").description("List request logs").option("--page-size <n>", "Rows per response", "50").option("--status-code <code>", "Filter by HTTP status").option("--trace-id <id>", "Return one trace id")
|
|
4722
|
+
logs.command("list").description("List request logs").option("--page-size <n>", "Rows per response", "50").option("--current-page <n>", "Zero-based transaction page", "0").option("--status-code <code>", "Filter by HTTP status").option("--trace-id <id>", "Return one trace id")
|
|
4680
4723
|
);
|
|
4681
4724
|
logsList.action(
|
|
4682
4725
|
(opts) => runDetail(
|
|
@@ -4685,6 +4728,7 @@ function registerAiGatewayTelemetryReads(telemetry) {
|
|
|
4685
4728
|
(client) => client.telemetry.logs({
|
|
4686
4729
|
...windowFrom(opts),
|
|
4687
4730
|
pageSize: parsePositiveInteger2(opts.pageSize ?? "50", "--page-size"),
|
|
4731
|
+
currentPage: parseNonnegativePage(opts.currentPage ?? "0"),
|
|
4688
4732
|
...opts.statusCode ? { statusCode: parsePositiveInteger2(opts.statusCode, "--status-code") } : {},
|
|
4689
4733
|
...opts.traceId ? { traceId: opts.traceId } : {}
|
|
4690
4734
|
})
|
|
@@ -4696,6 +4740,99 @@ function registerAiGatewayTelemetryReads(telemetry) {
|
|
|
4696
4740
|
registerMetric(telemetry, "user-trends", "Get user trends", "userTrends");
|
|
4697
4741
|
registerMetric(telemetry, "users", "Get unique-user count", "users");
|
|
4698
4742
|
}
|
|
4743
|
+
function parseNonnegativePage(value) {
|
|
4744
|
+
if (!/^\d+$/.test(value) || !Number.isSafeInteger(Number(value)))
|
|
4745
|
+
throw new CliUsageError("--current-page must be a nonnegative safe integer");
|
|
4746
|
+
return Number(value);
|
|
4747
|
+
}
|
|
4748
|
+
|
|
4749
|
+
// src/cli/commands/aigateway-report.ts
|
|
4750
|
+
import { lstat } from "fs/promises";
|
|
4751
|
+
import { resolve } from "path";
|
|
4752
|
+
import { AIGatewayClient as AIGatewayClient3 } from "@cdot65/prisma-airs-sdk";
|
|
4753
|
+
import { z } from "zod";
|
|
4754
|
+
import { randomUUID } from "crypto";
|
|
4755
|
+
function registerAiGatewayReportCommand(aigateway) {
|
|
4756
|
+
const command = aigateway.command("report").alias("dashboard").description("Generate an AI Gateway environment report (read-only)").requiredOption("--workspace <ref>", "Workspace slug, UUID, or unique display name").option("--days <n>", "Rolling window in days (default: 1)", "1").option("--start <iso>", "Explicit ISO-8601 start; requires --end").option("--end <iso>", "Explicit ISO-8601 end; requires --start").option("--output <format>", "Deliverable format: html or markdown (default: html)").option(
|
|
4757
|
+
"--output-file <path>",
|
|
4758
|
+
"Destination (default: new timestamped file in CWD); - for stdout; never overwrite"
|
|
4759
|
+
).option("--title <text>", "Report title", "AI Gateway environment report").option("--max-pages <n>", "Page budget per source, 1\u2013100 (50 transactions/page)", "40").option("--strict", "Exit 1 after writing if any source is incomplete").addHelpText(
|
|
4760
|
+
"after",
|
|
4761
|
+
examples(
|
|
4762
|
+
"airs aigateway report --workspace ws-develo-71f8d8 --output-file ./airs-daily.html",
|
|
4763
|
+
"airs aigateway report --workspace ws-develo-71f8d8 --output markdown --output-file ./airs-daily.md",
|
|
4764
|
+
"airs aigateway report --workspace ws-develo-71f8d8",
|
|
4765
|
+
"airs aigateway report --workspace ws-develo-71f8d8 --strict --max-pages 20 --output-file - > daily.html"
|
|
4766
|
+
)
|
|
4767
|
+
).action(async (opts) => {
|
|
4768
|
+
const format = opts.output ?? command.parent?.parent?.opts().output ?? "html";
|
|
4769
|
+
if (format !== "html" && format !== "markdown")
|
|
4770
|
+
usageError("Report output must be html or markdown");
|
|
4771
|
+
if (typeof opts.workspace !== "string" || !opts.workspace.trim())
|
|
4772
|
+
usageError("--workspace cannot be empty");
|
|
4773
|
+
if (!/^[1-9]\d*$/.test(opts.days) || !Number.isSafeInteger(Number(opts.days)))
|
|
4774
|
+
usageError("--days must be a positive integer");
|
|
4775
|
+
if (Boolean(opts.start) !== Boolean(opts.end))
|
|
4776
|
+
usageError("--start and --end must be supplied together");
|
|
4777
|
+
if (opts.start && command.getOptionValueSource("days") !== "default")
|
|
4778
|
+
usageError("Use --days or --start/--end, not both");
|
|
4779
|
+
const end = opts.end ? new Date(opts.end) : /* @__PURE__ */ new Date();
|
|
4780
|
+
const start = opts.start ? new Date(opts.start) : new Date(end.getTime() - Number(opts.days) * 864e5);
|
|
4781
|
+
const iso = z.string().datetime({ offset: true });
|
|
4782
|
+
if (opts.start && (!iso.safeParse(opts.start).success || !iso.safeParse(opts.end).success) || !Number.isFinite(start.getTime()) || !Number.isFinite(end.getTime()) || start >= end)
|
|
4783
|
+
usageError("Provide a valid increasing ISO-8601 window with timezone");
|
|
4784
|
+
const maxPages = Number(opts.maxPages);
|
|
4785
|
+
if (!/^[1-9]\d*$/.test(opts.maxPages) || !Number.isSafeInteger(maxPages) || maxPages > 100)
|
|
4786
|
+
usageError("--max-pages must be an integer from 1 to 100");
|
|
4787
|
+
if (typeof opts.title !== "string" || !opts.title.trim() || opts.title.length > 240)
|
|
4788
|
+
usageError("--title must contain 1\u2013240 characters");
|
|
4789
|
+
if (opts.outputFile !== void 0 && !opts.outputFile.trim())
|
|
4790
|
+
usageError("--output-file cannot be empty");
|
|
4791
|
+
const defaultName = `airs-aigateway-report-${(/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-")}-${randomUUID().slice(0, 8)}.${format === "html" ? "html" : "md"}`;
|
|
4792
|
+
const outputPath = opts.outputFile === "-" ? void 0 : resolve(opts.outputFile ?? defaultName);
|
|
4793
|
+
try {
|
|
4794
|
+
if (outputPath) {
|
|
4795
|
+
const existing = await lstat(outputPath).catch((error) => {
|
|
4796
|
+
if (error.code !== "ENOENT") throw error;
|
|
4797
|
+
return void 0;
|
|
4798
|
+
});
|
|
4799
|
+
if (existing) throw new Error("Output already exists");
|
|
4800
|
+
}
|
|
4801
|
+
const config = await loadConfig();
|
|
4802
|
+
const client = new AIGatewayClient3({
|
|
4803
|
+
...aiGatewayClientOptions(config),
|
|
4804
|
+
numRetries: 0
|
|
4805
|
+
});
|
|
4806
|
+
ui.status("Collecting AI Gateway statistics and current configuration (read-only)...");
|
|
4807
|
+
const report = await collectGatewayEnvironmentReport(client, {
|
|
4808
|
+
title: opts.title,
|
|
4809
|
+
maxPages,
|
|
4810
|
+
workspace: opts.workspace,
|
|
4811
|
+
tsgId: config.mgmtTsgId ?? process.env.PANW_AI_GW_TSG_ID ?? "",
|
|
4812
|
+
start,
|
|
4813
|
+
end
|
|
4814
|
+
});
|
|
4815
|
+
const rendered = format === "html" ? renderEnvironmentReportHtml(report) : renderEnvironmentReportMarkdown(report);
|
|
4816
|
+
if (outputPath) {
|
|
4817
|
+
await writeReportFile(outputPath, rendered);
|
|
4818
|
+
ui.status(`Report \u2192 ${outputPath.replace(/[\p{Cc}\p{Cf}]/gu, "_")} (mode 0600)`);
|
|
4819
|
+
} else {
|
|
4820
|
+
await new Promise(
|
|
4821
|
+
(done, reject) => process.stdout.write(rendered, (error) => error ? reject(error) : done())
|
|
4822
|
+
);
|
|
4823
|
+
}
|
|
4824
|
+
const incomplete = report.sources.filter((source) => source.status !== "complete").length;
|
|
4825
|
+
if (incomplete)
|
|
4826
|
+
ui.status(`${incomplete} source(s) incomplete; review the evidence section.`);
|
|
4827
|
+
if (report.health === "unknown" || opts.strict && incomplete > 0) process.exitCode = 1;
|
|
4828
|
+
} catch {
|
|
4829
|
+
ui.error(
|
|
4830
|
+
"Report generation failed. Verify Management API configuration and a writable, new output path. Existing files are never overwritten."
|
|
4831
|
+
);
|
|
4832
|
+
process.exitCode = 1;
|
|
4833
|
+
}
|
|
4834
|
+
});
|
|
4835
|
+
}
|
|
4699
4836
|
|
|
4700
4837
|
// src/cli/commands/aigateway.ts
|
|
4701
4838
|
async function createService() {
|
|
@@ -4759,6 +4896,7 @@ function scopeNameLooksUnrelated(name, scopeName) {
|
|
|
4759
4896
|
function registerAiGatewayCommand(program) {
|
|
4760
4897
|
const aigateway = program.command("aigateway").description("Manage and observe Prisma AIRS AI Gateway resources").action(() => aigateway.outputHelp());
|
|
4761
4898
|
registerAiGatewayInventory(aigateway);
|
|
4899
|
+
registerAiGatewayReportCommand(aigateway);
|
|
4762
4900
|
registerAiGatewayInference(aigateway);
|
|
4763
4901
|
const workspace = aigateway.command("workspaces").alias("workspace").description("Manage gateway workspaces").action(() => workspace.outputHelp());
|
|
4764
4902
|
const workspaceList = workspace.command("list").description("List workspaces (default: active workspaces you are scoped to)").option("--plane <plane>", "Plane to read from: data (scoped) or admin (whole tenant)").option("--status <status>", "Filter by lifecycle state: active or archived").option("--all", "Merge active + archived admin-plane reads (whole tenant, both states)").option("--output <format>", "Output format: pretty, table, markdown, csv, json, yaml").addHelpText(
|
|
@@ -5201,7 +5339,7 @@ function registerConfigCommand(program) {
|
|
|
5201
5339
|
}
|
|
5202
5340
|
|
|
5203
5341
|
// src/cli/commands/doctor.ts
|
|
5204
|
-
import { randomUUID } from "crypto";
|
|
5342
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
5205
5343
|
import { readFile as readFile4 } from "fs/promises";
|
|
5206
5344
|
import { init, Scanner } from "@cdot65/prisma-airs-sdk";
|
|
5207
5345
|
var DOCTOR_TIMEOUT_MS = 5e3;
|
|
@@ -5299,8 +5437,8 @@ async function withTimeout(promise, ms) {
|
|
|
5299
5437
|
try {
|
|
5300
5438
|
return await Promise.race([
|
|
5301
5439
|
promise,
|
|
5302
|
-
new Promise((
|
|
5303
|
-
timer = setTimeout(() =>
|
|
5440
|
+
new Promise((resolve6) => {
|
|
5441
|
+
timer = setTimeout(() => resolve6(TIMED_OUT), ms);
|
|
5304
5442
|
})
|
|
5305
5443
|
]);
|
|
5306
5444
|
} finally {
|
|
@@ -5455,7 +5593,7 @@ async function defaultScannerProbe() {
|
|
|
5455
5593
|
const config = await loadConfig();
|
|
5456
5594
|
init(runtimeInitOptions(config));
|
|
5457
5595
|
const scanner = new Scanner();
|
|
5458
|
-
return scanner.queryByScanIds([
|
|
5596
|
+
return scanner.queryByScanIds([randomUUID2()]);
|
|
5459
5597
|
}
|
|
5460
5598
|
async function defaultMgmtProbe() {
|
|
5461
5599
|
const config = await loadConfig();
|
|
@@ -5570,12 +5708,12 @@ function hasBin(bin) {
|
|
|
5570
5708
|
}
|
|
5571
5709
|
}
|
|
5572
5710
|
function run(bin, args, label) {
|
|
5573
|
-
return new Promise((
|
|
5711
|
+
return new Promise((resolve6, reject) => {
|
|
5574
5712
|
const child = spawn(bin, args, { stdio: "inherit" });
|
|
5575
5713
|
child.on("error", (err) => reject(new Error(`Failed to start ${bin}: ${err.message}`)));
|
|
5576
5714
|
child.on("close", (code) => {
|
|
5577
5715
|
if (code !== 0) reject(new Error(`${label} failed with exit code ${code}`));
|
|
5578
|
-
else
|
|
5716
|
+
else resolve6();
|
|
5579
5717
|
});
|
|
5580
5718
|
});
|
|
5581
5719
|
}
|
|
@@ -6153,24 +6291,33 @@ async function backupTargets(opts) {
|
|
|
6153
6291
|
return results;
|
|
6154
6292
|
}
|
|
6155
6293
|
|
|
6156
|
-
// src/cli/commands/redteam-
|
|
6157
|
-
import { lstat } from "fs/promises";
|
|
6158
|
-
import { resolve } from "path";
|
|
6294
|
+
// src/cli/commands/redteam-report.ts
|
|
6295
|
+
import { lstat as lstat2 } from "fs/promises";
|
|
6296
|
+
import { resolve as resolve2 } from "path";
|
|
6159
6297
|
import { RedTeamClient } from "@cdot65/prisma-airs-sdk";
|
|
6160
|
-
import { randomUUID as
|
|
6161
|
-
function
|
|
6162
|
-
const command = redteam.command("dashboard").description("Generate
|
|
6298
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
6299
|
+
function registerRedTeamReportCommand(redteam, scanReport) {
|
|
6300
|
+
const command = redteam.command("report [jobId]").alias("dashboard").description("Generate an environment report, or view an individual scan report by job ID").option("--attacks", "Individual scan only: include attack list", false).option("--severity <level>", "Individual scan only: filter attacks by severity").option("--limit <n>", "Individual scan only: max attacks to show", "20").option("--output <format>", "Deliverable format: html or markdown (default: html)").option(
|
|
6163
6301
|
"--output-file <path>",
|
|
6164
6302
|
"Destination (default: new timestamped file in CWD); - for stdout; never overwrite"
|
|
6165
6303
|
).option("--title <text>", "Report title", "Red Team environment report").option("--max-pages <n>", "Page budget per source, 1\u2013100 (15 records/page)", "40").option("--strict", "Exit 1 after writing if any source is incomplete").addHelpText(
|
|
6166
6304
|
"after",
|
|
6167
6305
|
examples(
|
|
6168
|
-
"airs redteam
|
|
6169
|
-
"airs redteam
|
|
6170
|
-
"airs redteam
|
|
6171
|
-
"airs redteam
|
|
6306
|
+
"airs redteam report --output-file ./airs-daily.html",
|
|
6307
|
+
"airs redteam report --output markdown --output-file ./airs-daily.md",
|
|
6308
|
+
"airs redteam report",
|
|
6309
|
+
"airs redteam report --strict --max-pages 20 --output-file - > daily.html"
|
|
6172
6310
|
)
|
|
6173
|
-
).action(async (opts) => {
|
|
6311
|
+
).action(async (jobId, opts) => {
|
|
6312
|
+
const explicit = (name) => command.getOptionValueSource(name) === "cli";
|
|
6313
|
+
if (jobId !== void 0) {
|
|
6314
|
+
if (["output", "outputFile", "title", "maxPages", "strict"].some(explicit) || ["html", "markdown"].includes(command.parent?.parent?.opts().output))
|
|
6315
|
+
usageError("Environment report options cannot be combined with a scan job ID");
|
|
6316
|
+
await scanReport(jobId, opts);
|
|
6317
|
+
return;
|
|
6318
|
+
}
|
|
6319
|
+
if (["attacks", "severity", "limit"].some(explicit))
|
|
6320
|
+
usageError("--attacks, --severity and --limit require a scan job ID");
|
|
6174
6321
|
const format = opts.output ?? command.parent?.parent?.opts().output ?? "html";
|
|
6175
6322
|
if (format !== "html" && format !== "markdown")
|
|
6176
6323
|
usageError("Report output must be html or markdown");
|
|
@@ -6181,11 +6328,11 @@ function registerRedTeamDashboardCommand(redteam) {
|
|
|
6181
6328
|
usageError("--title must contain 1\u2013240 characters");
|
|
6182
6329
|
if (opts.outputFile !== void 0 && !opts.outputFile.trim())
|
|
6183
6330
|
usageError("--output-file cannot be empty");
|
|
6184
|
-
const defaultName = `airs-redteam-report-${(/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-")}-${
|
|
6185
|
-
const outputPath = opts.outputFile === "-" ? void 0 :
|
|
6331
|
+
const defaultName = `airs-redteam-report-${(/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-")}-${randomUUID3().slice(0, 8)}.${format === "html" ? "html" : "md"}`;
|
|
6332
|
+
const outputPath = opts.outputFile === "-" ? void 0 : resolve2(opts.outputFile ?? defaultName);
|
|
6186
6333
|
try {
|
|
6187
6334
|
if (outputPath) {
|
|
6188
|
-
const existing = await
|
|
6335
|
+
const existing = await lstat2(outputPath).catch((error) => {
|
|
6189
6336
|
if (error.code !== "ENOENT") throw error;
|
|
6190
6337
|
return void 0;
|
|
6191
6338
|
});
|
|
@@ -6465,7 +6612,6 @@ function parseAdapterVariables(input) {
|
|
|
6465
6612
|
}
|
|
6466
6613
|
function registerRedteamCommand(program) {
|
|
6467
6614
|
const redteam = program.command("redteam").description("AI Red Team scan operations");
|
|
6468
|
-
registerRedTeamDashboardCommand(redteam);
|
|
6469
6615
|
redteam.command("abort <jobId>").description("Abort a running scan").action(async (jobId) => {
|
|
6470
6616
|
try {
|
|
6471
6617
|
renderRedteamHeader();
|
|
@@ -6848,7 +6994,7 @@ function registerRedteamCommand(program) {
|
|
|
6848
6994
|
fail(err);
|
|
6849
6995
|
}
|
|
6850
6996
|
});
|
|
6851
|
-
redteam
|
|
6997
|
+
registerRedTeamReportCommand(redteam, async (jobId, opts) => {
|
|
6852
6998
|
try {
|
|
6853
6999
|
renderRedteamHeader();
|
|
6854
7000
|
const service = await createService3();
|
|
@@ -7459,7 +7605,7 @@ function registerRedteamCommand(program) {
|
|
|
7459
7605
|
}
|
|
7460
7606
|
|
|
7461
7607
|
// src/cli/commands/runtime.ts
|
|
7462
|
-
import { randomUUID as
|
|
7608
|
+
import { randomUUID as randomUUID7 } from "crypto";
|
|
7463
7609
|
import * as fs5 from "fs";
|
|
7464
7610
|
import { readFile as readFile10 } from "fs/promises";
|
|
7465
7611
|
import { basename as basename3, dirname as dirname3, join as join3, resolve as resolvePath } from "path";
|
|
@@ -7707,7 +7853,7 @@ function mergeProfilePolicy(existing, overrides) {
|
|
|
7707
7853
|
}
|
|
7708
7854
|
|
|
7709
7855
|
// src/cli/bulk-scan-lock.ts
|
|
7710
|
-
import { randomUUID as
|
|
7856
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
7711
7857
|
import * as fs3 from "fs/promises";
|
|
7712
7858
|
function errorCode(error) {
|
|
7713
7859
|
return error?.code;
|
|
@@ -7738,7 +7884,7 @@ function processIsAlive(pid) {
|
|
|
7738
7884
|
}
|
|
7739
7885
|
}
|
|
7740
7886
|
async function installLock(lockPath, record) {
|
|
7741
|
-
const candidate = `${lockPath}.candidate-${process.pid}-${
|
|
7887
|
+
const candidate = `${lockPath}.candidate-${process.pid}-${randomUUID4()}`;
|
|
7742
7888
|
try {
|
|
7743
7889
|
await fs3.writeFile(candidate, JSON.stringify(record), {
|
|
7744
7890
|
encoding: "utf-8",
|
|
@@ -7756,7 +7902,7 @@ async function acquireBulkScanLock(statePath) {
|
|
|
7756
7902
|
version: 1,
|
|
7757
7903
|
pid: process.pid,
|
|
7758
7904
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7759
|
-
token:
|
|
7905
|
+
token: randomUUID4()
|
|
7760
7906
|
};
|
|
7761
7907
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
7762
7908
|
try {
|
|
@@ -7792,32 +7938,32 @@ async function acquireBulkScanLock(statePath) {
|
|
|
7792
7938
|
}
|
|
7793
7939
|
|
|
7794
7940
|
// src/cli/bulk-scan-state.ts
|
|
7795
|
-
import { randomUUID as
|
|
7941
|
+
import { randomUUID as randomUUID5 } from "crypto";
|
|
7796
7942
|
import * as fs4 from "fs/promises";
|
|
7797
7943
|
import * as path2 from "path";
|
|
7798
|
-
import { z } from "zod";
|
|
7799
|
-
var BulkScanResultSchema =
|
|
7800
|
-
index:
|
|
7801
|
-
reqId:
|
|
7802
|
-
prompt:
|
|
7803
|
-
response:
|
|
7804
|
-
scanId:
|
|
7805
|
-
reportId:
|
|
7806
|
-
action:
|
|
7807
|
-
category:
|
|
7808
|
-
triggered:
|
|
7809
|
-
detections:
|
|
7810
|
-
error:
|
|
7944
|
+
import { z as z2 } from "zod";
|
|
7945
|
+
var BulkScanResultSchema = z2.object({
|
|
7946
|
+
index: z2.number().int().nonnegative(),
|
|
7947
|
+
reqId: z2.number().int().nonnegative(),
|
|
7948
|
+
prompt: z2.string(),
|
|
7949
|
+
response: z2.string().optional(),
|
|
7950
|
+
scanId: z2.string(),
|
|
7951
|
+
reportId: z2.string(),
|
|
7952
|
+
action: z2.enum(["allow", "block", "failed"]),
|
|
7953
|
+
category: z2.string(),
|
|
7954
|
+
triggered: z2.boolean(),
|
|
7955
|
+
detections: z2.record(z2.boolean()),
|
|
7956
|
+
error: z2.string().optional()
|
|
7811
7957
|
});
|
|
7812
|
-
var BulkScanItemSchema =
|
|
7813
|
-
index:
|
|
7814
|
-
reqId:
|
|
7815
|
-
prompt:
|
|
7816
|
-
status:
|
|
7817
|
-
scanId:
|
|
7818
|
-
receiptReportId:
|
|
7958
|
+
var BulkScanItemSchema = z2.object({
|
|
7959
|
+
index: z2.number().int().nonnegative(),
|
|
7960
|
+
reqId: z2.number().int().nonnegative(),
|
|
7961
|
+
prompt: z2.string(),
|
|
7962
|
+
status: z2.enum(["pending", "submitting", "submitted", "complete", "failed", "ambiguous"]),
|
|
7963
|
+
scanId: z2.string().min(1).optional(),
|
|
7964
|
+
receiptReportId: z2.string().optional(),
|
|
7819
7965
|
result: BulkScanResultSchema.optional(),
|
|
7820
|
-
error:
|
|
7966
|
+
error: z2.string().optional()
|
|
7821
7967
|
}).superRefine((item, ctx) => {
|
|
7822
7968
|
if (item.reqId !== item.index) {
|
|
7823
7969
|
ctx.addIssue({ code: "custom", message: "reqId must match the stable input index" });
|
|
@@ -7855,15 +8001,15 @@ var BulkScanItemSchema = z.object({
|
|
|
7855
8001
|
}
|
|
7856
8002
|
}
|
|
7857
8003
|
});
|
|
7858
|
-
var BulkScanStateSchema =
|
|
7859
|
-
version:
|
|
7860
|
-
profile:
|
|
7861
|
-
sessionId:
|
|
7862
|
-
outputFile:
|
|
7863
|
-
batchSize:
|
|
7864
|
-
createdAt:
|
|
7865
|
-
updatedAt:
|
|
7866
|
-
items:
|
|
8004
|
+
var BulkScanStateSchema = z2.object({
|
|
8005
|
+
version: z2.literal(2),
|
|
8006
|
+
profile: z2.string().min(1),
|
|
8007
|
+
sessionId: z2.string().optional(),
|
|
8008
|
+
outputFile: z2.string().min(1),
|
|
8009
|
+
batchSize: z2.number().int().positive().max(Number.MAX_SAFE_INTEGER),
|
|
8010
|
+
createdAt: z2.string().datetime(),
|
|
8011
|
+
updatedAt: z2.string().datetime(),
|
|
8012
|
+
items: z2.array(BulkScanItemSchema).min(1)
|
|
7867
8013
|
}).superRefine((state, ctx) => {
|
|
7868
8014
|
const indices = /* @__PURE__ */ new Set();
|
|
7869
8015
|
for (const [position, item] of state.items.entries()) {
|
|
@@ -7889,8 +8035,8 @@ async function saveBulkScanState(state, dir, filePath) {
|
|
|
7889
8035
|
}
|
|
7890
8036
|
await fs4.mkdir(dir, { recursive: true, mode: 448 });
|
|
7891
8037
|
if (!filePath) await fs4.chmod(dir, 448);
|
|
7892
|
-
const target = filePath ?? path2.join(dir, `${state.createdAt.replace(/[:.]/g, "-")}-${
|
|
7893
|
-
const temporary = `${target}.tmp-${process.pid}-${
|
|
8038
|
+
const target = filePath ?? path2.join(dir, `${state.createdAt.replace(/[:.]/g, "-")}-${randomUUID5()}.bulk-scan.json`);
|
|
8039
|
+
const temporary = `${target}.tmp-${process.pid}-${randomUUID5()}`;
|
|
7894
8040
|
try {
|
|
7895
8041
|
await fs4.writeFile(temporary, JSON.stringify(state, null, 2), {
|
|
7896
8042
|
encoding: "utf-8",
|
|
@@ -9015,8 +9161,8 @@ function registerCleanupCommand(parent) {
|
|
|
9015
9161
|
}
|
|
9016
9162
|
|
|
9017
9163
|
// src/cli/commands/runtime-dashboard.ts
|
|
9018
|
-
import { lstat as
|
|
9019
|
-
import { resolve as
|
|
9164
|
+
import { lstat as lstat3 } from "fs/promises";
|
|
9165
|
+
import { resolve as resolve4 } from "path";
|
|
9020
9166
|
import { dump as dump5 } from "js-yaml";
|
|
9021
9167
|
var TIME_UNITS = ["hour", "hours", "day", "days"];
|
|
9022
9168
|
function integer2(value, name, minimum = 0) {
|
|
@@ -9092,8 +9238,8 @@ function registerRuntimeDashboardCommands(runtime) {
|
|
|
9092
9238
|
"Scan content requires exactly one of --show-content or --output-file <new-path>"
|
|
9093
9239
|
);
|
|
9094
9240
|
if (opts.outputFile) {
|
|
9095
|
-
const path3 =
|
|
9096
|
-
const existing = await
|
|
9241
|
+
const path3 = resolve4(text(opts.outputFile, "--output-file"));
|
|
9242
|
+
const existing = await lstat3(path3).catch((error) => {
|
|
9097
9243
|
if (error.code !== "ENOENT") throw error;
|
|
9098
9244
|
return void 0;
|
|
9099
9245
|
});
|
|
@@ -9108,7 +9254,7 @@ function registerRuntimeDashboardCommands(runtime) {
|
|
|
9108
9254
|
const result = await read(client.dashboard);
|
|
9109
9255
|
const output = format === "yaml" ? dump5(result, { noRefs: true, lineWidth: -1 }).trimEnd() : JSON.stringify(result, null, 2);
|
|
9110
9256
|
if (command.name() === "scan-content" && opts.outputFile) {
|
|
9111
|
-
await writeReportFile(
|
|
9257
|
+
await writeReportFile(resolve4(String(opts.outputFile)), `${output}
|
|
9112
9258
|
`);
|
|
9113
9259
|
ui.status("Scan content exported to the requested private file (0600).");
|
|
9114
9260
|
} else console.log(output);
|
|
@@ -9279,11 +9425,11 @@ function registerRuntimeDashboardCommands(runtime) {
|
|
|
9279
9425
|
}
|
|
9280
9426
|
|
|
9281
9427
|
// src/cli/commands/runtime-report.ts
|
|
9282
|
-
import { lstat as
|
|
9283
|
-
import { resolve as
|
|
9284
|
-
import { randomUUID as
|
|
9428
|
+
import { lstat as lstat4 } from "fs/promises";
|
|
9429
|
+
import { resolve as resolve5 } from "path";
|
|
9430
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
9285
9431
|
function registerRuntimeReportCommand(runtime) {
|
|
9286
|
-
const command = runtime.command("report").description("Generate a daily AI Runtime Security environment
|
|
9432
|
+
const command = runtime.command("report").description("Generate a daily AI Runtime Security environment report (read-only)").option("--output <format>", "Deliverable format: html or markdown (default: html)").option(
|
|
9287
9433
|
"--output-file <path>",
|
|
9288
9434
|
"Destination (default: new timestamped file in CWD); - for stdout; never overwrite"
|
|
9289
9435
|
).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(
|
|
@@ -9305,11 +9451,11 @@ function registerRuntimeReportCommand(runtime) {
|
|
|
9305
9451
|
usageError("--title must contain 1\u2013240 characters");
|
|
9306
9452
|
if (opts.outputFile !== void 0 && !opts.outputFile.trim())
|
|
9307
9453
|
usageError("--output-file cannot be empty");
|
|
9308
|
-
const defaultName = `airs-runtime-report-${(/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-")}-${
|
|
9309
|
-
const outputPath = opts.outputFile === "-" ? void 0 :
|
|
9454
|
+
const defaultName = `airs-runtime-report-${(/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-")}-${randomUUID6().slice(0, 8)}.${format === "html" ? "html" : "md"}`;
|
|
9455
|
+
const outputPath = opts.outputFile === "-" ? void 0 : resolve5(opts.outputFile ?? defaultName);
|
|
9310
9456
|
try {
|
|
9311
9457
|
if (outputPath) {
|
|
9312
|
-
const existing = await
|
|
9458
|
+
const existing = await lstat4(outputPath).catch((error) => {
|
|
9313
9459
|
if (error.code !== "ENOENT") throw error;
|
|
9314
9460
|
return void 0;
|
|
9315
9461
|
});
|
|
@@ -9796,7 +9942,7 @@ function completedBulkResults(state) {
|
|
|
9796
9942
|
}
|
|
9797
9943
|
async function writeBulkResults(outputPath, results) {
|
|
9798
9944
|
await fs5.promises.mkdir(dirname3(outputPath), { recursive: true });
|
|
9799
|
-
const temporary = `${outputPath}.tmp-${process.pid}-${
|
|
9945
|
+
const temporary = `${outputPath}.tmp-${process.pid}-${randomUUID7()}`;
|
|
9800
9946
|
try {
|
|
9801
9947
|
await fs5.promises.writeFile(temporary, SdkRuntimeService.formatResultsCsv(results), {
|
|
9802
9948
|
encoding: "utf-8",
|
|
@@ -10641,12 +10787,12 @@ function buildProgram() {
|
|
|
10641
10787
|
program.hook("preAction", async (_thisCommand, actionCommand) => {
|
|
10642
10788
|
const root = actionCommand.optsWithGlobals?.() ?? _thisCommand.opts();
|
|
10643
10789
|
setQuiet(Boolean(root.quiet));
|
|
10644
|
-
const isEnvironmentReport = actionCommand.name() === "report" && actionCommand.parent?.name()
|
|
10790
|
+
const isEnvironmentReport = actionCommand.name() === "report" && ["runtime", "redteam", "aigateway"].includes(actionCommand.parent?.name() ?? "") && !(actionCommand.parent?.name() === "redteam" && actionCommand.args.length > 0);
|
|
10645
10791
|
if (isEnvironmentReport && (root.debug || /^(1|true|yes|on)$/i.test(process.env.PANW_AI_SEC_DEBUG?.trim() ?? "")))
|
|
10646
10792
|
usageError(
|
|
10647
10793
|
"Disable --debug and PANW_AI_SEC_DEBUG for environment reports to avoid persisting sensitive traffic content"
|
|
10648
10794
|
);
|
|
10649
|
-
if (!isEnvironmentReport && READ_COMMAND_NAMES.has(actionCommand.name()) && actionCommand.options.some((option) => option.long === "--output")) {
|
|
10795
|
+
if (!isEnvironmentReport && !(actionCommand.name() === "report" && actionCommand.parent?.name() === "redteam") && READ_COMMAND_NAMES.has(actionCommand.name()) && actionCommand.options.some((option) => option.long === "--output")) {
|
|
10650
10796
|
try {
|
|
10651
10797
|
const format = await resolveOutput(actionCommand, actionCommand.opts());
|
|
10652
10798
|
actionCommand.setOptionValueWithSource("output", format, "implied");
|
|
@@ -10657,7 +10803,7 @@ function buildProgram() {
|
|
|
10657
10803
|
if (root.debug) {
|
|
10658
10804
|
const logPath = join4(
|
|
10659
10805
|
process.cwd(),
|
|
10660
|
-
`debug-api-${Date.now()}-${
|
|
10806
|
+
`debug-api-${Date.now()}-${randomUUID8().slice(0, 8)}.jsonl`
|
|
10661
10807
|
);
|
|
10662
10808
|
try {
|
|
10663
10809
|
installDebugLogger(logPath);
|