@cdot65/prisma-airs-cli 4.3.0 → 4.4.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
@@ -5,7 +5,7 @@
5
5
  [![CI](https://github.com/cdot65/prisma-airs-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/cdot65/prisma-airs-cli/actions/workflows/ci.yml)
6
6
  [![npm](https://img.shields.io/npm/v/@cdot65/prisma-airs-cli)](https://www.npmjs.com/package/@cdot65/prisma-airs-cli)
7
7
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
8
- [![Node 20+](https://img.shields.io/badge/node-%3E%3D20-brightgreen.svg)](https://nodejs.org/)
8
+ [![Node 20.17+ / 22.13+ / 24+](https://img.shields.io/badge/node-20.17%2B%20%7C%2022.13%2B%20%7C%2024%2B-brightgreen.svg)](https://nodejs.org/)
9
9
 
10
10
  **Full operational coverage over Palo Alto Prisma AIRS AI security — guardrail refinement, runtime scanning, AI red teaming, AI Gateway, and model security.**
11
11
 
@@ -30,7 +30,7 @@ npm install -g @cdot65/prisma-airs-cli
30
30
  airs --version
31
31
  ```
32
32
 
33
- Requires **Node.js >= 20**. Also available via `pnpm add -g`, `npx`, or as a [Docker image](https://github.com/cdot65/prisma-airs-cli/pkgs/container/prisma-airs-cli). See the [installation guide](https://cdot65.github.io/prisma-airs-cli/getting-started/installation/) for details.
33
+ Requires **Node.js 20.17+, 22.13+, or 24+** (exact engine range: `^20.17.0 || ^22.13.0 || >=23.5.0`). Also available via `pnpm add -g`, `npx`, or as a [Docker image](https://github.com/cdot65/prisma-airs-cli/pkgs/container/prisma-airs-cli). See the [installation guide](https://cdot65.github.io/prisma-airs-cli/getting-started/installation/) for details.
34
34
 
35
35
  ## Quick Start
36
36
 
package/dist/cli/index.js CHANGED
@@ -4549,6 +4549,12 @@ function registerAiGatewayInventory(root) {
4549
4549
  );
4550
4550
  }
4551
4551
 
4552
+ // src/cli/commands/aigateway/telemetry.ts
4553
+ import {
4554
+ AI_GW_GROUP_COLUMNS,
4555
+ AI_GW_GROUP_DIMENSIONS
4556
+ } from "@cdot65/prisma-airs-sdk";
4557
+
4552
4558
  // src/cli/commands/aigateway/telemetry-filters.ts
4553
4559
  import { AIGatewayChartFiltersSchema as AIGatewayChartFiltersSchema2 } from "@cdot65/prisma-airs-sdk";
4554
4560
  function addChartFilterOptions(command) {
@@ -4648,6 +4654,25 @@ function registerFilteredMetric(telemetry, name, description) {
4648
4654
  await runDetail(command, opts, (client) => client.telemetry[name](options));
4649
4655
  });
4650
4656
  }
4657
+ var groupDimensions = [...AI_GW_GROUP_DIMENSIONS, "status_code", "users"];
4658
+ function isGroupDimension(value) {
4659
+ return groupDimensions.some((dimension) => dimension === value);
4660
+ }
4661
+ function isGroupColumn(value) {
4662
+ return AI_GW_GROUP_COLUMNS.some((column) => column === value);
4663
+ }
4664
+ function groupOptionsFrom(dimension, opts) {
4665
+ const options = { ...windowFrom(opts), ...chartFiltersFrom(opts) };
4666
+ if (opts.columns !== void 0) {
4667
+ if (dimension === "users") throw new CliUsageError("User grouping does not support --columns");
4668
+ const columns = opts.columns.split(",").map((column) => column.trim());
4669
+ if (!columns.every(isGroupColumn)) {
4670
+ throw new CliUsageError(`Invalid --columns: choose from ${AI_GW_GROUP_COLUMNS.join(", ")}`);
4671
+ }
4672
+ options.columns = columns;
4673
+ }
4674
+ return options;
4675
+ }
4651
4676
  function registerAiGatewayTelemetryReads(telemetry) {
4652
4677
  const cache = showHelpOnEmpty(telemetry.command("cache").description("Inspect cache telemetry"));
4653
4678
  const cacheSummary = addWindowOptions(cache.command("summary").description("Get cache totals"));
@@ -4675,19 +4700,30 @@ function registerAiGatewayTelemetryReads(telemetry) {
4675
4700
  (opts) => runDetail(command, opts, (client) => client.telemetry[method](windowFrom(opts)))
4676
4701
  );
4677
4702
  }
4678
- const groupBy = addWindowOptions(
4679
- telemetry.command("group-by <dimension>").description("Aggregate telemetry by provider, model, status, or another SDK dimension").option("--columns <names>", "Comma-separated aggregate columns")
4680
- );
4681
- groupBy.action(
4682
- (dimension, opts) => runDetail(
4683
- groupBy,
4684
- opts,
4685
- (client) => client.telemetry.groupBy(dimension, {
4686
- ...windowFrom(opts),
4687
- ...opts.columns ? { columns: opts.columns.split(",").map((value) => value.trim()) } : {}
4688
- })
4703
+ const groupBy = addChartFilterOptions(
4704
+ addWindowOptions(
4705
+ telemetry.command("group-by <dimension>").description(`Aggregate telemetry by ${groupDimensions.join(", ")}`).option("--columns <names>", "Comma-separated aggregate columns (not supported for users)")
4689
4706
  )
4690
4707
  );
4708
+ groupBy.action(async (dimension, opts) => {
4709
+ let options;
4710
+ try {
4711
+ if (!isGroupDimension(dimension)) {
4712
+ throw new CliUsageError(
4713
+ `Invalid grouping dimension: choose from ${groupDimensions.join(", ")}`
4714
+ );
4715
+ }
4716
+ options = groupOptionsFrom(dimension, opts);
4717
+ } catch (error) {
4718
+ failAiGateway(error);
4719
+ }
4720
+ await runDetail(groupBy, opts, (client) => {
4721
+ if (dimension === "users") return client.telemetry.byUser(options);
4722
+ if (dimension === "status_code") return client.telemetry.byStatusCode(options);
4723
+ if (isGroupDimension(dimension)) return client.telemetry.groupBy(dimension, options);
4724
+ throw new CliUsageError("Invalid grouping dimension");
4725
+ });
4726
+ });
4691
4727
  registerFilteredMetric(telemetry, "latency", "Get latency telemetry");
4692
4728
  const logs = showHelpOnEmpty(telemetry.command("logs").description("Inspect request logs"));
4693
4729
  const logsList = addWindowOptions(
@@ -5220,17 +5256,23 @@ import { randomUUID } from "crypto";
5220
5256
  import { readFile as readFile4 } from "fs/promises";
5221
5257
  import { init, Scanner } from "@cdot65/prisma-airs-sdk";
5222
5258
  var DOCTOR_TIMEOUT_MS = 5e3;
5223
- var MIN_NODE_MAJOR = 20;
5259
+ var SUPPORTED_NODE_VERSIONS = "^20.17.0 || ^22.13.0 || >=23.5.0";
5224
5260
  function checkNodeVersion(version = process.version) {
5225
- const major = Number.parseInt(version.replace(/^v/, ""), 10);
5226
- if (Number.isFinite(major) && major >= MIN_NODE_MAJOR) {
5227
- return { name: "Node.js version", status: "pass", detail: `${version} (>= 20 required)` };
5261
+ const match = /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.exec(version);
5262
+ const [major, minor, patch] = match ? match.slice(1).map(Number) : [];
5263
+ const supported = [major, minor, patch].every(Number.isSafeInteger) && (major === 20 && minor >= 17 || major === 22 && minor >= 13 || major === 23 && minor >= 5 || major >= 24);
5264
+ if (supported) {
5265
+ return {
5266
+ name: "Node.js version",
5267
+ status: "pass",
5268
+ detail: `${version} (${SUPPORTED_NODE_VERSIONS} required)`
5269
+ };
5228
5270
  }
5229
5271
  return {
5230
5272
  name: "Node.js version",
5231
5273
  status: "fail",
5232
- detail: `${version} is below the required Node ${MIN_NODE_MAJOR}`,
5233
- hint: `Upgrade to Node ${MIN_NODE_MAJOR}+ (e.g. via nvm or your package manager)`
5274
+ detail: `${version} does not satisfy ${SUPPORTED_NODE_VERSIONS}`,
5275
+ hint: "Use a supported Node runtime: 20.17+, 22.13+, or 24+ (e.g. via nvm or your package manager)"
5234
5276
  };
5235
5277
  }
5236
5278
  async function checkConfigFile(filePath) {
@@ -8489,6 +8531,30 @@ function register2(dlp) {
8489
8531
  });
8490
8532
  }
8491
8533
 
8534
+ // src/cli/renderer/dlp-generate.ts
8535
+ function renderGenerateSummary(summary, output) {
8536
+ if (output === "json") {
8537
+ console.log(JSON.stringify(summary, null, 2));
8538
+ return;
8539
+ }
8540
+ ui.header("DLP Test-File Generation");
8541
+ ui.keyValue([
8542
+ ["Output", summary.out],
8543
+ ["Seed", summary.seed],
8544
+ ["Clean", summary.clean],
8545
+ ["Dirty", summary.dirty],
8546
+ ["Manifest", summary.manifestPath]
8547
+ ]);
8548
+ ui.table(
8549
+ [
8550
+ { key: "format", label: "Format" },
8551
+ { key: "clean", label: "Clean" },
8552
+ { key: "dirty", label: "Dirty" }
8553
+ ],
8554
+ Object.entries(summary.byFormat).map(([format, counts]) => ({ format, ...counts }))
8555
+ );
8556
+ }
8557
+
8492
8558
  // src/cli/commands/dlp/generate.ts
8493
8559
  var ALL_FORMATS = ["pdf", "png", "jpeg", "svg", "docx"];
8494
8560
  var OPTIONAL_DEPS_HINT = "DLP generate requires optional dependencies. Install them with: pnpm add sharp pdf-lib docx piexifjs";
@@ -8517,39 +8583,29 @@ function parseTypes(value) {
8517
8583
  return types;
8518
8584
  }
8519
8585
  function register3(parent) {
8520
- parent.command("generate").description(
8586
+ const command = parent.command("generate").description(
8521
8587
  "Generate clean + dirty DLP test files (synthetic sensitive data) across PDF/PNG/JPEG/SVG/DOCX"
8522
- ).option("--types <list>", "Comma list: pdf,png,jpeg,svg,docx (or all)", "all").option("--count <n>", "Clean files per type", "1").option("--out <dir>", "Output base directory", "./temp").option("--techniques <list>", "all or comma list of technique ids", "all").option("--seed <n>", "Seed for reproducible payloads").option("--output <format>", "Summary format: pretty or json", "pretty").action(async (opts) => {
8588
+ ).option("--types <list>", "Comma list: pdf,png,jpeg,svg,docx (or all)", "all").option("--count <n>", "Clean files per type", "1").option("--out <dir>", "Output base directory", "./temp").option("--techniques <list>", "all or comma list of technique ids", "all").option("--seed <n>", "Safe integer seed for reproducible payloads").option("--output <format>", "Summary format: pretty or json", "pretty").action(async (opts) => {
8523
8589
  let types;
8524
8590
  try {
8525
8591
  types = parseTypes(opts.types);
8526
8592
  } catch (err) {
8527
8593
  usageError(err instanceof Error ? err.message : String(err));
8528
8594
  }
8529
- const count = Number.parseInt(opts.count, 10);
8530
- if (!Number.isInteger(count) || count < 1) {
8531
- usageError("--count must be a positive integer");
8595
+ const count = Number(opts.count);
8596
+ if (!/^\d+$/.test(opts.count) || !Number.isSafeInteger(count) || count < 1) {
8597
+ usageError("--count must be a positive safe integer");
8532
8598
  }
8533
8599
  const techniques = opts.techniques === "all" ? "all" : opts.techniques.split(",").map((t) => t.trim());
8534
- const seed = opts.seed === void 0 ? void 0 : Number.parseInt(opts.seed, 10);
8600
+ const seed = opts.seed === void 0 ? void 0 : Number(opts.seed);
8601
+ if (seed !== void 0 && (!/^-?\d+$/.test(opts.seed) || !Number.isSafeInteger(seed))) {
8602
+ usageError("--seed must be a safe integer");
8603
+ }
8535
8604
  try {
8605
+ const output = await resolveOutput(command, opts, { allowed: ["pretty", "json"] });
8536
8606
  const generateCorpus = await loadGenerateCorpus();
8537
8607
  const summary = await generateCorpus({ types, count, out: opts.out, techniques, seed });
8538
- if (opts.output === "json") {
8539
- console.log(JSON.stringify(summary, null, 2));
8540
- return;
8541
- }
8542
- ui.header("DLP Test-File Generation");
8543
- ui.keyValue([
8544
- ["Output", summary.out],
8545
- ["Seed", summary.seed],
8546
- ["Clean", summary.clean],
8547
- ["Dirty", summary.dirty],
8548
- ["Manifest", summary.manifestPath]
8549
- ]);
8550
- for (const [fmt, counts] of Object.entries(summary.byFormat)) {
8551
- ui.dim(`${fmt.padEnd(5)} clean=${counts.clean} dirty=${counts.dirty}`);
8552
- }
8608
+ renderGenerateSummary(summary, output);
8553
8609
  } catch (err) {
8554
8610
  fail(err);
8555
8611
  }
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": "4.3.0",
4
+ "version": "4.4.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",
@@ -14,11 +14,13 @@
14
14
  },
15
15
  "scripts": {
16
16
  "build": "tsup",
17
- "prepublishOnly": "pnpm run build",
17
+ "prepublishOnly": "pnpm audit:prod && pnpm run build",
18
18
  "dev": "tsx src/cli/index.ts",
19
19
  "test": "vitest run",
20
20
  "test:watch": "vitest",
21
21
  "test:coverage": "vitest run --coverage",
22
+ "test:release": "node --test .github/scripts/*.test.mjs",
23
+ "audit:prod": "pnpm audit --prod --audit-level low",
22
24
  "test:e2e": "vitest run --config vitest.e2e.config.ts",
23
25
  "test:e2e:aigateway": "vitest run --config vitest.e2e.config.ts tests/e2e/aigateway-cli.e2e.spec.ts",
24
26
  "lint": "biome check --config-path config .",
@@ -49,7 +51,7 @@
49
51
  "local:unlink": "pnpm uninstall --global @cdot65/prisma-airs-cli"
50
52
  },
51
53
  "engines": {
52
- "node": ">=20.0.0"
54
+ "node": "^20.17.0 || ^22.13.0 || >=23.5.0"
53
55
  },
54
56
  "repository": {
55
57
  "type": "git",
@@ -57,13 +59,13 @@
57
59
  },
58
60
  "license": "MIT",
59
61
  "dependencies": {
60
- "@cdot65/prisma-airs-sdk": "0.24.0",
62
+ "@cdot65/prisma-airs-sdk": "0.25.0",
61
63
  "@inquirer/prompts": "^8.3.0",
62
64
  "chalk": "^5.6.2",
63
65
  "commander": "^14.0.3",
64
66
  "dotenv": "^17.3.1",
65
- "js-yaml": "^4.1.1",
66
- "nanoid": "^5.0.0",
67
+ "js-yaml": "^4.3.2",
68
+ "nanoid": "^5.1.16",
67
69
  "p-limit": "^6.0.0",
68
70
  "zod": "^3.24.0"
69
71
  },
@@ -84,6 +86,6 @@
84
86
  "docx": "^9.6.1",
85
87
  "pdf-lib": "^1.17.1",
86
88
  "piexifjs": "^1.0.6",
87
- "sharp": "^0.34.5"
89
+ "sharp": "^0.35.4"
88
90
  }
89
91
  }