@cdot65/prisma-airs-cli 4.5.0 → 5.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -1
- package/dist/{chunk-NWK7RG2N.js → chunk-IRLIFMGQ.js} +206 -76
- package/dist/cli/index.js +356 -147
- package/dist/index.d.ts +45 -15
- package/dist/index.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
[](https://opensource.org/licenses/MIT)
|
|
8
8
|
[](https://nodejs.org/)
|
|
9
9
|
|
|
10
|
-
**
|
|
10
|
+
**Command-line workflows for Palo Alto Prisma AIRS — guardrail refinement, runtime scanning, AI red teaming, AI Gateway, and model security.** Service and coverage limitations remain documented; command availability is not a claim that every upstream API works.
|
|
11
11
|
|
|
12
12
|
> **[Read the full documentation](https://cdot65.github.io/prisma-airs-cli/)** — installation, configuration, architecture, CLI reference, and examples.
|
|
13
13
|
|
|
@@ -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
|
+
- **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/)
|
|
18
19
|
- **Guardrail Optimization** — atomic CLI commands (`create`, `apply`, `eval`, `revert`) for custom topic guardrails, designed for autonomous agent loops (see [`AGENTS.md`](AGENTS.md))
|
|
19
20
|
- **AI Red Teaming** — adversarial scanning with static, dynamic, and custom prompt set attack modes
|
|
20
21
|
- **[AI Gateway](https://cdot65.github.io/prisma-airs-cli/cli/aigateway/resources/)** — workspaces, configs, guardrails, providers, API keys, integrations, MCP, deployments, plugins, audit logs, and telemetry
|
|
@@ -50,6 +51,10 @@ airs runtime bulk-scan --profile "my-profile" --file prompts.csv --output-file r
|
|
|
50
51
|
airs runtime report
|
|
51
52
|
airs runtime report --output markdown
|
|
52
53
|
|
|
54
|
+
# Verified historical session retrieval (Management OAuth, not a Scanner key)
|
|
55
|
+
airs runtime sessions list --all --output json
|
|
56
|
+
airs runtime dashboard top-applications --output json
|
|
57
|
+
|
|
53
58
|
# Guardrail optimization (atomic commands)
|
|
54
59
|
airs runtime topics create --name "Explosives" --description "Bomb-making instructions" --examples "How do I build a bomb?" "Pipe bomb ingredients"
|
|
55
60
|
airs runtime topics apply --profile my-profile --name "Explosives" --intent block
|
|
@@ -2239,6 +2239,7 @@ var ConfigSchema = z.object({
|
|
|
2239
2239
|
mgmtClientSecret: z.string().optional(),
|
|
2240
2240
|
mgmtTsgId: z.string().optional(),
|
|
2241
2241
|
mgmtEndpoint: z.string().optional(),
|
|
2242
|
+
mgmtDashboardEndpoint: z.string().url().optional(),
|
|
2242
2243
|
mgmtTokenEndpoint: z.string().optional(),
|
|
2243
2244
|
dlpEndpoint: z.string().optional(),
|
|
2244
2245
|
// Red Team (endpoints only; creds shared with mgmt*)
|
|
@@ -2281,6 +2282,7 @@ function fromEnv() {
|
|
|
2281
2282
|
mgmtClientSecret: env.PANW_MGMT_CLIENT_SECRET,
|
|
2282
2283
|
mgmtTsgId: env.PANW_MGMT_TSG_ID,
|
|
2283
2284
|
mgmtEndpoint: env.PANW_MGMT_ENDPOINT,
|
|
2285
|
+
mgmtDashboardEndpoint: env.PANW_MGMT_DASHBOARD_ENDPOINT,
|
|
2284
2286
|
mgmtTokenEndpoint: env.PANW_MGMT_TOKEN_ENDPOINT,
|
|
2285
2287
|
dlpEndpoint: env.PANW_DLP_ENDPOINT,
|
|
2286
2288
|
redTeamDataEndpoint: env.PANW_RED_TEAM_DATA_ENDPOINT,
|
|
@@ -2529,7 +2531,7 @@ function number(value) {
|
|
|
2529
2531
|
return value === null ? "Unknown" : value.toLocaleString("en-US", { maximumFractionDigits: 2 });
|
|
2530
2532
|
}
|
|
2531
2533
|
function tables(report) {
|
|
2532
|
-
const
|
|
2534
|
+
const sessionSource = report.sources.find((source2) => source2.name === "Daily session inventory");
|
|
2533
2535
|
return [
|
|
2534
2536
|
{
|
|
2535
2537
|
title: "Daily application activity",
|
|
@@ -2585,14 +2587,46 @@ function tables(report) {
|
|
|
2585
2587
|
note: "Current registered applications, separate from scan-metadata application buckets. Association counts do not establish key validity or deployment health. No key values or auth codes are included."
|
|
2586
2588
|
},
|
|
2587
2589
|
{
|
|
2588
|
-
title: "Collected
|
|
2589
|
-
id: "
|
|
2590
|
-
headers: ["
|
|
2591
|
-
rows: [
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2590
|
+
title: "Collected session observations",
|
|
2591
|
+
id: "sessions",
|
|
2592
|
+
headers: ["Session status", "Entries"],
|
|
2593
|
+
rows: report.sessions.statuses.map((entry) => [entry.name, number(entry.count)]),
|
|
2594
|
+
note: `Source: ${sessionSource?.status ?? "unavailable"}. Timestamp-eligible entries: ${number(report.sessions.entries)}. Violated status: ${number(report.sessions.violatingSessions)}. Missing timestamps: ${report.sessions.missingTimestamps}; outside window: ${report.sessions.outsideWindow}. These are collected session counts, not scan actions or detector events. No scan content is fetched.`
|
|
2595
|
+
},
|
|
2596
|
+
{
|
|
2597
|
+
title: "Daily session chart",
|
|
2598
|
+
id: "session-chart",
|
|
2599
|
+
headers: ["Bucket time (UTC)", "Sessions", "Violating sessions", "Detector violations"],
|
|
2600
|
+
rows: report.dailyTelemetry.chart.buckets.map((bucket) => [
|
|
2601
|
+
bucket.time,
|
|
2602
|
+
number(bucket.sessions),
|
|
2603
|
+
number(bucket.violatingSessions),
|
|
2604
|
+
number(bucket.violations.total)
|
|
2605
|
+
]),
|
|
2606
|
+
note: `Independent chart totals: ${number(report.dailyTelemetry.chart.sessions)} sessions; ${number(report.dailyTelemetry.chart.violatingSessions)} violating sessions. Do not force these counters to equal a separately paginated inventory.`
|
|
2607
|
+
},
|
|
2608
|
+
{
|
|
2609
|
+
title: "Top applications by detector violations",
|
|
2610
|
+
id: "top-violations",
|
|
2611
|
+
headers: ["Application bucket", "Detector violations", "Detection types"],
|
|
2612
|
+
rows: report.dailyTelemetry.topApplications.map((app) => [
|
|
2613
|
+
app.name,
|
|
2614
|
+
number(app.violations),
|
|
2615
|
+
app.detectors.map((detector) => `${detector.name}: ${number(detector.count)}`).join(", ")
|
|
2616
|
+
]),
|
|
2617
|
+
note: "Server-ranked subset for one day, not a complete application inventory. Detector violations may exceed the number of violating sessions."
|
|
2618
|
+
},
|
|
2619
|
+
{
|
|
2620
|
+
title: "Daily detector severity trend",
|
|
2621
|
+
id: "violation-trend",
|
|
2622
|
+
headers: ["Bucket time (UTC)", "Critical", "High", "Medium", "Low", "Total"],
|
|
2623
|
+
rows: report.dailyTelemetry.violationTrend.map((bucket) => [
|
|
2624
|
+
bucket.time,
|
|
2625
|
+
...["critical", "high", "medium", "low", "total"].map(
|
|
2626
|
+
(key) => number(bucket.violations[key])
|
|
2627
|
+
)
|
|
2628
|
+
]),
|
|
2629
|
+
note: "API-reported detector-policy events over the rolling day. Severity counts are preserved independently from distinct session counts; no previous-day comparison is inferred."
|
|
2596
2630
|
},
|
|
2597
2631
|
{
|
|
2598
2632
|
title: "Evidence and collection coverage",
|
|
@@ -2679,7 +2713,7 @@ function renderRuntimeReportMarkdown(report) {
|
|
|
2679
2713
|
"",
|
|
2680
2714
|
...report.limitations.map((note) => `- ${md(note)}`),
|
|
2681
2715
|
"",
|
|
2682
|
-
|
|
2716
|
+
`Generated by Prisma AIRS CLI \xB7 Report schema ${report.schemaVersion}`,
|
|
2683
2717
|
""
|
|
2684
2718
|
);
|
|
2685
2719
|
return lines.join("\n");
|
|
@@ -2724,7 +2758,7 @@ function renderRuntimeReportHtml(report) {
|
|
|
2724
2758
|
<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>
|
|
2725
2759
|
<div class="cards">${metrics(report).map(([name, value]) => `<div class="card"><span>${name}</span><strong>${value}</strong></div>`).join("")}</div>
|
|
2726
2760
|
<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>
|
|
2727
|
-
${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
|
|
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>
|
|
2728
2762
|
`;
|
|
2729
2763
|
}
|
|
2730
2764
|
|
|
@@ -2799,16 +2833,25 @@ function total(values) {
|
|
|
2799
2833
|
const result = values.reduce((sum, value) => sum + (value ?? 0), 0);
|
|
2800
2834
|
return count(result);
|
|
2801
2835
|
}
|
|
2802
|
-
function distribution(rows
|
|
2836
|
+
function distribution(rows) {
|
|
2803
2837
|
const groups = /* @__PURE__ */ new Map();
|
|
2804
2838
|
for (const row of rows) {
|
|
2805
|
-
const name = label(row
|
|
2839
|
+
const name = label(row.violation_status).toLowerCase();
|
|
2806
2840
|
groups.set(name, (groups.get(name) ?? 0) + 1);
|
|
2807
2841
|
}
|
|
2808
2842
|
return [...groups].map(([name, n]) => ({ name, count: n })).sort((a, b) => b.count - a.count);
|
|
2809
2843
|
}
|
|
2844
|
+
function severity(value) {
|
|
2845
|
+
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)
|
|
2851
|
+
};
|
|
2852
|
+
}
|
|
2810
2853
|
async function collectRuntimeDailyReport(client, options = {}) {
|
|
2811
|
-
const maxPages = options.maxPages ??
|
|
2854
|
+
const maxPages = options.maxPages ?? 40;
|
|
2812
2855
|
if (!Number.isSafeInteger(maxPages) || maxPages < 1 || maxPages > 100) {
|
|
2813
2856
|
throw new Error("maxPages must be an integer from 1 to 100");
|
|
2814
2857
|
}
|
|
@@ -2827,14 +2870,35 @@ async function collectRuntimeDailyReport(client, options = {}) {
|
|
|
2827
2870
|
"Current configuration"
|
|
2828
2871
|
);
|
|
2829
2872
|
const appSource = source("Registered applications", "customerApps.list", "Current configuration");
|
|
2830
|
-
const
|
|
2831
|
-
"
|
|
2832
|
-
"
|
|
2833
|
-
"Rolling
|
|
2873
|
+
const sessionSource = source(
|
|
2874
|
+
"Daily session inventory",
|
|
2875
|
+
"dashboard.sessionsOverview",
|
|
2876
|
+
"Rolling 1 day"
|
|
2877
|
+
);
|
|
2878
|
+
const chartSource = source("Daily session chart", "dashboard.sessionsChart", "Rolling 1 day");
|
|
2879
|
+
const rankingSource = source(
|
|
2880
|
+
"Top application violations",
|
|
2881
|
+
"dashboard.topApplicationsViolations",
|
|
2882
|
+
"Rolling 1 day; server-ranked subset"
|
|
2883
|
+
);
|
|
2884
|
+
const trendSource = source(
|
|
2885
|
+
"Daily violation trend",
|
|
2886
|
+
"dashboard.applicationsViolationsTrend",
|
|
2887
|
+
"Rolling 1 day"
|
|
2834
2888
|
);
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2889
|
+
const sources = [
|
|
2890
|
+
activitySource,
|
|
2891
|
+
profileSource,
|
|
2892
|
+
appSource,
|
|
2893
|
+
sessionSource,
|
|
2894
|
+
chartSource,
|
|
2895
|
+
rankingSource,
|
|
2896
|
+
trendSource
|
|
2897
|
+
];
|
|
2898
|
+
const daily = { timeInterval: 1, timeUnit: "day" };
|
|
2899
|
+
let expectedSessionTotal;
|
|
2900
|
+
let expectedAppTotal;
|
|
2901
|
+
const [applications, profileRows, registered, rawSessions, chart, rankings, trend] = await Promise.all([
|
|
2838
2902
|
collect(
|
|
2839
2903
|
activitySource,
|
|
2840
2904
|
maxPages,
|
|
@@ -2847,8 +2911,19 @@ async function collectRuntimeDailyReport(client, options = {}) {
|
|
|
2847
2911
|
});
|
|
2848
2912
|
const size = page.items?.length ?? 0;
|
|
2849
2913
|
const totalItems = count(page.pagination?.total_items);
|
|
2914
|
+
if (totalItems !== null) {
|
|
2915
|
+
if (expectedAppTotal !== void 0 && totalItems !== expectedAppTotal)
|
|
2916
|
+
throw new ReportDataError(
|
|
2917
|
+
"Application total changed during pagination; retry for a stable inventory."
|
|
2918
|
+
);
|
|
2919
|
+
expectedAppTotal = totalItems;
|
|
2920
|
+
if (offset + size > totalItems)
|
|
2921
|
+
throw new ReportDataError("Application records exceed the advertised total.");
|
|
2922
|
+
}
|
|
2850
2923
|
if (totalItems !== null && totalItems > offset + size && size === 0) {
|
|
2851
|
-
throw new ReportDataError(
|
|
2924
|
+
throw new ReportDataError(
|
|
2925
|
+
"The API returned an empty page before its advertised total."
|
|
2926
|
+
);
|
|
2852
2927
|
}
|
|
2853
2928
|
return {
|
|
2854
2929
|
items: page.items,
|
|
@@ -2882,46 +2957,56 @@ async function collectRuntimeDailyReport(client, options = {}) {
|
|
|
2882
2957
|
(item) => item.customer_appId
|
|
2883
2958
|
),
|
|
2884
2959
|
collect(
|
|
2885
|
-
|
|
2960
|
+
sessionSource,
|
|
2886
2961
|
maxPages,
|
|
2887
2962
|
async (offset) => {
|
|
2888
|
-
const
|
|
2889
|
-
const
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
filter: "all",
|
|
2895
|
-
page_token: pageToken
|
|
2896
|
-
});
|
|
2897
|
-
const items = page.scan_result_for_dashboard?.scan_result_entries;
|
|
2898
|
-
if (page.page_number !== void 0 && page.page_number !== pageNumber) {
|
|
2963
|
+
const page = await client.dashboard.sessionsOverview({ ...daily, limit: 25, offset });
|
|
2964
|
+
const size = page.items?.length ?? 0;
|
|
2965
|
+
const totalItems = count(page.pagination?.total_items);
|
|
2966
|
+
if (totalItems === null || page.pagination?.skip !== offset || page.pagination?.limit !== 25)
|
|
2967
|
+
throw new ReportDataError("Missing or mismatched session pagination metadata.");
|
|
2968
|
+
if (expectedSessionTotal !== void 0 && expectedSessionTotal !== totalItems)
|
|
2899
2969
|
throw new ReportDataError(
|
|
2900
|
-
"
|
|
2970
|
+
"Session total changed during pagination; retry for a stable inventory."
|
|
2901
2971
|
);
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
throw new ReportDataError(
|
|
2910
|
-
"The scan-log continuation token repeated; collection stopped safely."
|
|
2911
|
-
);
|
|
2912
|
-
seenTokens.add(page.page_token);
|
|
2913
|
-
pageToken = page.page_token;
|
|
2914
|
-
}
|
|
2915
|
-
return { items, next };
|
|
2972
|
+
expectedSessionTotal = totalItems;
|
|
2973
|
+
if (offset + size > totalItems || size > 25 || size === 0 && offset < totalItems)
|
|
2974
|
+
throw new ReportDataError("Session page does not agree with its advertised total.");
|
|
2975
|
+
return {
|
|
2976
|
+
items: page.items,
|
|
2977
|
+
next: offset + size < totalItems ? offset + size : void 0
|
|
2978
|
+
};
|
|
2916
2979
|
},
|
|
2917
|
-
(item) => JSON.stringify([item.
|
|
2980
|
+
(item) => JSON.stringify([item.application_id, item.application_name, item.session_id])
|
|
2981
|
+
),
|
|
2982
|
+
collect(
|
|
2983
|
+
chartSource,
|
|
2984
|
+
1,
|
|
2985
|
+
async () => ({ items: (await client.dashboard.sessionsChart(daily)).buckets }),
|
|
2986
|
+
(item) => String(item.bucket_number)
|
|
2987
|
+
),
|
|
2988
|
+
collect(
|
|
2989
|
+
rankingSource,
|
|
2990
|
+
1,
|
|
2991
|
+
async () => ({
|
|
2992
|
+
items: (await client.dashboard.topApplicationsViolations(daily)).applications
|
|
2993
|
+
}),
|
|
2994
|
+
(item) => JSON.stringify([item.id, item.name])
|
|
2995
|
+
),
|
|
2996
|
+
collect(
|
|
2997
|
+
trendSource,
|
|
2998
|
+
1,
|
|
2999
|
+
async () => ({
|
|
3000
|
+
items: (await client.dashboard.applicationsViolationsTrend(daily)).violations
|
|
3001
|
+
}),
|
|
3002
|
+
(item) => String(item.bucket_number)
|
|
2918
3003
|
)
|
|
2919
3004
|
]);
|
|
2920
3005
|
const findings = [];
|
|
2921
3006
|
const add = (priority, title, evidence, recommendation, evidenceSource) => {
|
|
2922
3007
|
findings.push({ priority, title, evidence, recommendation, source: evidenceSource });
|
|
2923
3008
|
};
|
|
2924
|
-
for (const s of
|
|
3009
|
+
for (const s of sources) {
|
|
2925
3010
|
if (s.status !== "complete")
|
|
2926
3011
|
add(
|
|
2927
3012
|
"review",
|
|
@@ -3029,8 +3114,8 @@ async function collectRuntimeDailyReport(client, options = {}) {
|
|
|
3029
3114
|
);
|
|
3030
3115
|
let missingTimestamps = 0;
|
|
3031
3116
|
let outsideWindow = 0;
|
|
3032
|
-
const
|
|
3033
|
-
const timestamp = Date.parse(row.
|
|
3117
|
+
const sessionRows = rawSessions.filter((row) => {
|
|
3118
|
+
const timestamp = Date.parse(row.last_session_activity ?? "");
|
|
3034
3119
|
if (!Number.isFinite(timestamp)) {
|
|
3035
3120
|
missingTimestamps++;
|
|
3036
3121
|
return false;
|
|
@@ -3044,26 +3129,49 @@ async function collectRuntimeDailyReport(client, options = {}) {
|
|
|
3044
3129
|
if (missingTimestamps || outsideWindow)
|
|
3045
3130
|
add(
|
|
3046
3131
|
"review",
|
|
3047
|
-
"Some
|
|
3048
|
-
`${missingTimestamps} records lack a valid
|
|
3049
|
-
"Verify ingestion timestamps and collection-window drift before using
|
|
3050
|
-
|
|
3132
|
+
"Some session records excluded from daily analysis",
|
|
3133
|
+
`${missingTimestamps} records lack a valid last-activity timestamp; ${outsideWindow} fall outside the anchored UTC window.`,
|
|
3134
|
+
"Verify ingestion timestamps and collection-window drift before using session counts as daily totals.",
|
|
3135
|
+
sessionSource.name
|
|
3051
3136
|
);
|
|
3052
|
-
const
|
|
3053
|
-
(row) => ["failed", "error", "timeout"].includes(row.
|
|
3137
|
+
const failedSessions = sessionRows.filter(
|
|
3138
|
+
(row) => ["failed", "error", "timeout"].includes(row.violation_status.trim().toLowerCase())
|
|
3054
3139
|
).length;
|
|
3055
|
-
if (
|
|
3140
|
+
if (failedSessions)
|
|
3056
3141
|
add(
|
|
3057
3142
|
"attention",
|
|
3058
|
-
"
|
|
3059
|
-
`${
|
|
3060
|
-
"Review these
|
|
3061
|
-
|
|
3143
|
+
"Session processing failures observed",
|
|
3144
|
+
`${failedSessions} timestamp-eligible collected sessions explicitly report failed, error, or timeout status.`,
|
|
3145
|
+
"Review these sessions in SCM and check integration error handling. This is not a tenant-wide failure rate.",
|
|
3146
|
+
sessionSource.name
|
|
3147
|
+
);
|
|
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)));
|
|
3150
|
+
if (chartSessions !== null && sessionSource.status === "complete" && chartSessions !== rawSessions.length)
|
|
3151
|
+
add(
|
|
3152
|
+
"review",
|
|
3153
|
+
"Session sources disagree",
|
|
3154
|
+
`The chart reports ${chartSessions} sessions; the independently paginated inventory returned ${rawSessions.length}.`,
|
|
3155
|
+
"Preserve both measurements and verify in SCM. Separate rolling queries are not a consistent snapshot; the discrepancy has no verified cause.",
|
|
3156
|
+
chartSource.name
|
|
3157
|
+
);
|
|
3158
|
+
const urgentViolations = total(
|
|
3159
|
+
trend.map(
|
|
3160
|
+
(bucket) => total([count(bucket.violation_breakdown.critical), count(bucket.violation_breakdown.high)])
|
|
3161
|
+
)
|
|
3162
|
+
);
|
|
3163
|
+
if ((urgentViolations ?? 0) > 0)
|
|
3164
|
+
add(
|
|
3165
|
+
"attention",
|
|
3166
|
+
"High or critical detector violations observed",
|
|
3167
|
+
`${urgentViolations} high/critical detector violations in the returned daily trend. These are not distinct sessions.`,
|
|
3168
|
+
"Investigate the affected applications and confirm enforcement decisions before assigning incident severity.",
|
|
3169
|
+
trendSource.name
|
|
3062
3170
|
);
|
|
3063
3171
|
const priorities = { attention: 0, review: 1, info: 2 };
|
|
3064
3172
|
findings.sort((left, right) => priorities[left.priority] - priorities[right.priority]);
|
|
3065
3173
|
return {
|
|
3066
|
-
schemaVersion:
|
|
3174
|
+
schemaVersion: 2,
|
|
3067
3175
|
product: "Prisma AIRS AI Runtime Security",
|
|
3068
3176
|
title: label(options.title ?? "Daily environment report"),
|
|
3069
3177
|
generatedAt: now().toISOString(),
|
|
@@ -3073,10 +3181,8 @@ async function collectRuntimeDailyReport(client, options = {}) {
|
|
|
3073
3181
|
end,
|
|
3074
3182
|
description: "Rolling last 24 hours, UTC (approximate for server-relative application queries)"
|
|
3075
3183
|
},
|
|
3076
|
-
health:
|
|
3077
|
-
|
|
3078
|
-
) ? "unknown" : findings.some((f) => f.priority === "attention") ? "attention" : findings.some((f) => f.priority === "review") ? "review" : "no-findings",
|
|
3079
|
-
sources: [activitySource, profileSource, appSource, logSource],
|
|
3184
|
+
health: sources.every((s) => s.status === "unavailable") ? "unknown" : findings.some((f) => f.priority === "attention") ? "attention" : findings.some((f) => f.priority === "review") ? "review" : "no-findings",
|
|
3185
|
+
sources,
|
|
3080
3186
|
findings,
|
|
3081
3187
|
activity: {
|
|
3082
3188
|
sessions,
|
|
@@ -3092,21 +3198,45 @@ async function collectRuntimeDailyReport(client, options = {}) {
|
|
|
3092
3198
|
model: label(app.model_name),
|
|
3093
3199
|
keyAssociations: app.api_keys_dp_info ? app.api_keys_dp_info.length : null
|
|
3094
3200
|
})).sort((a, b) => a.name.localeCompare(b.name)),
|
|
3095
|
-
|
|
3096
|
-
entries:
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
tokens: logSource.status === "unavailable" ? null : total(logs.map((row) => count(row.tokens))),
|
|
3201
|
+
sessions: {
|
|
3202
|
+
entries: sessionSource.status === "unavailable" ? null : sessionRows.length,
|
|
3203
|
+
statuses: distribution(sessionRows),
|
|
3204
|
+
violatingSessions: sessionSource.status === "unavailable" ? null : sessionRows.filter((row) => row.violation_status === "violated").length,
|
|
3100
3205
|
missingTimestamps,
|
|
3101
3206
|
outsideWindow
|
|
3102
3207
|
},
|
|
3208
|
+
dailyTelemetry: {
|
|
3209
|
+
chart: {
|
|
3210
|
+
sessions: chartSessions,
|
|
3211
|
+
violatingSessions: chartViolatingSessions,
|
|
3212
|
+
buckets: chart.map((bucket) => ({
|
|
3213
|
+
time: label(bucket.time),
|
|
3214
|
+
sessions: count(bucket.total),
|
|
3215
|
+
violatingSessions: count(bucket.violating),
|
|
3216
|
+
violations: severity(bucket.violation_breakdown)
|
|
3217
|
+
}))
|
|
3218
|
+
},
|
|
3219
|
+
topApplications: rankings.map((app) => ({
|
|
3220
|
+
name: label(app.name),
|
|
3221
|
+
violations: count(app.total_violations),
|
|
3222
|
+
detectors: app.policy_violations.map((detector) => ({
|
|
3223
|
+
name: label(detector.detection_type),
|
|
3224
|
+
count: count(detector.total)
|
|
3225
|
+
}))
|
|
3226
|
+
})),
|
|
3227
|
+
violationTrend: trend.map((bucket) => ({
|
|
3228
|
+
time: label(bucket.date),
|
|
3229
|
+
violations: severity(bucket.violation_breakdown)
|
|
3230
|
+
}))
|
|
3231
|
+
},
|
|
3103
3232
|
limitations: [
|
|
3104
3233
|
"This is a read-only operational review, not an uptime SLA, compliance attestation, or proof that attacks succeeded or were blocked. No health score is invented.",
|
|
3105
3234
|
"Application activity uses the API\u2019s rolling one-day window; each paginated request evaluates its own server-relative window. Collection is not a transactionally consistent historical snapshot. Ingestion may lag.",
|
|
3106
|
-
"
|
|
3235
|
+
"Application buckets, session inventory, chart sessions, and detector violations are different measurements. They are not added together. Session inventory summaries use timestamp-eligible entries only; absent data is unknown.",
|
|
3107
3236
|
"Application buckets are keyed by registered application ID plus the literal scan metadata.app_name, which can differ from registered names. Do not sum session counts as unique users or unique tenant-wide sessions.",
|
|
3108
3237
|
"Current configuration is not a configuration-change audit. Missing policy settings are unknown, not disabled. Inactive profiles can be intentional; current profile state may differ from the revision used by a historical scan.",
|
|
3109
|
-
"
|
|
3238
|
+
"Daily charts, rankings and severity trends come from their own one-day endpoints. Rankings are a server-selected subset, not a complete inventory. Per-app token summaries and drill-downs require longer windows; no daily token usage or previous-day comparison is inferred.",
|
|
3239
|
+
"The legacy ScanLogsClient / scan-logs query path is broken and under refactor. This report uses the verified dashboard session APIs instead. It never automatically fetches transactions or stored scan content.",
|
|
3110
3240
|
"Prompts, responses, user identities/IPs, API keys, auth codes, tenant IDs, and raw errors are omitted. Application/profile names and configuration metadata remain confidential; review before sharing."
|
|
3111
3241
|
]
|
|
3112
3242
|
};
|