@cdot65/prisma-airs-cli 5.8.5 → 5.9.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.
Files changed (2) hide show
  1. package/dist/cli/index.js +60 -11
  2. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -8870,6 +8870,17 @@ async function parseBody(opts) {
8870
8870
  }
8871
8871
  }
8872
8872
 
8873
+ // src/cli/commands/dlp/visibility.ts
8874
+ function predefinedFlag(cmd) {
8875
+ return cmd.option(
8876
+ "--include-predefined",
8877
+ "Include predefined (PANW-shipped) records; listings show only tenant-created records by default"
8878
+ );
8879
+ }
8880
+ function visibleRecords(records, includePredefined) {
8881
+ return includePredefined ? records : records.filter((record) => record.type !== "predefined");
8882
+ }
8883
+
8873
8884
  // src/cli/commands/dlp/dictionaries.ts
8874
8885
  async function buildMetadata(opts) {
8875
8886
  if (opts.metadataFile) {
@@ -8894,6 +8905,7 @@ function register(dlp) {
8894
8905
  "Starting offset \u2014 rounds down to a page boundary",
8895
8906
  (v) => Number.parseInt(v, 10)
8896
8907
  ).option("--sort <field,dir>", "(repeatable)", (v, p = []) => [...p, v]).option("--keywords", "Include keyword list in response").option("--include-keywords", "Alias for --keywords").option("--output <fmt>", "Output format: pretty, table, markdown, csv, json, yaml");
8908
+ predefinedFlag(listCmd);
8897
8909
  registerPageAliases(listCmd, { sizeFlag: "--size", sizeKey: "size" });
8898
8910
  listCmd.action(async (opts) => {
8899
8911
  try {
@@ -8905,9 +8917,10 @@ function register(dlp) {
8905
8917
  sort: opts.sort,
8906
8918
  keywords: includeKeywords ? true : void 0
8907
8919
  };
8908
- const all = opts.all ? await svc.listAll({ ...params, max: Number(opts.max) }) : void 0;
8920
+ const all = opts.all ? await svc.listAll({ ...params, max: Number(opts.max) }) : (await svc.list({ ...params, page })).content;
8921
+ const visible = visibleRecords(all, opts.includePredefined);
8909
8922
  dlpDictionaries.renderList(
8910
- all ? { content: all, totalElements: all.length } : await svc.list({ ...params, page }),
8923
+ { content: visible, totalElements: visible.length },
8911
8924
  await resolveOutput(listCmd, opts)
8912
8925
  );
8913
8926
  } catch (err) {
@@ -9321,14 +9334,17 @@ async function resolveWriteBody(opts) {
9321
9334
  }
9322
9335
  function register4(dlp) {
9323
9336
  const group = dlp.command("patterns").description("DLP data patterns (full CRUD)");
9324
- const listCmd = listFlags2(group.command("list").description("List data patterns"));
9337
+ const listCmd = predefinedFlag(
9338
+ listFlags2(group.command("list").description("List data patterns (tenant-created by default)"))
9339
+ );
9325
9340
  listCmd.action(async (opts) => {
9326
9341
  try {
9327
9342
  const { page, size } = resolvePageParams(listCmd, opts);
9328
9343
  const svc = new SdkDataPatternsService(await loadDlpClientOptions());
9329
- const result = opts.all ? await svc.listAll({ size, sort: opts.sort, max: Number(opts.max) }) : void 0;
9344
+ const result = opts.all ? await svc.listAll({ size, sort: opts.sort, max: Number(opts.max) }) : (await svc.list({ page, size, sort: opts.sort })).content;
9345
+ const visible = visibleRecords(result, opts.includePredefined);
9330
9346
  dlpPatterns.renderList(
9331
- result ? { content: result, totalElements: result.length } : await svc.list({ page, size, sort: opts.sort }),
9347
+ { content: visible, totalElements: visible.length },
9332
9348
  await resolveOutput(listCmd, opts)
9333
9349
  );
9334
9350
  } catch (err) {
@@ -9467,14 +9483,17 @@ function register5(dlp) {
9467
9483
  const group = dlp.command("profiles").description(
9468
9484
  "DLP data profiles. No supported DELETE; status-based retirement is not live-verified."
9469
9485
  );
9470
- const listCmd = listFlags3(group.command("list").description("List data profiles"));
9486
+ const listCmd = predefinedFlag(
9487
+ listFlags3(group.command("list").description("List data profiles (tenant-created by default)"))
9488
+ );
9471
9489
  listCmd.action(async (opts) => {
9472
9490
  try {
9473
9491
  const { page, size } = resolvePageParams(listCmd, opts);
9474
9492
  const svc = new SdkDataProfilesService(await loadDlpClientOptions());
9475
- const result = opts.all ? await svc.listAll({ size, sort: opts.sort, max: Number(opts.max) }) : void 0;
9493
+ const result = opts.all ? await svc.listAll({ size, sort: opts.sort, max: Number(opts.max) }) : (await svc.list({ page, size, sort: opts.sort })).content;
9494
+ const visible = visibleRecords(result, opts.includePredefined);
9476
9495
  dlpProfiles.renderList(
9477
- result ? { content: result, totalElements: result.length } : await svc.list({ page, size, sort: opts.sort }),
9496
+ { content: visible, totalElements: visible.length },
9478
9497
  await resolveOutput(listCmd, opts)
9479
9498
  );
9480
9499
  } catch (err) {
@@ -9752,6 +9771,10 @@ async function backupDlpResources(api, tsgId, options = {}) {
9752
9771
  if (RETIRED_PROFILE.has(profile.profile_status ?? "active")) continue;
9753
9772
  const name = profile.name ?? "(unnamed)";
9754
9773
  try {
9774
+ if (!profile.detection_rules?.length)
9775
+ throw new Error(
9776
+ "has no exported detection rules (the API does not expose basic data profile content)"
9777
+ );
9755
9778
  for (const leaf of ruleLeaves(profile)) {
9756
9779
  const id = leaf.value.id;
9757
9780
  if (id == null || id === "") {
@@ -9769,6 +9792,8 @@ async function backupDlpResources(api, tsgId, options = {}) {
9769
9792
  } else {
9770
9793
  const record = patternById.get(id);
9771
9794
  if (!record) throw new Error(`references an unknown data pattern: ${id}`);
9795
+ if (RETIRED_PATTERN.has(record.status ?? "active"))
9796
+ throw new Error(`references a retired data pattern: ${record.name ?? id}`);
9772
9797
  if (leaf.value.version != null && record.version !== leaf.value.version)
9773
9798
  throw new Error(
9774
9799
  `references data pattern version ${leaf.value.version}, but the catalog holds version ${record.version ?? "unknown"}: ${id}`
@@ -9822,6 +9847,10 @@ function compareEcho(request, readBack) {
9822
9847
  differences.push(path3);
9823
9848
  return;
9824
9849
  }
9850
+ if (path3.endsWith(".supported_confidence_levels") && Array.isArray(a) && Array.isArray(b) && a.every((level) => b.includes(level))) {
9851
+ if (a.length !== b.length) serverAdded.push(path3);
9852
+ return;
9853
+ }
9825
9854
  if (Array.isArray(a) || Array.isArray(b)) {
9826
9855
  if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) {
9827
9856
  differences.push(path3);
@@ -10049,6 +10078,20 @@ async function planDlpResourcesRestore(api, input2, destinationTsgId, options =
10049
10078
  `Data pattern uses a tenant-bound detection technique: ${source.name}; provision it in the destination and bind it with --pattern-map "${source.name}=<destination-name>"`
10050
10079
  );
10051
10080
  }
10081
+ if (["regex", "weighted_regex"].includes(source.detection_config?.technique ?? "") && !(Array.isArray(source.matching_rules?.regexes) && source.matching_rules.regexes.length)) {
10082
+ if (options.skipUnresolved) {
10083
+ unresolved.push({
10084
+ kind: "pattern",
10085
+ sourceId: source.id,
10086
+ name: source.name,
10087
+ reason: "detection content is not exported by the API (copy of a predefined pattern)"
10088
+ });
10089
+ continue;
10090
+ }
10091
+ throw new Error(
10092
+ `Data pattern has no exported detection content (a copy of a predefined pattern): ${source.name}; bind an equivalent with --pattern-map "${source.name}=<destination-name>" or exclude dependents with --skip-unresolved`
10093
+ );
10094
+ }
10052
10095
  const name = `${prefix}${source.name}`;
10053
10096
  assertName(name);
10054
10097
  const body = patternRequest(source, name);
@@ -10271,7 +10314,9 @@ async function restoreDlpResources(api, plan, options = {}) {
10271
10314
  const readBack = await api.dictionaries.get(created.id, { includeKeywords: true });
10272
10315
  const echo = dictionaryEcho(item.metadata, readBack, item.keywords);
10273
10316
  if (!echo.matches || readBack.name !== item.name)
10274
- throw new Error(`Restored dictionary did not verify: ${item.name}`);
10317
+ throw new Error(
10318
+ `Restored dictionary did not verify: ${item.name} (created id ${created.id}; inspect it before retrying)`
10319
+ );
10275
10320
  if (echo.serverAdded.length)
10276
10321
  result.serverAdded.push({ resource: `dictionary ${item.name}`, fields: echo.serverAdded });
10277
10322
  bindings.set(item.sourceId, { id: created.id, name: item.name });
@@ -10306,7 +10351,9 @@ async function restoreDlpResources(api, plan, options = {}) {
10306
10351
  const readBack = await api.patterns.get(created.id);
10307
10352
  const echo = compareEcho(item.body, readBack);
10308
10353
  if (!echo.matches || readBack.name !== item.name || RETIRED_PATTERN.has(readBack.status ?? "active"))
10309
- throw new Error(`Restored data pattern did not verify: ${item.name}`);
10354
+ throw new Error(
10355
+ `Restored data pattern did not verify: ${item.name} (created id ${created.id}; inspect it before retrying)`
10356
+ );
10310
10357
  if (echo.serverAdded.length)
10311
10358
  result.serverAdded.push({
10312
10359
  resource: `data pattern ${item.name}`,
@@ -10357,7 +10404,9 @@ async function restoreDlpResources(api, plan, options = {}) {
10357
10404
  const readBack = await api.profiles.get(created.id);
10358
10405
  const echo = compareEcho(body, readBack);
10359
10406
  if (!echo.matches || readBack.name !== item.name || RETIRED_PROFILE.has(readBack.profile_status ?? "active"))
10360
- throw new Error(`Restored data profile did not verify: ${item.name}`);
10407
+ throw new Error(
10408
+ `Restored data profile did not verify: ${item.name} (created id ${created.id}; inspect it before retrying)`
10409
+ );
10361
10410
  if (echo.serverAdded.length)
10362
10411
  result.serverAdded.push({
10363
10412
  resource: `data profile ${item.name}`,
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.8.5",
4
+ "version": "5.9.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",