@cdot65/prisma-airs-cli 5.0.1 → 5.1.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 CHANGED
@@ -15,6 +15,7 @@
15
15
 
16
16
  - **Runtime Scanning** — scan prompts and responses against AIRS security profiles, single or bulk with CSV export
17
17
  - **Daily environment report** — `airs runtime report` delivers a read-only AI Runtime Security dashboard as self-contained HTML (default) or Markdown in your working directory, with explicit evidence gaps and configuration review findings
18
+ - **Red Team environment dashboard** — `airs redteam dashboard` collects seven read-only SDK feeds into private HTML/Markdown deliverables, with source completeness, quota and risk review findings, and independently scoped scan-creation activity
18
19
  - **SCM dashboard and sessions** — `runtime dashboard` and `runtime sessions` expose application activity, daily trends, checked pagination and explicit session-to-content drill-down. Legacy `scan-logs query` is **broken/under refactor** and exits with migration guidance; see the [session reference](https://cdot65.github.io/prisma-airs-cli/cli/runtime/sessions/)
19
20
  - **Guardrail Optimization** — atomic CLI commands (`create`, `apply`, `eval`, `revert`) for custom topic guardrails, designed for autonomous agent loops (see [`AGENTS.md`](AGENTS.md))
20
21
  - **AI Red Teaming** — adversarial scanning with static, dynamic, and custom prompt set attack modes
@@ -65,6 +66,9 @@ airs runtime topics revert --profile my-profile --name "Explosives"
65
66
  airs redteam scan --target <uuid> --name "Full Scan" --type STATIC
66
67
  airs redteam report <job-id>
67
68
 
69
+ # Read-only environment dashboard (HTML by default; --output markdown also supported)
70
+ airs redteam dashboard --strict
71
+
68
72
  # Red team custom target adapters
69
73
  airs redteam adapter list --output json
70
74
 
@@ -2501,7 +2501,302 @@ async function writeReportFile(path2, content) {
2501
2501
  }
2502
2502
  }
2503
2503
 
2504
- // src/reports/render.ts
2504
+ // src/reports/redteam.ts
2505
+ var DataError = class extends Error {
2506
+ };
2507
+ var label = (value) => typeof value === "string" ? value.replace(/[\p{Cc}\p{Cf}]/gu, " ").slice(0, 120) : "Unknown";
2508
+ function count(value) {
2509
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0)
2510
+ throw new DataError("A required counter is missing or invalid.");
2511
+ return value;
2512
+ }
2513
+ function failure(error) {
2514
+ if (error instanceof DataError) return error.message;
2515
+ const code = error?.statusCode;
2516
+ return typeof code === "number" && Number.isInteger(code) && code >= 400 && code <= 599 ? `HTTP ${code}: source unavailable; check credentials, service availability and SDK compatibility.` : "Source request or validation failed. No upstream error body is included.";
2517
+ }
2518
+ async function collectRedTeamEnvironmentReport(client, options = {}) {
2519
+ const maxPages = options.maxPages ?? 40;
2520
+ if (!Number.isSafeInteger(maxPages) || maxPages < 1 || maxPages > 100)
2521
+ throw new Error("maxPages must be an integer from 1 to 100");
2522
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
2523
+ const end = now();
2524
+ const report = {
2525
+ schemaVersion: 1,
2526
+ product: "Prisma AIRS AI Red Teaming",
2527
+ title: options.title ?? "Red Team environment report",
2528
+ collectionStartedAt: end.toISOString(),
2529
+ generatedAt: "",
2530
+ window: { start: new Date(end.getTime() - 864e5).toISOString(), end: end.toISOString() },
2531
+ health: "unknown",
2532
+ sources: [],
2533
+ findings: [],
2534
+ tables: [],
2535
+ limitations: [
2536
+ "This is a read-only snapshot, not a security certification, availability SLA or numerical health score.",
2537
+ "Only scan creation timestamps are filtered locally to the preceding 24 hours. Current scan status is not a history of status changes.",
2538
+ "Dashboard statistics use server defaults with an unverified measurement window. They are not daily totals and need not match the paginated scan inventory.",
2539
+ "Targets and adapters are current configuration inventories, not configuration change logs. Broker channels not online may be intentionally idle; no outage is inferred.",
2540
+ "Risk categories are server-reported. Quota consumption is not reset or reinterpreted as daily usage; unlimited zero allocation is not exhaustion.",
2541
+ "Partial sources describe collected records only. Missing data is unknown, never zero. Independently read pages are not an atomic snapshot.",
2542
+ "Only aggregate counters and category labels are included. Target names, IDs, connection settings, scripts, variables, prompts and scan content are excluded."
2543
+ ]
2544
+ };
2545
+ function finding(priority, title, evidence, recommendation, source3) {
2546
+ report.findings.push({ priority, title, evidence, recommendation, source: source3 });
2547
+ }
2548
+ async function source2(name, method, window, read) {
2549
+ const s = {
2550
+ name,
2551
+ method,
2552
+ window,
2553
+ status: "unavailable",
2554
+ records: 0,
2555
+ pages: 0,
2556
+ notes: []
2557
+ };
2558
+ report.sources.push(s);
2559
+ try {
2560
+ await read(s);
2561
+ if (s.status !== "partial") s.status = "complete";
2562
+ } catch (error) {
2563
+ s.status = s.records > 0 ? "partial" : "unavailable";
2564
+ s.notes.push(failure(error));
2565
+ }
2566
+ if (s.status !== "complete")
2567
+ finding(
2568
+ "review",
2569
+ `${name} incomplete`,
2570
+ s.notes.join(" "),
2571
+ "Resolve the source error or increase the page budget, then regenerate.",
2572
+ name
2573
+ );
2574
+ }
2575
+ async function pages(s, read) {
2576
+ const rows = [];
2577
+ const seen = /* @__PURE__ */ new Set();
2578
+ let total2;
2579
+ try {
2580
+ for (let page = 0; page < maxPages; page++) {
2581
+ const r = await read(rows.length);
2582
+ s.pages++;
2583
+ if (!Array.isArray(r.data)) throw new DataError("Missing inventory array.");
2584
+ const reported = count(r.pagination.total_items);
2585
+ total2 ??= reported;
2586
+ if (total2 !== reported) throw new DataError("Inventory changed during pagination.");
2587
+ if (r.data.length > 15) throw new DataError("Page exceeds requested size.");
2588
+ for (const row of r.data) {
2589
+ if (!row.uuid || seen.has(row.uuid))
2590
+ throw new DataError("Missing or repeated identity; pagination stopped.");
2591
+ seen.add(row.uuid);
2592
+ rows.push(row);
2593
+ s.records = rows.length;
2594
+ }
2595
+ if (rows.length > total2) throw new DataError("Inventory exceeds reported total.");
2596
+ if (rows.length === total2) return rows;
2597
+ if (!r.data.length) throw new DataError("Premature empty page.");
2598
+ }
2599
+ throw new DataError(`Reached the ${maxPages}-page budget; inventory is incomplete.`);
2600
+ } catch (error) {
2601
+ if (!rows.length) throw error;
2602
+ s.status = "partial";
2603
+ s.notes.push(failure(error));
2604
+ return rows;
2605
+ }
2606
+ }
2607
+ const table = (title, note, headers, rows) => report.tables.push({ title, note, headers, rows });
2608
+ function groups(values) {
2609
+ const counts = /* @__PURE__ */ new Map();
2610
+ for (const v of values) {
2611
+ const key = label(v);
2612
+ counts.set(key, (counts.get(key) ?? 0) + 1);
2613
+ }
2614
+ return [...counts].sort(([a], [b]) => a.localeCompare(b));
2615
+ }
2616
+ await source2("Overview", "getDashboardOverview()", "Current dashboard snapshot", async (s) => {
2617
+ const r = await client.getDashboardOverview();
2618
+ const total2 = count(r.total_targets);
2619
+ table("Dashboard overview", s.window, ["Metric", "Count"], [["Targets", total2]]);
2620
+ s.records = 1;
2621
+ s.pages = 1;
2622
+ });
2623
+ await source2(
2624
+ "Statistics",
2625
+ "getScanStatistics()",
2626
+ "Server-default window (unverified)",
2627
+ async (s) => {
2628
+ const r = await client.getScanStatistics();
2629
+ const rows = [
2630
+ ["Scans", count(r.total_scans)],
2631
+ ["Targets scanned", count(r.targets_scanned)]
2632
+ ];
2633
+ for (const risk of r.risk_profile ?? []) {
2634
+ const n = count(risk.total);
2635
+ rows.push([`Risk: ${label(risk.risk_rating)}`, n]);
2636
+ if (/^(high|critical)$/i.test(risk.risk_rating) && n > 0)
2637
+ finding(
2638
+ "attention",
2639
+ "Elevated risk reported",
2640
+ `${n} in the ${label(risk.risk_rating)} risk category.`,
2641
+ "Review affected targets in Red Team; the statistics window is server-defined.",
2642
+ s.name
2643
+ );
2644
+ }
2645
+ for (const status of r.scan_status ?? [])
2646
+ rows.push([`Status: ${label(status.name)}`, count(status.count)]);
2647
+ table("Dashboard scan statistics", s.window, ["Metric", "Count"], rows);
2648
+ s.records = 1;
2649
+ s.pages = 1;
2650
+ }
2651
+ );
2652
+ await source2("Targets", "targets.list()", "Current paginated inventory", async (s) => {
2653
+ const rows = await pages(s, (skip) => client.targets.list({ skip, limit: 15 }));
2654
+ table(
2655
+ "Target inventory",
2656
+ "Collected targets by type; consult source completeness.",
2657
+ ["Type", "Count"],
2658
+ groups(rows.map((r) => r.target_type))
2659
+ );
2660
+ });
2661
+ await source2("Quota", "getQuotaSummary() \u2014 GET", "Current quota snapshot", async (s) => {
2662
+ if (!client.getQuotaSummary)
2663
+ throw new DataError(
2664
+ "Installed SDK lacks getQuotaSummary(); use the locally updated SDK. POST is not used as a fallback."
2665
+ );
2666
+ const r = await client.getQuotaSummary();
2667
+ const rows = [];
2668
+ for (const kind of ["static", "dynamic", "custom"]) {
2669
+ const q = r[kind];
2670
+ const allocated = count(q.allocated);
2671
+ const consumed = count(q.consumed);
2672
+ if (typeof q.unlimited !== "boolean") throw new DataError("Missing unlimited quota flag.");
2673
+ rows.push([
2674
+ kind,
2675
+ allocated,
2676
+ consumed,
2677
+ q.unlimited ? "Unlimited" : String(Math.max(0, allocated - consumed))
2678
+ ]);
2679
+ if (!q.unlimited && consumed >= allocated)
2680
+ finding(
2681
+ "attention",
2682
+ `${kind} quota has no remaining allocation`,
2683
+ `${consumed} consumed; ${allocated} allocated.`,
2684
+ "Review quota allocation before scheduling more scans.",
2685
+ s.name
2686
+ );
2687
+ }
2688
+ table(
2689
+ "Quota",
2690
+ "Remaining is shown only for finite quotas; these are not daily usage counters.",
2691
+ ["Type", "Allocated", "Consumed", "Remaining"],
2692
+ rows
2693
+ );
2694
+ s.records = 3;
2695
+ s.pages = 1;
2696
+ });
2697
+ await source2(
2698
+ "Scans",
2699
+ "scans.list()",
2700
+ "Paginated inventory; local 24-hour creation filter",
2701
+ async (s) => {
2702
+ const rows = await pages(s, (skip) => client.scans.list({ skip, limit: 15 }));
2703
+ let unknown = 0;
2704
+ const recent = rows.filter((r) => {
2705
+ const t = typeof r.created_at === "string" && /(?:Z|[+-]\d{2}:\d{2})$/i.test(r.created_at) ? Date.parse(r.created_at) : Number.NaN;
2706
+ if (!Number.isFinite(t) || t > end.getTime()) {
2707
+ unknown++;
2708
+ return false;
2709
+ }
2710
+ return t >= end.getTime() - 864e5;
2711
+ });
2712
+ table(
2713
+ "Scan inventory and daily creation activity",
2714
+ "Daily count covers collected records created in the stated UTC window, not scans completed or attacks executed.",
2715
+ ["Metric", "Count"],
2716
+ [
2717
+ ["Collected scans", rows.length],
2718
+ ["Created in preceding 24 hours", recent.length],
2719
+ ["Unusable or future creation timestamps", unknown]
2720
+ ]
2721
+ );
2722
+ table(
2723
+ "Current status of collected scans",
2724
+ "All collected scans, independent of creation window.",
2725
+ ["Status", "Count"],
2726
+ groups(rows.map((r) => r.status))
2727
+ );
2728
+ const failed = rows.filter((r) => /^(failed|error)$/i.test(r.status ?? "")).length;
2729
+ if (failed)
2730
+ finding(
2731
+ "attention",
2732
+ "Scans require failure review",
2733
+ `${failed} collected scans currently report failed/error status.`,
2734
+ "Inspect individual scan reports and error logs before rerunning; no reruns are performed here.",
2735
+ s.name
2736
+ );
2737
+ if (unknown)
2738
+ finding(
2739
+ "review",
2740
+ "Daily activity has timestamp gaps",
2741
+ `${unknown} records could not be assigned to the daily window.`,
2742
+ "Review source timestamps; do not interpret the daily count as complete.",
2743
+ s.name
2744
+ );
2745
+ }
2746
+ );
2747
+ await source2(
2748
+ "Network broker",
2749
+ "networkBroker.getChannelStats()",
2750
+ "Current channel snapshot",
2751
+ async (s) => {
2752
+ const r = await client.networkBroker.getChannelStats();
2753
+ const total2 = count(r.total_channels);
2754
+ const online = count(r.online_channels);
2755
+ if (online > total2) throw new DataError("Online channel count exceeds total.");
2756
+ table(
2757
+ "Network broker",
2758
+ "Not-online channels may be intentionally idle; this does not establish an outage.",
2759
+ ["Metric", "Count"],
2760
+ [
2761
+ ["Configured channels", total2],
2762
+ ["Online channels", online]
2763
+ ]
2764
+ );
2765
+ if (total2 > online)
2766
+ finding(
2767
+ "review",
2768
+ "Confirm expected broker connectivity",
2769
+ `${online} of ${total2} configured channels are online.`,
2770
+ "Compare with intended channel schedules before investigating connectivity.",
2771
+ s.name
2772
+ );
2773
+ s.records = 1;
2774
+ s.pages = 1;
2775
+ }
2776
+ );
2777
+ await source2(
2778
+ "Adapters",
2779
+ "adapters.list({ include_target_count: true })",
2780
+ "Current paginated configuration",
2781
+ async (s) => {
2782
+ const rows = await pages(
2783
+ s,
2784
+ (skip) => client.adapters.list({ skip, limit: 15, include_target_count: true })
2785
+ );
2786
+ table(
2787
+ "Adapter inventory",
2788
+ "Configuration status only; scripts and variables are excluded.",
2789
+ ["Status", "Count"],
2790
+ groups(rows.map((r) => r.status))
2791
+ );
2792
+ }
2793
+ );
2794
+ report.health = report.sources.every((s) => s.status === "unavailable") ? "unknown" : report.findings.some((f) => f.priority === "attention") ? "attention" : report.findings.length ? "review" : "no-findings";
2795
+ report.generatedAt = now().toISOString();
2796
+ return report;
2797
+ }
2798
+
2799
+ // src/reports/redteam-render.ts
2505
2800
  import { createHash } from "crypto";
2506
2801
  var HEALTH = {
2507
2802
  attention: "Attention required",
@@ -2509,7 +2804,107 @@ var HEALTH = {
2509
2804
  "no-findings": "No findings in available evidence",
2510
2805
  unknown: "Unable to assess"
2511
2806
  };
2512
- function html(value) {
2807
+ var html = (v) => String(v ?? "Unknown").replace(/[&<>"']/g, (c) => `&#${c.charCodeAt(0)};`);
2808
+ var md = (v) => String(v ?? "Unknown").replace(
2809
+ /[&<>"'\\`*_[\]{}()|!#~\r\n]/g,
2810
+ (c) => /[\r\n]/.test(c) ? " " : `&#${c.charCodeAt(0)};`
2811
+ );
2812
+ function tables(r) {
2813
+ return [
2814
+ ...r.tables,
2815
+ {
2816
+ title: "Evidence and completeness",
2817
+ note: "Complete means the source was collected within its budget, not that the environment is secure. Unavailable is not zero.",
2818
+ headers: ["Source", "SDK method", "Window", "Status", "Records", "Pages", "Notes"],
2819
+ rows: r.sources.map((s) => [
2820
+ s.name,
2821
+ s.method,
2822
+ s.window,
2823
+ s.status,
2824
+ s.records,
2825
+ s.pages,
2826
+ s.notes.join(" ") || "Collection completed."
2827
+ ])
2828
+ }
2829
+ ];
2830
+ }
2831
+ function renderRedTeamReportMarkdown(r) {
2832
+ const lines = [
2833
+ `# ${md(r.title)}`,
2834
+ "",
2835
+ `**${r.product}**`,
2836
+ "",
2837
+ `Assessment: **${HEALTH[r.health]}**`,
2838
+ "",
2839
+ `Collection: ${r.collectionStartedAt} \u2192 ${r.generatedAt}`,
2840
+ "",
2841
+ `Scan creation window (UTC): ${r.window.start} \u2192 ${r.window.end}`,
2842
+ "",
2843
+ "## What needs attention",
2844
+ ""
2845
+ ];
2846
+ for (const f of r.findings)
2847
+ lines.push(
2848
+ `### ${f.priority.toUpperCase()}: ${md(f.title)}`,
2849
+ "",
2850
+ `Evidence: ${md(f.evidence)}`,
2851
+ "",
2852
+ `Next step: ${md(f.recommendation)}`,
2853
+ "",
2854
+ `Source: ${md(f.source)}`,
2855
+ ""
2856
+ );
2857
+ if (!r.findings.length)
2858
+ lines.push("No findings in available evidence; this does not establish absence of risk.", "");
2859
+ for (const t of tables(r)) {
2860
+ lines.push(`## ${md(t.title)}`, "", md(t.note), "");
2861
+ if (t.rows.length)
2862
+ lines.push(
2863
+ `| ${t.headers.map(md).join(" | ")} |`,
2864
+ `| ${t.headers.map(() => "---").join(" | ")} |`,
2865
+ ...t.rows.map((row) => `| ${row.map(md).join(" | ")} |`),
2866
+ ""
2867
+ );
2868
+ else
2869
+ lines.push(
2870
+ "No rows collected; consult source completeness before interpreting this as empty.",
2871
+ ""
2872
+ );
2873
+ }
2874
+ lines.push(
2875
+ "## Scope and limitations",
2876
+ "",
2877
+ ...r.limitations.map((v) => `- ${md(v)}`),
2878
+ "",
2879
+ `Prisma AIRS CLI \xB7 Red Team report schema ${r.schemaVersion}`,
2880
+ ""
2881
+ );
2882
+ return lines.join("\n");
2883
+ }
2884
+ var CSS = `
2885
+ :root{font:16px/1.6 system-ui,sans-serif;color:#193449;background:#f0f4f7}*{box-sizing:border-box}body{margin:0}header{background:#152e43;color:white;padding:3rem max(1rem,calc((100vw - 1160px)/2))}h1{font-size:clamp(1.8rem,4vw,3rem);line-height:1.15}h1,h2,h3,p,td{overflow-wrap:anywhere}main{max-width:1200px;margin:auto;padding:1rem}section,article{padding:1.4rem;background:white;border:1px solid #cddbe4;border-radius:10px;margin:1rem 0}.finding{border-left:5px solid #b08528}.finding[data-priority=attention]{border-left-color:#b1392d}.note{color:#536575;font-size:.9rem}.badge{border:1px solid;padding:.3rem .8rem;border-radius:30px;display:inline-block}nav,.controls{display:flex;gap:1rem;flex-wrap:wrap}button,select{font:inherit;padding:.4rem;border:1px solid #526778;border-radius:5px;background:white;color:#193449}a{color:#006b63}.table-wrap{overflow:auto}table{border-collapse:collapse;width:100%;text-align:left}th,td{padding:.7rem;border-bottom:1px solid #d4dfe6;vertical-align:top}th{background:#edf3f7}caption{text-align:left}footer{padding:1rem;color:#536575}.js-only{display:none}.ready .js-only{display:flex}[hidden]{display:none!important}:focus-visible{outline:3px solid #ae6816;outline-offset:3px}.skip{position:absolute;left:-10000px}.skip:focus{left:1rem;top:1rem;background:white;padding:1rem}@media(max-width:600px){header{padding:1.5rem 1rem}section{padding:1rem}th,td{padding:.5rem}}@media print{body{background:white;font-size:10pt}header{background:white;color:black;padding:0}main{max-width:none;padding:0}nav,.controls,.skip{display:none!important}[hidden]{display:revert!important}.table-wrap{overflow:visible}section{border:0;padding:0}article{break-inside:avoid}}
2886
+ `;
2887
+ var JS = `'use strict';document.documentElement.classList.add('ready');document.getElementById('print').addEventListener('click',function(){window.print()});document.getElementById('priority').addEventListener('change',function(){var n=0;document.querySelectorAll('.finding').forEach(function(e){e.hidden=this.value!=='all'&&e.dataset.priority!==this.value;if(!e.hidden)n++},this);document.getElementById('count').textContent=n+' findings shown'});`;
2888
+ function renderRedTeamReportHtml(r) {
2889
+ const hash = (v) => createHash("sha256").update(v).digest("base64");
2890
+ const policy = `default-src 'none'; script-src 'sha256-${hash(JS)}'; style-src 'sha256-${hash(CSS)}'; connect-src 'none'; base-uri 'none'; form-action 'none'`;
2891
+ const sections = tables(r).map(
2892
+ (t, i) => `<section id="table-${i}"><h2>${html(t.title)}</h2><p class="note">${html(t.note)}</p>${t.rows.length ? `<div class="table-wrap" role="region" aria-label="${html(t.title)}" tabindex="0"><table><caption>${t.rows.length} aggregate rows</caption><thead><tr>${t.headers.map((v) => `<th scope="col">${html(v)}</th>`).join("")}</tr></thead><tbody>${t.rows.map((row) => `<tr>${row.map((v) => `<td>${html(v)}</td>`).join("")}</tr>`).join("")}</tbody></table></div>` : "<p>No rows collected; consult source completeness before interpreting this as empty.</p>"}</section>`
2893
+ ).join("");
2894
+ return `<!doctype html>
2895
+ <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta http-equiv="Content-Security-Policy" content="${html(policy)}"><meta name="referrer" content="no-referrer"><title>${html(r.title)}</title><style>${CSS}</style></head><body><a class="skip" href="#main">Skip to report</a><header><p>${r.product}</p><h1>${html(r.title)}</h1><span class="badge">${HEALTH[r.health]}</span><p>Collection: ${html(r.collectionStartedAt)} \u2192 ${html(r.generatedAt)}</p><p>Scan creation window (UTC): ${html(r.window.start)} \u2192 ${html(r.window.end)}</p><p>Read-only evidence \xB7 Confidential environment metadata</p></header><main id="main"><nav aria-label="Report sections"><a href="#findings">Attention</a><a href="#table-0">Evidence tables</a><a href="#scope">Scope</a></nav><section id="findings"><h2>What needs attention</h2><div class="controls js-only"><label for="priority">Priority</label><select id="priority"><option value="all">All</option><option value="attention">Attention</option><option value="review">Review</option></select><span id="count" role="status"></span><button id="print" type="button">Print / save PDF</button></div>${r.findings.map((f) => `<article class="finding" data-priority="${html(f.priority)}"><h3>${html(f.title)}</h3><p>${html(f.evidence)}</p><p><strong>Next step:</strong> ${html(f.recommendation)}</p><p class="note">${html(f.source)}</p></article>`).join("") || "<p>No findings in available evidence; this does not establish absence of risk.</p>"}</section>${sections}<section id="scope"><h2>Scope and limitations</h2><ul>${r.limitations.map((v) => `<li>${html(v)}</li>`).join("")}</ul></section><footer>Prisma AIRS CLI \xB7 Red Team report schema ${r.schemaVersion} \xB7 Works offline and without JavaScript.</footer></main><script>${JS}</script></body></html>
2896
+ `;
2897
+ }
2898
+
2899
+ // src/reports/render.ts
2900
+ import { createHash as createHash2 } from "crypto";
2901
+ var HEALTH2 = {
2902
+ attention: "Attention required",
2903
+ review: "Review required",
2904
+ "no-findings": "No findings in available evidence",
2905
+ unknown: "Unable to assess"
2906
+ };
2907
+ function html2(value) {
2513
2908
  return String(value ?? "Unknown").replace(
2514
2909
  /[&<>"']/g,
2515
2910
  (char) => ({
@@ -2521,7 +2916,7 @@ function html(value) {
2521
2916
  })[char] ?? char
2522
2917
  );
2523
2918
  }
2524
- function md(value) {
2919
+ function md2(value) {
2525
2920
  return String(value ?? "Unknown").replace(
2526
2921
  /[&<>"'\\`*_[\]{}()|!#~\r\n]/g,
2527
2922
  (char) => char === "\r" || char === "\n" ? " " : `&#${char.charCodeAt(0)};`
@@ -2530,7 +2925,7 @@ function md(value) {
2530
2925
  function number(value) {
2531
2926
  return value === null ? "Unknown" : value.toLocaleString("en-US", { maximumFractionDigits: 2 });
2532
2927
  }
2533
- function tables(report) {
2928
+ function tables2(report) {
2534
2929
  const sessionSource = report.sources.find((source2) => source2.name === "Daily session inventory");
2535
2930
  return [
2536
2931
  {
@@ -2660,15 +3055,15 @@ function metrics(report) {
2660
3055
  }
2661
3056
  function renderRuntimeReportMarkdown(report) {
2662
3057
  const lines = [
2663
- `# ${md(report.title)}`,
3058
+ `# ${md2(report.title)}`,
2664
3059
  "",
2665
- `**${md(report.product)}**`,
3060
+ `**${md2(report.product)}**`,
2666
3061
  "",
2667
- `Assessment: **${HEALTH[report.health]}**`,
3062
+ `Assessment: **${HEALTH2[report.health]}**`,
2668
3063
  "",
2669
- `Window: ${md(report.window.start)} \u2192 ${md(report.window.end)}`,
3064
+ `Window: ${md2(report.window.start)} \u2192 ${md2(report.window.end)}`,
2670
3065
  "",
2671
- `${md(report.window.description)}. Collection: ${md(report.collectionStartedAt)} \u2192 ${md(report.generatedAt)}.`,
3066
+ `${md2(report.window.description)}. Collection: ${md2(report.collectionStartedAt)} \u2192 ${md2(report.generatedAt)}.`,
2672
3067
  "",
2673
3068
  "## At a glance",
2674
3069
  "",
@@ -2684,17 +3079,17 @@ function renderRuntimeReportMarkdown(report) {
2684
3079
  );
2685
3080
  for (const finding of report.findings)
2686
3081
  lines.push(
2687
- `### ${finding.priority.toUpperCase()}: ${md(finding.title)}`,
3082
+ `### ${finding.priority.toUpperCase()}: ${md2(finding.title)}`,
2688
3083
  "",
2689
- `Evidence: ${md(finding.evidence)}`,
3084
+ `Evidence: ${md2(finding.evidence)}`,
2690
3085
  "",
2691
- `Next step: ${md(finding.recommendation)}`,
3086
+ `Next step: ${md2(finding.recommendation)}`,
2692
3087
  "",
2693
- `Source: ${md(finding.source)}`,
3088
+ `Source: ${md2(finding.source)}`,
2694
3089
  ""
2695
3090
  );
2696
- for (const table of tables(report)) {
2697
- lines.push(`## ${table.title}`, "", md(table.note), "");
3091
+ for (const table of tables2(report)) {
3092
+ lines.push(`## ${table.title}`, "", md2(table.note), "");
2698
3093
  if (table.rows.length === 0)
2699
3094
  lines.push(
2700
3095
  "No rows available. Consult the source status before interpreting this as an empty result.",
@@ -2704,21 +3099,21 @@ function renderRuntimeReportMarkdown(report) {
2704
3099
  lines.push(
2705
3100
  `| ${table.headers.join(" | ")} |`,
2706
3101
  `| ${table.headers.map(() => "---").join(" | ")} |`,
2707
- ...table.rows.map((row) => `| ${row.map(md).join(" | ")} |`),
3102
+ ...table.rows.map((row) => `| ${row.map(md2).join(" | ")} |`),
2708
3103
  ""
2709
3104
  );
2710
3105
  }
2711
3106
  lines.push(
2712
3107
  "## Scope, privacy, and limitations",
2713
3108
  "",
2714
- ...report.limitations.map((note) => `- ${md(note)}`),
3109
+ ...report.limitations.map((note) => `- ${md2(note)}`),
2715
3110
  "",
2716
3111
  `Generated by Prisma AIRS CLI \xB7 Report schema ${report.schemaVersion}`,
2717
3112
  ""
2718
3113
  );
2719
3114
  return lines.join("\n");
2720
3115
  }
2721
- var CSS = `
3116
+ var CSS2 = `
2722
3117
  #evidence td:nth-child(3){white-space:nowrap}
2723
3118
  :root{color-scheme:light;--ink:#172b40;--muted:#526476;--line:#d8e2e9;--bg:#f2f5f8;--accent:#00786e;font:16px/1.55 system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
2724
3119
  *{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--ink)}a{color:var(--accent)}header{background:#11283d;color:#fff;padding:2.5rem max(1.25rem,calc((100vw - 1240px)/2))}header p{color:#d7e4ec;max-width:85ch}h1{font-size:clamp(1.8rem,4vw,2.7rem);line-height:1.15;margin:.6rem 0 1rem;overflow-wrap:anywhere}h2{font-size:1.35rem;margin:0 0 .6rem}h3{font-size:1.05rem;margin:.35rem 0}p{margin:.5rem 0 1rem}.eyebrow{font-size:.8rem;letter-spacing:.12em;text-transform:uppercase}.badge{display:inline-block;padding:.35rem .7rem;border:1px solid currentColor;border-radius:100px;font-weight:650;background:#fff;color:var(--ink)}.attention .badge{color:#a62b1b}.review .badge{color:#795500}.unknown .badge{color:#69439b}.no-findings .badge{color:#006a54}
@@ -2726,7 +3121,7 @@ main{max-width:1280px;margin:auto;padding:1.5rem 1.25rem 3rem}nav{display:flex;g
2726
3121
  @media(max-width:760px){.cards{grid-template-columns:repeat(2,minmax(0,1fr))}section{padding:1rem}header{padding:1.5rem 1.25rem}.card strong{font-size:1.5rem}}
2727
3122
  @media print{body{background:#fff;font-size:10pt}header{background:#fff;color:#000;padding:0}header p{color:#333}main{padding:0;max-width:none}nav,.toolbar,.skip{display:none!important}section,.card{border-radius:0;break-inside:auto}.finding,.card{break-inside:avoid}.table-wrap{overflow:visible}table{font-size:8pt}th,td{min-width:0;padding:.3rem}.hidden,[hidden]{display:revert!important}h2,h3{break-after:avoid}a{color:#000}}
2728
3123
  `;
2729
- var JS = `
3124
+ var JS2 = `
2730
3125
  'use strict';
2731
3126
  document.documentElement.classList.add('js-ready');
2732
3127
  document.getElementById('print-report').addEventListener('click',function(){window.print();});
@@ -2744,21 +3139,21 @@ search.addEventListener('input',function(){
2744
3139
  });
2745
3140
  `;
2746
3141
  function renderRuntimeReportHtml(report) {
2747
- const hash = (text) => createHash("sha256").update(text).digest("base64");
2748
- const policy = `default-src 'none'; script-src 'sha256-${hash(JS)}'; style-src 'sha256-${hash(CSS)}'; base-uri 'none'; form-action 'none'; connect-src 'none'; img-src 'none'`;
3142
+ const hash = (text) => createHash2("sha256").update(text).digest("base64");
3143
+ const policy = `default-src 'none'; script-src 'sha256-${hash(JS2)}'; style-src 'sha256-${hash(CSS2)}'; base-uri 'none'; form-action 'none'; connect-src 'none'; img-src 'none'`;
2749
3144
  const findingCards = report.findings.map(
2750
- (finding) => `<article class="finding" data-priority="${html(finding.priority)}"><span class="priority">${html(finding.priority)}</span><h3>${html(finding.title)}</h3><p><strong>Evidence:</strong> ${html(finding.evidence)}</p><p><strong>Next step:</strong> ${html(finding.recommendation)}</p><p class="note">Source: ${html(finding.source)}</p></article>`
3145
+ (finding) => `<article class="finding" data-priority="${html2(finding.priority)}"><span class="priority">${html2(finding.priority)}</span><h3>${html2(finding.title)}</h3><p><strong>Evidence:</strong> ${html2(finding.evidence)}</p><p><strong>Next step:</strong> ${html2(finding.recommendation)}</p><p class="note">Source: ${html2(finding.source)}</p></article>`
2751
3146
  ).join("");
2752
- const sections = tables(report).map(
2753
- (table) => `<section id="${table.id}" aria-labelledby="${table.id}-title"><h2 id="${table.id}-title">${table.title}</h2><p class="note">${html(table.note)}</p>${table.id === "applications" ? '<div class="toolbar js-only"><label for="app-search">Find an application</label><input id="app-search" type="search" placeholder="Filter application buckets"><span id="app-count" role="status"></span></div>' : ""}${table.rows.length ? `<div class="table-wrap" role="region" aria-label="${table.title}" tabindex="0"><table><caption>${table.rows.length} collected rows</caption><thead><tr>${table.headers.map((header) => `<th scope="col">${header}</th>`).join("")}</tr></thead><tbody>${table.rows.map((row) => `<tr>${row.map((cell) => `<td>${html(cell)}</td>`).join("")}</tr>`).join("")}</tbody></table></div>` : "<p>No rows available. Consult the source status before interpreting this as an empty result.</p>"}</section>`
3147
+ const sections = tables2(report).map(
3148
+ (table) => `<section id="${table.id}" aria-labelledby="${table.id}-title"><h2 id="${table.id}-title">${table.title}</h2><p class="note">${html2(table.note)}</p>${table.id === "applications" ? '<div class="toolbar js-only"><label for="app-search">Find an application</label><input id="app-search" type="search" placeholder="Filter application buckets"><span id="app-count" role="status"></span></div>' : ""}${table.rows.length ? `<div class="table-wrap" role="region" aria-label="${table.title}" tabindex="0"><table><caption>${table.rows.length} collected rows</caption><thead><tr>${table.headers.map((header) => `<th scope="col">${header}</th>`).join("")}</tr></thead><tbody>${table.rows.map((row) => `<tr>${row.map((cell) => `<td>${html2(cell)}</td>`).join("")}</tr>`).join("")}</tbody></table></div>` : "<p>No rows available. Consult the source status before interpreting this as an empty result.</p>"}</section>`
2754
3149
  ).join("");
2755
3150
  return `<!doctype html>
2756
- <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta http-equiv="Content-Security-Policy" content="${html(policy)}"><meta name="referrer" content="no-referrer"><title>${html(report.title)} \u2014 Prisma AIRS</title><style>${CSS}</style></head>
2757
- <body class="${html(report.health)}"><a class="skip" href="#main">Skip to report</a><header><div class="eyebrow">Prisma AIRS \xB7 AI Runtime Security</div><h1>${html(report.title)}</h1><span class="badge">${HEALTH[report.health]}</span><p>${html(report.window.description)}<br><time>${html(report.window.start)}</time> \u2192 <time>${html(report.window.end)}</time></p><p>Collected ${html(report.collectionStartedAt)} \u2192 ${html(report.generatedAt)}<br>Read-only evidence \xB7 Confidential environment metadata</p></header>
3151
+ <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta http-equiv="Content-Security-Policy" content="${html2(policy)}"><meta name="referrer" content="no-referrer"><title>${html2(report.title)} \u2014 Prisma AIRS</title><style>${CSS2}</style></head>
3152
+ <body class="${html2(report.health)}"><a class="skip" href="#main">Skip to report</a><header><div class="eyebrow">Prisma AIRS \xB7 AI Runtime Security</div><h1>${html2(report.title)}</h1><span class="badge">${HEALTH2[report.health]}</span><p>${html2(report.window.description)}<br><time>${html2(report.window.start)}</time> \u2192 <time>${html2(report.window.end)}</time></p><p>Collected ${html2(report.collectionStartedAt)} \u2192 ${html2(report.generatedAt)}<br>Read-only evidence \xB7 Confidential environment metadata</p></header>
2758
3153
  <main id="main"><nav aria-label="Report sections"><a href="#findings">Attention</a><a href="#applications">Activity</a><a href="#profiles">Profiles</a><a href="#inventory">Inventory</a><a href="#evidence">Evidence</a><a href="#scope">Scope</a></nav>
2759
3154
  <div class="cards">${metrics(report).map(([name, value]) => `<div class="card"><span>${name}</span><strong>${value}</strong></div>`).join("")}</div>
2760
3155
  <section id="findings"><h2>What needs attention</h2><p class="note">Observed violations and configuration review items, not a numerical health score. Collection gaps remain visible in the evidence table.</p><div class="toolbar js-only"><label for="finding-filter">Priority</label><select id="finding-filter"><option value="all">All findings</option><option value="attention">Attention</option><option value="review">Review</option><option value="info">Information</option></select><span id="finding-count" role="status"></span><button id="print-report" type="button">Print / save PDF</button></div>${findingCards || "<p>No findings in the available evidence. This does not establish an absence of risk.</p>"}</section>
2761
- ${sections}<section id="scope"><h2>Scope, privacy, and limitations</h2><ul>${report.limitations.map((note) => `<li>${html(note)}</li>`).join("")}</ul></section><footer>Generated by Prisma AIRS CLI \xB7 Report schema ${report.schemaVersion} \xB7 Self-contained HTML; no network access required. All evidence remains readable with JavaScript disabled.</footer></main><script>${JS}</script></body></html>
3156
+ ${sections}<section id="scope"><h2>Scope, privacy, and limitations</h2><ul>${report.limitations.map((note) => `<li>${html2(note)}</li>`).join("")}</ul></section><footer>Generated by Prisma AIRS CLI \xB7 Report schema ${report.schemaVersion} \xB7 Self-contained HTML; no network access required. All evidence remains readable with JavaScript disabled.</footer></main><script>${JS2}</script></body></html>
2762
3157
  `;
2763
3158
  }
2764
3159
 
@@ -2778,10 +3173,10 @@ function safeFailure(error) {
2778
3173
  }
2779
3174
  return "Source request or response validation failed; check connectivity and SDK compatibility.";
2780
3175
  }
2781
- function label(value) {
3176
+ function label2(value) {
2782
3177
  return (value?.replace(/[\p{Cc}\p{Cf}]/gu, " ").trim() || "Unknown").slice(0, 240);
2783
3178
  }
2784
- function count(value) {
3179
+ function count2(value) {
2785
3180
  return Number.isSafeInteger(value) && value >= 0 ? value : null;
2786
3181
  }
2787
3182
  async function collect(source2, maxPages, read, identity) {
@@ -2831,23 +3226,23 @@ function inventoryNext(next, size, offset) {
2831
3226
  function total(values) {
2832
3227
  if (values.some((value) => value === null)) return null;
2833
3228
  const result = values.reduce((sum, value) => sum + (value ?? 0), 0);
2834
- return count(result);
3229
+ return count2(result);
2835
3230
  }
2836
3231
  function distribution(rows) {
2837
3232
  const groups = /* @__PURE__ */ new Map();
2838
3233
  for (const row of rows) {
2839
- const name = label(row.violation_status).toLowerCase();
3234
+ const name = label2(row.violation_status).toLowerCase();
2840
3235
  groups.set(name, (groups.get(name) ?? 0) + 1);
2841
3236
  }
2842
3237
  return [...groups].map(([name, n]) => ({ name, count: n })).sort((a, b) => b.count - a.count);
2843
3238
  }
2844
3239
  function severity(value) {
2845
3240
  return {
2846
- critical: count(value.critical),
2847
- high: count(value.high),
2848
- medium: count(value.medium),
2849
- low: count(value.low),
2850
- total: count(value.total)
3241
+ critical: count2(value.critical),
3242
+ high: count2(value.high),
3243
+ medium: count2(value.medium),
3244
+ low: count2(value.low),
3245
+ total: count2(value.total)
2851
3246
  };
2852
3247
  }
2853
3248
  async function collectRuntimeDailyReport(client, options = {}) {
@@ -2910,7 +3305,7 @@ async function collectRuntimeDailyReport(client, options = {}) {
2910
3305
  offset
2911
3306
  });
2912
3307
  const size = page.items?.length ?? 0;
2913
- const totalItems = count(page.pagination?.total_items);
3308
+ const totalItems = count2(page.pagination?.total_items);
2914
3309
  if (totalItems !== null) {
2915
3310
  if (expectedAppTotal !== void 0 && totalItems !== expectedAppTotal)
2916
3311
  throw new ReportDataError(
@@ -2962,7 +3357,7 @@ async function collectRuntimeDailyReport(client, options = {}) {
2962
3357
  async (offset) => {
2963
3358
  const page = await client.dashboard.sessionsOverview({ ...daily, limit: 25, offset });
2964
3359
  const size = page.items?.length ?? 0;
2965
- const totalItems = count(page.pagination?.total_items);
3360
+ const totalItems = count2(page.pagination?.total_items);
2966
3361
  if (totalItems === null || page.pagination?.skip !== offset || page.pagination?.limit !== 25)
2967
3362
  throw new ReportDataError("Missing or mismatched session pagination metadata.");
2968
3363
  if (expectedSessionTotal !== void 0 && expectedSessionTotal !== totalItems)
@@ -3018,9 +3413,9 @@ async function collectRuntimeDailyReport(client, options = {}) {
3018
3413
  }
3019
3414
  const appIds = new Set(registered.map((app) => app.customer_appId));
3020
3415
  const reportApps = applications.map((app) => ({
3021
- name: label(app.name),
3022
- sessions: count(app.sessions_total),
3023
- violatingSessions: count(app.sessions_violated),
3416
+ name: label2(app.name),
3417
+ sessions: count2(app.sessions_total),
3418
+ violatingSessions: count2(app.sessions_violated),
3024
3419
  registered: app.id && appIds.has(app.id) ? true : appSource.status === "complete" && app.id ? false : null
3025
3420
  })).sort(
3026
3421
  (a, b) => (b.violatingSessions ?? -1) - (a.violatingSessions ?? -1) || a.name.localeCompare(b.name)
@@ -3066,9 +3461,9 @@ async function collectRuntimeDailyReport(client, options = {}) {
3066
3461
  (p) => p["model-configuration"] ? [p["model-configuration"]] : []
3067
3462
  );
3068
3463
  const timeoutActions = [
3069
- ...new Set(configs.map((c) => label(c.latency?.["inline-timeout-action"])))
3464
+ ...new Set(configs.map((c) => label2(c.latency?.["inline-timeout-action"])))
3070
3465
  ];
3071
- const name = label(profile.profile_name);
3466
+ const name = label2(profile.profile_name);
3072
3467
  if (profile.active === false)
3073
3468
  add(
3074
3469
  "review",
@@ -3097,7 +3492,7 @@ async function collectRuntimeDailyReport(client, options = {}) {
3097
3492
  );
3098
3493
  return {
3099
3494
  name,
3100
- revision: count(profile.revision),
3495
+ revision: count2(profile.revision),
3101
3496
  active: profile.active ?? null,
3102
3497
  modified: profile.last_modified_ts && Number.isFinite(Date.parse(profile.last_modified_ts)) ? new Date(profile.last_modified_ts).toISOString() : null,
3103
3498
  timeoutActions,
@@ -3145,8 +3540,8 @@ async function collectRuntimeDailyReport(client, options = {}) {
3145
3540
  "Review these sessions in SCM and check integration error handling. This is not a tenant-wide failure rate.",
3146
3541
  sessionSource.name
3147
3542
  );
3148
- const chartSessions = chartSource.status === "unavailable" ? null : total(chart.map((bucket) => count(bucket.total)));
3149
- const chartViolatingSessions = chartSource.status === "unavailable" ? null : total(chart.map((bucket) => count(bucket.violating)));
3543
+ const chartSessions = chartSource.status === "unavailable" ? null : total(chart.map((bucket) => count2(bucket.total)));
3544
+ const chartViolatingSessions = chartSource.status === "unavailable" ? null : total(chart.map((bucket) => count2(bucket.violating)));
3150
3545
  if (chartSessions !== null && sessionSource.status === "complete" && chartSessions !== rawSessions.length)
3151
3546
  add(
3152
3547
  "review",
@@ -3157,7 +3552,7 @@ async function collectRuntimeDailyReport(client, options = {}) {
3157
3552
  );
3158
3553
  const urgentViolations = total(
3159
3554
  trend.map(
3160
- (bucket) => total([count(bucket.violation_breakdown.critical), count(bucket.violation_breakdown.high)])
3555
+ (bucket) => total([count2(bucket.violation_breakdown.critical), count2(bucket.violation_breakdown.high)])
3161
3556
  )
3162
3557
  );
3163
3558
  if ((urgentViolations ?? 0) > 0)
@@ -3173,7 +3568,7 @@ async function collectRuntimeDailyReport(client, options = {}) {
3173
3568
  return {
3174
3569
  schemaVersion: 2,
3175
3570
  product: "Prisma AIRS AI Runtime Security",
3176
- title: label(options.title ?? "Daily environment report"),
3571
+ title: label2(options.title ?? "Daily environment report"),
3177
3572
  generatedAt: now().toISOString(),
3178
3573
  collectionStartedAt: end,
3179
3574
  window: {
@@ -3192,10 +3587,10 @@ async function collectRuntimeDailyReport(client, options = {}) {
3192
3587
  },
3193
3588
  profiles,
3194
3589
  registeredApps: registered.map((app) => ({
3195
- name: label(app.app_name),
3196
- environment: label(app.environment),
3197
- cloud: label(app.cloud_provider),
3198
- model: label(app.model_name),
3590
+ name: label2(app.app_name),
3591
+ environment: label2(app.environment),
3592
+ cloud: label2(app.cloud_provider),
3593
+ model: label2(app.model_name),
3199
3594
  keyAssociations: app.api_keys_dp_info ? app.api_keys_dp_info.length : null
3200
3595
  })).sort((a, b) => a.name.localeCompare(b.name)),
3201
3596
  sessions: {
@@ -3210,22 +3605,22 @@ async function collectRuntimeDailyReport(client, options = {}) {
3210
3605
  sessions: chartSessions,
3211
3606
  violatingSessions: chartViolatingSessions,
3212
3607
  buckets: chart.map((bucket) => ({
3213
- time: label(bucket.time),
3214
- sessions: count(bucket.total),
3215
- violatingSessions: count(bucket.violating),
3608
+ time: label2(bucket.time),
3609
+ sessions: count2(bucket.total),
3610
+ violatingSessions: count2(bucket.violating),
3216
3611
  violations: severity(bucket.violation_breakdown)
3217
3612
  }))
3218
3613
  },
3219
3614
  topApplications: rankings.map((app) => ({
3220
- name: label(app.name),
3221
- violations: count(app.total_violations),
3615
+ name: label2(app.name),
3616
+ violations: count2(app.total_violations),
3222
3617
  detectors: app.policy_violations.map((detector) => ({
3223
- name: label(detector.detection_type),
3224
- count: count(detector.total)
3618
+ name: label2(detector.detection_type),
3619
+ count: count2(detector.total)
3225
3620
  }))
3226
3621
  })),
3227
3622
  violationTrend: trend.map((bucket) => ({
3228
- time: label(bucket.date),
3623
+ time: label2(bucket.date),
3229
3624
  violations: severity(bucket.violation_breakdown)
3230
3625
  }))
3231
3626
  },
@@ -3268,6 +3663,9 @@ export {
3268
3663
  computeMetrics,
3269
3664
  computeCategoryBreakdown,
3270
3665
  writeReportFile,
3666
+ collectRedTeamEnvironmentReport,
3667
+ renderRedTeamReportMarkdown,
3668
+ renderRedTeamReportHtml,
3271
3669
  renderRuntimeReportMarkdown,
3272
3670
  renderRuntimeReportHtml,
3273
3671
  collectRuntimeDailyReport
package/dist/cli/index.js CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  SdkPromptSetService,
10
10
  SdkRedTeamService,
11
11
  SdkRuntimeService,
12
+ collectRedTeamEnvironmentReport,
12
13
  collectRuntimeDailyReport,
13
14
  computeMetrics,
14
15
  getOrCreateManagementClient,
@@ -16,6 +17,8 @@ import {
16
17
  loadConfig,
17
18
  readBackupDir,
18
19
  readBackupFile,
20
+ renderRedTeamReportHtml,
21
+ renderRedTeamReportMarkdown,
19
22
  renderRuntimeReportHtml,
20
23
  renderRuntimeReportMarkdown,
21
24
  resolveConfigFilePath,
@@ -24,7 +27,7 @@ import {
24
27
  validateTopic,
25
28
  writeBackupFile,
26
29
  writeReportFile
27
- } from "../chunk-IRLIFMGQ.js";
30
+ } from "../chunk-DR6MZG4E.js";
28
31
 
29
32
  // src/cli/index.ts
30
33
  import "dotenv/config";
@@ -43,7 +46,7 @@ function installProcessGuards() {
43
46
  }
44
47
 
45
48
  // src/cli/program.ts
46
- import { randomUUID as randomUUID6 } from "crypto";
49
+ import { randomUUID as randomUUID7 } from "crypto";
47
50
  import { readFileSync as readFileSync4 } from "fs";
48
51
  import { dirname as dirname4, join as join4 } from "path";
49
52
  import { fileURLToPath } from "url";
@@ -3052,8 +3055,8 @@ function registerAiGatewayInference(parent) {
3052
3055
  process.once("SIGINT", interrupt);
3053
3056
  process.once("SIGTERM", interrupt);
3054
3057
  process.stdout.on("error", outputError);
3055
- const write = (text2) => new Promise((resolve4, reject) => {
3056
- process.stdout.write(text2, (error) => error ? reject(error) : resolve4());
3058
+ const write = (text2) => new Promise((resolve5, reject) => {
3059
+ process.stdout.write(text2, (error) => error ? reject(error) : resolve5());
3057
3060
  });
3058
3061
  const requestOptions = { signal: controller.signal };
3059
3062
  const result = kind === "chat" ? await client.createChatCompletion(
@@ -5296,8 +5299,8 @@ async function withTimeout(promise, ms) {
5296
5299
  try {
5297
5300
  return await Promise.race([
5298
5301
  promise,
5299
- new Promise((resolve4) => {
5300
- timer = setTimeout(() => resolve4(TIMED_OUT), ms);
5302
+ new Promise((resolve5) => {
5303
+ timer = setTimeout(() => resolve5(TIMED_OUT), ms);
5301
5304
  })
5302
5305
  ]);
5303
5306
  } finally {
@@ -5567,12 +5570,12 @@ function hasBin(bin) {
5567
5570
  }
5568
5571
  }
5569
5572
  function run(bin, args, label) {
5570
- return new Promise((resolve4, reject) => {
5573
+ return new Promise((resolve5, reject) => {
5571
5574
  const child = spawn(bin, args, { stdio: "inherit" });
5572
5575
  child.on("error", (err) => reject(new Error(`Failed to start ${bin}: ${err.message}`)));
5573
5576
  child.on("close", (code) => {
5574
5577
  if (code !== 0) reject(new Error(`${label} failed with exit code ${code}`));
5575
- else resolve4();
5578
+ else resolve5();
5576
5579
  });
5577
5580
  });
5578
5581
  }
@@ -6150,6 +6153,76 @@ async function backupTargets(opts) {
6150
6153
  return results;
6151
6154
  }
6152
6155
 
6156
+ // src/cli/commands/redteam-dashboard.ts
6157
+ import { lstat } from "fs/promises";
6158
+ import { resolve } from "path";
6159
+ import { RedTeamClient } from "@cdot65/prisma-airs-sdk";
6160
+ import { randomUUID as randomUUID2 } from "crypto";
6161
+ function registerRedTeamDashboardCommand(redteam) {
6162
+ const command = redteam.command("dashboard").description("Generate a Red Team environment dashboard (read-only)").option("--output <format>", "Deliverable format: html or markdown (default: html)").option(
6163
+ "--output-file <path>",
6164
+ "Destination (default: new timestamped file in CWD); - for stdout; never overwrite"
6165
+ ).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
+ "after",
6167
+ examples(
6168
+ "airs redteam dashboard --output-file ./airs-daily.html",
6169
+ "airs redteam dashboard --output markdown --output-file ./airs-daily.md",
6170
+ "airs redteam dashboard",
6171
+ "airs redteam dashboard --strict --max-pages 20 --output-file - > daily.html"
6172
+ )
6173
+ ).action(async (opts) => {
6174
+ const format = opts.output ?? command.parent?.parent?.opts().output ?? "html";
6175
+ if (format !== "html" && format !== "markdown")
6176
+ usageError("Report output must be html or markdown");
6177
+ const maxPages = Number(opts.maxPages);
6178
+ if (!/^[1-9]\d*$/.test(opts.maxPages) || !Number.isSafeInteger(maxPages) || maxPages > 100)
6179
+ usageError("--max-pages must be an integer from 1 to 100");
6180
+ if (typeof opts.title !== "string" || !opts.title.trim() || opts.title.length > 240)
6181
+ usageError("--title must contain 1\u2013240 characters");
6182
+ if (opts.outputFile !== void 0 && !opts.outputFile.trim())
6183
+ usageError("--output-file cannot be empty");
6184
+ const defaultName = `airs-redteam-report-${(/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-")}-${randomUUID2().slice(0, 8)}.${format === "html" ? "html" : "md"}`;
6185
+ const outputPath = opts.outputFile === "-" ? void 0 : resolve(opts.outputFile ?? defaultName);
6186
+ try {
6187
+ if (outputPath) {
6188
+ const existing = await lstat(outputPath).catch((error) => {
6189
+ if (error.code !== "ENOENT") throw error;
6190
+ return void 0;
6191
+ });
6192
+ if (existing) throw new Error("Output already exists");
6193
+ }
6194
+ const config = await loadConfig();
6195
+ const client = new RedTeamClient({
6196
+ ...redTeamClientOptions(config),
6197
+ numRetries: 0
6198
+ });
6199
+ ui.status("Collecting Red Team statistics and current configuration (read-only)...");
6200
+ const report = await collectRedTeamEnvironmentReport(client, {
6201
+ title: opts.title,
6202
+ maxPages
6203
+ });
6204
+ const rendered = format === "html" ? renderRedTeamReportHtml(report) : renderRedTeamReportMarkdown(report);
6205
+ if (outputPath) {
6206
+ await writeReportFile(outputPath, rendered);
6207
+ ui.status(`Report \u2192 ${outputPath.replace(/[\p{Cc}\p{Cf}]/gu, "_")} (mode 0600)`);
6208
+ } else {
6209
+ await new Promise(
6210
+ (done, reject) => process.stdout.write(rendered, (error) => error ? reject(error) : done())
6211
+ );
6212
+ }
6213
+ const incomplete = report.sources.filter((source) => source.status !== "complete").length;
6214
+ if (incomplete)
6215
+ ui.status(`${incomplete} source(s) incomplete; review the evidence section.`);
6216
+ if (report.health === "unknown" || opts.strict && incomplete > 0) process.exitCode = 1;
6217
+ } catch {
6218
+ ui.error(
6219
+ "Report generation failed. Verify Management API configuration and a writable, new output path. Existing files are never overwritten."
6220
+ );
6221
+ process.exitCode = 1;
6222
+ }
6223
+ });
6224
+ }
6225
+
6153
6226
  // src/cli/commands/restore.ts
6154
6227
  var SERVER_DERIVED_FIELDS = [
6155
6228
  "uuid",
@@ -6392,6 +6465,7 @@ function parseAdapterVariables(input) {
6392
6465
  }
6393
6466
  function registerRedteamCommand(program) {
6394
6467
  const redteam = program.command("redteam").description("AI Red Team scan operations");
6468
+ registerRedTeamDashboardCommand(redteam);
6395
6469
  redteam.command("abort <jobId>").description("Abort a running scan").action(async (jobId) => {
6396
6470
  try {
6397
6471
  renderRedteamHeader();
@@ -7385,7 +7459,7 @@ function registerRedteamCommand(program) {
7385
7459
  }
7386
7460
 
7387
7461
  // src/cli/commands/runtime.ts
7388
- import { randomUUID as randomUUID5 } from "crypto";
7462
+ import { randomUUID as randomUUID6 } from "crypto";
7389
7463
  import * as fs5 from "fs";
7390
7464
  import { readFile as readFile10 } from "fs/promises";
7391
7465
  import { basename as basename3, dirname as dirname3, join as join3, resolve as resolvePath } from "path";
@@ -7633,7 +7707,7 @@ function mergeProfilePolicy(existing, overrides) {
7633
7707
  }
7634
7708
 
7635
7709
  // src/cli/bulk-scan-lock.ts
7636
- import { randomUUID as randomUUID2 } from "crypto";
7710
+ import { randomUUID as randomUUID3 } from "crypto";
7637
7711
  import * as fs3 from "fs/promises";
7638
7712
  function errorCode(error) {
7639
7713
  return error?.code;
@@ -7664,7 +7738,7 @@ function processIsAlive(pid) {
7664
7738
  }
7665
7739
  }
7666
7740
  async function installLock(lockPath, record) {
7667
- const candidate = `${lockPath}.candidate-${process.pid}-${randomUUID2()}`;
7741
+ const candidate = `${lockPath}.candidate-${process.pid}-${randomUUID3()}`;
7668
7742
  try {
7669
7743
  await fs3.writeFile(candidate, JSON.stringify(record), {
7670
7744
  encoding: "utf-8",
@@ -7682,7 +7756,7 @@ async function acquireBulkScanLock(statePath) {
7682
7756
  version: 1,
7683
7757
  pid: process.pid,
7684
7758
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
7685
- token: randomUUID2()
7759
+ token: randomUUID3()
7686
7760
  };
7687
7761
  for (let attempt = 0; attempt < 2; attempt++) {
7688
7762
  try {
@@ -7718,7 +7792,7 @@ async function acquireBulkScanLock(statePath) {
7718
7792
  }
7719
7793
 
7720
7794
  // src/cli/bulk-scan-state.ts
7721
- import { randomUUID as randomUUID3 } from "crypto";
7795
+ import { randomUUID as randomUUID4 } from "crypto";
7722
7796
  import * as fs4 from "fs/promises";
7723
7797
  import * as path2 from "path";
7724
7798
  import { z } from "zod";
@@ -7815,8 +7889,8 @@ async function saveBulkScanState(state, dir, filePath) {
7815
7889
  }
7816
7890
  await fs4.mkdir(dir, { recursive: true, mode: 448 });
7817
7891
  if (!filePath) await fs4.chmod(dir, 448);
7818
- const target = filePath ?? path2.join(dir, `${state.createdAt.replace(/[:.]/g, "-")}-${randomUUID3()}.bulk-scan.json`);
7819
- const temporary = `${target}.tmp-${process.pid}-${randomUUID3()}`;
7892
+ const target = filePath ?? path2.join(dir, `${state.createdAt.replace(/[:.]/g, "-")}-${randomUUID4()}.bulk-scan.json`);
7893
+ const temporary = `${target}.tmp-${process.pid}-${randomUUID4()}`;
7820
7894
  try {
7821
7895
  await fs4.writeFile(temporary, JSON.stringify(state, null, 2), {
7822
7896
  encoding: "utf-8",
@@ -8941,8 +9015,8 @@ function registerCleanupCommand(parent) {
8941
9015
  }
8942
9016
 
8943
9017
  // src/cli/commands/runtime-dashboard.ts
8944
- import { lstat } from "fs/promises";
8945
- import { resolve as resolve2 } from "path";
9018
+ import { lstat as lstat2 } from "fs/promises";
9019
+ import { resolve as resolve3 } from "path";
8946
9020
  import { dump as dump5 } from "js-yaml";
8947
9021
  var TIME_UNITS = ["hour", "hours", "day", "days"];
8948
9022
  function integer2(value, name, minimum = 0) {
@@ -9018,8 +9092,8 @@ function registerRuntimeDashboardCommands(runtime) {
9018
9092
  "Scan content requires exactly one of --show-content or --output-file <new-path>"
9019
9093
  );
9020
9094
  if (opts.outputFile) {
9021
- const path3 = resolve2(text(opts.outputFile, "--output-file"));
9022
- const existing = await lstat(path3).catch((error) => {
9095
+ const path3 = resolve3(text(opts.outputFile, "--output-file"));
9096
+ const existing = await lstat2(path3).catch((error) => {
9023
9097
  if (error.code !== "ENOENT") throw error;
9024
9098
  return void 0;
9025
9099
  });
@@ -9034,7 +9108,7 @@ function registerRuntimeDashboardCommands(runtime) {
9034
9108
  const result = await read(client.dashboard);
9035
9109
  const output = format === "yaml" ? dump5(result, { noRefs: true, lineWidth: -1 }).trimEnd() : JSON.stringify(result, null, 2);
9036
9110
  if (command.name() === "scan-content" && opts.outputFile) {
9037
- await writeReportFile(resolve2(String(opts.outputFile)), `${output}
9111
+ await writeReportFile(resolve3(String(opts.outputFile)), `${output}
9038
9112
  `);
9039
9113
  ui.status("Scan content exported to the requested private file (0600).");
9040
9114
  } else console.log(output);
@@ -9205,9 +9279,9 @@ function registerRuntimeDashboardCommands(runtime) {
9205
9279
  }
9206
9280
 
9207
9281
  // src/cli/commands/runtime-report.ts
9208
- import { lstat as lstat2 } from "fs/promises";
9209
- import { resolve as resolve3 } from "path";
9210
- import { randomUUID as randomUUID4 } from "crypto";
9282
+ import { lstat as lstat3 } from "fs/promises";
9283
+ import { resolve as resolve4 } from "path";
9284
+ import { randomUUID as randomUUID5 } from "crypto";
9211
9285
  function registerRuntimeReportCommand(runtime) {
9212
9286
  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(
9213
9287
  "--output-file <path>",
@@ -9231,11 +9305,11 @@ function registerRuntimeReportCommand(runtime) {
9231
9305
  usageError("--title must contain 1\u2013240 characters");
9232
9306
  if (opts.outputFile !== void 0 && !opts.outputFile.trim())
9233
9307
  usageError("--output-file cannot be empty");
9234
- const defaultName = `airs-runtime-report-${(/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-")}-${randomUUID4().slice(0, 8)}.${format === "html" ? "html" : "md"}`;
9235
- const outputPath = opts.outputFile === "-" ? void 0 : resolve3(opts.outputFile ?? defaultName);
9308
+ const defaultName = `airs-runtime-report-${(/* @__PURE__ */ new Date()).toISOString().replaceAll(":", "-")}-${randomUUID5().slice(0, 8)}.${format === "html" ? "html" : "md"}`;
9309
+ const outputPath = opts.outputFile === "-" ? void 0 : resolve4(opts.outputFile ?? defaultName);
9236
9310
  try {
9237
9311
  if (outputPath) {
9238
- const existing = await lstat2(outputPath).catch((error) => {
9312
+ const existing = await lstat3(outputPath).catch((error) => {
9239
9313
  if (error.code !== "ENOENT") throw error;
9240
9314
  return void 0;
9241
9315
  });
@@ -9722,7 +9796,7 @@ function completedBulkResults(state) {
9722
9796
  }
9723
9797
  async function writeBulkResults(outputPath, results) {
9724
9798
  await fs5.promises.mkdir(dirname3(outputPath), { recursive: true });
9725
- const temporary = `${outputPath}.tmp-${process.pid}-${randomUUID5()}`;
9799
+ const temporary = `${outputPath}.tmp-${process.pid}-${randomUUID6()}`;
9726
9800
  try {
9727
9801
  await fs5.promises.writeFile(temporary, SdkRuntimeService.formatResultsCsv(results), {
9728
9802
  encoding: "utf-8",
@@ -10567,12 +10641,12 @@ function buildProgram() {
10567
10641
  program.hook("preAction", async (_thisCommand, actionCommand) => {
10568
10642
  const root = actionCommand.optsWithGlobals?.() ?? _thisCommand.opts();
10569
10643
  setQuiet(Boolean(root.quiet));
10570
- const isRuntimeReport = actionCommand.name() === "report" && actionCommand.parent?.name() === "runtime";
10571
- if (isRuntimeReport && (root.debug || /^(1|true|yes|on)$/i.test(process.env.PANW_AI_SEC_DEBUG?.trim() ?? "")))
10644
+ const isEnvironmentReport = actionCommand.name() === "report" && actionCommand.parent?.name() === "runtime" || actionCommand.name() === "dashboard" && actionCommand.parent?.name() === "redteam";
10645
+ if (isEnvironmentReport && (root.debug || /^(1|true|yes|on)$/i.test(process.env.PANW_AI_SEC_DEBUG?.trim() ?? "")))
10572
10646
  usageError(
10573
10647
  "Disable --debug and PANW_AI_SEC_DEBUG for environment reports to avoid persisting sensitive traffic content"
10574
10648
  );
10575
- if (!isRuntimeReport && READ_COMMAND_NAMES.has(actionCommand.name()) && actionCommand.options.some((option) => option.long === "--output")) {
10649
+ if (!isEnvironmentReport && READ_COMMAND_NAMES.has(actionCommand.name()) && actionCommand.options.some((option) => option.long === "--output")) {
10576
10650
  try {
10577
10651
  const format = await resolveOutput(actionCommand, actionCommand.opts());
10578
10652
  actionCommand.setOptionValueWithSource("output", format, "implied");
@@ -10583,7 +10657,7 @@ function buildProgram() {
10583
10657
  if (root.debug) {
10584
10658
  const logPath = join4(
10585
10659
  process.cwd(),
10586
- `debug-api-${Date.now()}-${randomUUID6().slice(0, 8)}.jsonl`
10660
+ `debug-api-${Date.now()}-${randomUUID7().slice(0, 8)}.jsonl`
10587
10661
  );
10588
10662
  try {
10589
10663
  installDebugLogger(logPath);
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { CreateCustomTopicRequest, CustomTopic as CustomTopic$1, CreateSecurityProfileRequest, ManagementClientOptions, ModelSecurityClientOptions, RedTeamClientOptions, InitOptions, ManagementClient } from '@cdot65/prisma-airs-sdk';
1
+ import { CreateCustomTopicRequest, CustomTopic as CustomTopic$1, CreateSecurityProfileRequest, ManagementClientOptions, ModelSecurityClientOptions, RedTeamClientOptions, InitOptions, ManagementClient, RedTeamClient, QuotaSummary } from '@cdot65/prisma-airs-sdk';
2
2
  import { z } from 'zod';
3
3
 
4
4
  /**
@@ -1970,6 +1970,45 @@ interface RuntimeReportOptions {
1970
1970
  now?: () => Date;
1971
1971
  }
1972
1972
 
1973
+ /** Read-only SDK surface; older SDK installations explicitly report quota as unavailable. */
1974
+ type RedTeamReportClient = Pick<RedTeamClient, 'getDashboardOverview' | 'getScanStatistics'> & {
1975
+ targets: Pick<RedTeamClient['targets'], 'list'>;
1976
+ scans: Pick<RedTeamClient['scans'], 'list'>;
1977
+ adapters: Pick<RedTeamClient['adapters'], 'list'>;
1978
+ networkBroker: Pick<RedTeamClient['networkBroker'], 'getChannelStats'>;
1979
+ getQuotaSummary?: () => Promise<QuotaSummary>;
1980
+ };
1981
+ interface RedTeamReportTable {
1982
+ title: string;
1983
+ note: string;
1984
+ headers: string[];
1985
+ rows: Array<Array<string | number | null>>;
1986
+ }
1987
+ /** Aggregate-only projection: no target credentials, identities, scripts, prompts or raw responses. */
1988
+ interface RedTeamEnvironmentReport {
1989
+ schemaVersion: 1;
1990
+ product: 'Prisma AIRS AI Red Teaming';
1991
+ title: string;
1992
+ collectionStartedAt: string;
1993
+ generatedAt: string;
1994
+ window: {
1995
+ start: string;
1996
+ end: string;
1997
+ };
1998
+ health: 'attention' | 'review' | 'no-findings' | 'unknown';
1999
+ sources: ReportSource[];
2000
+ findings: ReportFinding[];
2001
+ tables: RedTeamReportTable[];
2002
+ limitations: string[];
2003
+ }
2004
+ /** Collect seven read-only feeds, with explicit source budgets and independent metric windows. */
2005
+ declare function collectRedTeamEnvironmentReport(client: RedTeamReportClient, options?: RuntimeReportOptions): Promise<RedTeamEnvironmentReport>;
2006
+
2007
+ /** Portable Markdown using the same allowlisted evidence as the HTML dashboard. */
2008
+ declare function renderRedTeamReportMarkdown(r: RedTeamEnvironmentReport): string;
2009
+ /** Self-contained HTML with hash-based CSP, static inline JS/CSS and no remote resources. */
2010
+ declare function renderRedTeamReportHtml(r: RedTeamEnvironmentReport): string;
2011
+
1973
2012
  /** Render the same evidence model as a portable, inert Markdown deliverable. */
1974
2013
  declare function renderRuntimeReportMarkdown(report: RuntimeDailyReport): string;
1975
2014
  /** Standalone HTML: inline CSS/JS, no dependencies, external requests, or embedded raw data. */
@@ -1981,4 +2020,4 @@ declare function renderRuntimeReportHtml(report: RuntimeDailyReport): string;
1981
2020
  */
1982
2021
  declare function collectRuntimeDailyReport(client: RuntimeReportClient, options?: RuntimeReportOptions): Promise<RuntimeDailyReport>;
1983
2022
 
1984
- export { AirsScanService, type AnalysisReport, type ApiKeyInfo, type ApiKeyListResult, type BackupEnvelope, type BackupFormat, type BackupResult, type BatchEntry, type BulkScanAction, type BulkScanResult, type CategoryBreakdown, type ConsumptionAppListEntry, type ConsumptionQueryOptions, type ConsumptionTimeInterval, type CustomTopic, type CustomerAppConsumption, type CustomerAppInfo, type CustomerAppListResult, type DeleteResponse, type DeploymentProfileInfo, type EfficacyMetrics, type EulaContent, type EulaStatus, type IndexedPrompt, type InstanceDetail, type InstanceRequest, type InstanceResponse, type IterationResult, type ManagementService, type ModelSecurityEvaluation, type ModelSecurityFile, type ModelSecurityFileListOptions, type ModelSecurityGroup, type ModelSecurityGroupCreateRequest, type ModelSecurityGroupListOptions, type ModelSecurityGroupUpdateRequest, type ModelSecurityLabel, type ModelSecurityModel, type ModelSecurityModelListOptions, type ModelSecurityModelVersion, type ModelSecurityModelVersionListOptions, type ModelSecurityPyPIAuth, type ModelSecurityRule, type ModelSecurityRuleEditableField, type ModelSecurityRuleInstance, type ModelSecurityRuleInstanceListOptions, type ModelSecurityRuleInstanceUpdateRequest, type ModelSecurityRuleListOptions, type ModelSecurityScan, type ModelSecurityScanListOptions, type ModelSecurityService, type ModelSecurityViolation, type MutationResponse, type PaginationOptions, type PollRetryOptions, type ProfileTopic, type PromptDetail, type PromptSetDetail, type PromptSetService, type PromptSetVersionInfo, type PropertyValueList, type RedTeamAdapterCreateRequest, type RedTeamAdapterDetail, type RedTeamAdapterListItem, type RedTeamAdapterListOptions, type RedTeamAdapterUpdateOverrides, type RedTeamAdapterValidateRequest, type RedTeamAdapterValidationResult, type RedTeamAdapterVar, type RedTeamAttack, type RedTeamCategory, type RedTeamChannel, type RedTeamChannelCreateRequest, type RedTeamChannelListOptions, type RedTeamChannelStats, type RedTeamChannelUpdateRequest, type RedTeamCustomAttack, type RedTeamCustomReport, type RedTeamDynamicReport, type RedTeamErrorLog, type RedTeamJob, type RedTeamLanguages, type RedTeamService, type RedTeamStaticReport, type RedTeamTarget, type RedTeamTargetCreateRequest, type RedTeamTargetDetail, type RedTeamTargetUpdateRequest, type RegistryCredentials, type ReliableRuntimeService, type ReportApplication, type ReportDailyTelemetry, type ReportFinding, type ReportFormat, type ReportPriority, type ReportProfile, type ReportRegisteredApp, type ReportSessionSummary, type ReportSeverity, type ReportSource, type ReportSourceStatus, type ResourceType, type RestoreResult, type RunState, type RuntimeDailyReport, type RuntimeReportClient, type RuntimeReportOptions, type RuntimeScanResult, type RuntimeService, SDK_ASYNC_BATCH_SIZE, type ScanLogQueryOptions, type ScanLogQueryResult, type ScanResult, type ScanService, SdkManagementService, SdkModelSecurityService, SdkPromptSetService, SdkRedTeamService, SdkRuntimeService, type SecurityProfileInfo, type SecurityProfileListResult, type SubmittedBatch, type TargetAuthValidationRequest, type TargetAuthValidationResult, type TargetOperationOptions, type TestCase, type TestResult, type UserInput, type ValidationError, collectRuntimeDailyReport, computeCategoryBreakdown, computeMetrics, loadConfig, readBackupDir, readBackupFile, renderRuntimeReportHtml, renderRuntimeReportMarkdown, resolveOutputDir, sanitizeFilename, validateDescription, validateExamples, validateName, validateTopic, writeBackupFile, writeReportFile };
2023
+ export { AirsScanService, type AnalysisReport, type ApiKeyInfo, type ApiKeyListResult, type BackupEnvelope, type BackupFormat, type BackupResult, type BatchEntry, type BulkScanAction, type BulkScanResult, type CategoryBreakdown, type ConsumptionAppListEntry, type ConsumptionQueryOptions, type ConsumptionTimeInterval, type CustomTopic, type CustomerAppConsumption, type CustomerAppInfo, type CustomerAppListResult, type DeleteResponse, type DeploymentProfileInfo, type EfficacyMetrics, type EulaContent, type EulaStatus, type IndexedPrompt, type InstanceDetail, type InstanceRequest, type InstanceResponse, type IterationResult, type ManagementService, type ModelSecurityEvaluation, type ModelSecurityFile, type ModelSecurityFileListOptions, type ModelSecurityGroup, type ModelSecurityGroupCreateRequest, type ModelSecurityGroupListOptions, type ModelSecurityGroupUpdateRequest, type ModelSecurityLabel, type ModelSecurityModel, type ModelSecurityModelListOptions, type ModelSecurityModelVersion, type ModelSecurityModelVersionListOptions, type ModelSecurityPyPIAuth, type ModelSecurityRule, type ModelSecurityRuleEditableField, type ModelSecurityRuleInstance, type ModelSecurityRuleInstanceListOptions, type ModelSecurityRuleInstanceUpdateRequest, type ModelSecurityRuleListOptions, type ModelSecurityScan, type ModelSecurityScanListOptions, type ModelSecurityService, type ModelSecurityViolation, type MutationResponse, type PaginationOptions, type PollRetryOptions, type ProfileTopic, type PromptDetail, type PromptSetDetail, type PromptSetService, type PromptSetVersionInfo, type PropertyValueList, type RedTeamAdapterCreateRequest, type RedTeamAdapterDetail, type RedTeamAdapterListItem, type RedTeamAdapterListOptions, type RedTeamAdapterUpdateOverrides, type RedTeamAdapterValidateRequest, type RedTeamAdapterValidationResult, type RedTeamAdapterVar, type RedTeamAttack, type RedTeamCategory, type RedTeamChannel, type RedTeamChannelCreateRequest, type RedTeamChannelListOptions, type RedTeamChannelStats, type RedTeamChannelUpdateRequest, type RedTeamCustomAttack, type RedTeamCustomReport, type RedTeamDynamicReport, type RedTeamEnvironmentReport, type RedTeamErrorLog, type RedTeamJob, type RedTeamLanguages, type RedTeamReportClient, type RedTeamReportTable, type RedTeamService, type RedTeamStaticReport, type RedTeamTarget, type RedTeamTargetCreateRequest, type RedTeamTargetDetail, type RedTeamTargetUpdateRequest, type RegistryCredentials, type ReliableRuntimeService, type ReportApplication, type ReportDailyTelemetry, type ReportFinding, type ReportFormat, type ReportPriority, type ReportProfile, type ReportRegisteredApp, type ReportSessionSummary, type ReportSeverity, type ReportSource, type ReportSourceStatus, type ResourceType, type RestoreResult, type RunState, type RuntimeDailyReport, type RuntimeReportClient, type RuntimeReportOptions, type RuntimeScanResult, type RuntimeService, SDK_ASYNC_BATCH_SIZE, type ScanLogQueryOptions, type ScanLogQueryResult, type ScanResult, type ScanService, SdkManagementService, SdkModelSecurityService, SdkPromptSetService, SdkRedTeamService, SdkRuntimeService, type SecurityProfileInfo, type SecurityProfileListResult, type SubmittedBatch, type TargetAuthValidationRequest, type TargetAuthValidationResult, type TargetOperationOptions, type TestCase, type TestResult, type UserInput, type ValidationError, collectRedTeamEnvironmentReport, collectRuntimeDailyReport, computeCategoryBreakdown, computeMetrics, loadConfig, readBackupDir, readBackupFile, renderRedTeamReportHtml, renderRedTeamReportMarkdown, renderRuntimeReportHtml, renderRuntimeReportMarkdown, resolveOutputDir, sanitizeFilename, validateDescription, validateExamples, validateName, validateTopic, writeBackupFile, writeReportFile };
package/dist/index.js CHANGED
@@ -6,12 +6,15 @@ import {
6
6
  SdkPromptSetService,
7
7
  SdkRedTeamService,
8
8
  SdkRuntimeService,
9
+ collectRedTeamEnvironmentReport,
9
10
  collectRuntimeDailyReport,
10
11
  computeCategoryBreakdown,
11
12
  computeMetrics,
12
13
  loadConfig,
13
14
  readBackupDir,
14
15
  readBackupFile,
16
+ renderRedTeamReportHtml,
17
+ renderRedTeamReportMarkdown,
15
18
  renderRuntimeReportHtml,
16
19
  renderRuntimeReportMarkdown,
17
20
  resolveOutputDir,
@@ -22,7 +25,7 @@ import {
22
25
  validateTopic,
23
26
  writeBackupFile,
24
27
  writeReportFile
25
- } from "./chunk-IRLIFMGQ.js";
28
+ } from "./chunk-DR6MZG4E.js";
26
29
  export {
27
30
  AirsScanService,
28
31
  SDK_ASYNC_BATCH_SIZE,
@@ -31,12 +34,15 @@ export {
31
34
  SdkPromptSetService,
32
35
  SdkRedTeamService,
33
36
  SdkRuntimeService,
37
+ collectRedTeamEnvironmentReport,
34
38
  collectRuntimeDailyReport,
35
39
  computeCategoryBreakdown,
36
40
  computeMetrics,
37
41
  loadConfig,
38
42
  readBackupDir,
39
43
  readBackupFile,
44
+ renderRedTeamReportHtml,
45
+ renderRedTeamReportMarkdown,
40
46
  renderRuntimeReportHtml,
41
47
  renderRuntimeReportMarkdown,
42
48
  resolveOutputDir,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cdot65/prisma-airs-cli",
3
3
  "packageManager": "pnpm@10.6.5",
4
- "version": "5.0.1",
4
+ "version": "5.1.0",
5
5
  "description": "CLI and library for Palo Alto Prisma AIRS — guardrail refinement, AI red teaming, model security scanning, profile audits",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",
@@ -59,7 +59,7 @@
59
59
  },
60
60
  "license": "MIT",
61
61
  "dependencies": {
62
- "@cdot65/prisma-airs-sdk": "0.26.0",
62
+ "@cdot65/prisma-airs-sdk": "0.27.0",
63
63
  "@inquirer/prompts": "^8.3.0",
64
64
  "chalk": "^5.6.2",
65
65
  "commander": "^14.0.3",