@cdot65/prisma-airs-cli 5.8.4 → 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.
- package/dist/cli/index.js +78 -20
- 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) }) :
|
|
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
|
-
|
|
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 =
|
|
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) }) :
|
|
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
|
-
|
|
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 =
|
|
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) }) :
|
|
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
|
-
|
|
9496
|
+
{ content: visible, totalElements: visible.length },
|
|
9478
9497
|
await resolveOutput(listCmd, opts)
|
|
9479
9498
|
);
|
|
9480
9499
|
} catch (err) {
|
|
@@ -9719,11 +9738,19 @@ async function backupDlpResources(api, tsgId, options = {}) {
|
|
|
9719
9738
|
if (!record.id) throw new Error("Inventory dictionary has no id");
|
|
9720
9739
|
if (record.type !== "predefined" && !Array.isArray(record.keywords))
|
|
9721
9740
|
throw new Error(`Dictionary keywords were not returned by the region: ${record.name}`);
|
|
9722
|
-
|
|
9741
|
+
const stored = record.type === "predefined" ? { id: record.id, name: record.name, type: record.type } : record;
|
|
9742
|
+
dictionaries.set(record.id, structuredClone(stored));
|
|
9723
9743
|
};
|
|
9724
9744
|
const addPattern = (record) => {
|
|
9725
9745
|
if (!record.id) throw new Error("Inventory data pattern has no id");
|
|
9726
|
-
|
|
9746
|
+
const stored = record.type === "predefined" ? {
|
|
9747
|
+
id: record.id,
|
|
9748
|
+
name: record.name,
|
|
9749
|
+
type: record.type,
|
|
9750
|
+
...record.version != null ? { version: record.version } : {},
|
|
9751
|
+
...record.detection_config?.technique ? { detection_config: { technique: record.detection_config.technique } } : {}
|
|
9752
|
+
} : record;
|
|
9753
|
+
patterns.set(record.id, structuredClone(stored));
|
|
9727
9754
|
};
|
|
9728
9755
|
if (kinds.includes("dictionaries")) {
|
|
9729
9756
|
for (const record of inventory2.dictionaries)
|
|
@@ -9744,6 +9771,10 @@ async function backupDlpResources(api, tsgId, options = {}) {
|
|
|
9744
9771
|
if (RETIRED_PROFILE.has(profile.profile_status ?? "active")) continue;
|
|
9745
9772
|
const name = profile.name ?? "(unnamed)";
|
|
9746
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
|
+
);
|
|
9747
9778
|
for (const leaf of ruleLeaves(profile)) {
|
|
9748
9779
|
const id = leaf.value.id;
|
|
9749
9780
|
if (id == null || id === "") {
|
|
@@ -9761,6 +9792,8 @@ async function backupDlpResources(api, tsgId, options = {}) {
|
|
|
9761
9792
|
} else {
|
|
9762
9793
|
const record = patternById.get(id);
|
|
9763
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}`);
|
|
9764
9797
|
if (leaf.value.version != null && record.version !== leaf.value.version)
|
|
9765
9798
|
throw new Error(
|
|
9766
9799
|
`references data pattern version ${leaf.value.version}, but the catalog holds version ${record.version ?? "unknown"}: ${id}`
|
|
@@ -9814,6 +9847,10 @@ function compareEcho(request, readBack) {
|
|
|
9814
9847
|
differences.push(path3);
|
|
9815
9848
|
return;
|
|
9816
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
|
+
}
|
|
9817
9854
|
if (Array.isArray(a) || Array.isArray(b)) {
|
|
9818
9855
|
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) {
|
|
9819
9856
|
differences.push(path3);
|
|
@@ -9885,14 +9922,15 @@ function normalizedName(value) {
|
|
|
9885
9922
|
}
|
|
9886
9923
|
function predefinedCandidates(source, records) {
|
|
9887
9924
|
const wanted = normalizedName(source.name ?? "");
|
|
9888
|
-
const
|
|
9889
|
-
|
|
9890
|
-
(record
|
|
9891
|
-
|
|
9925
|
+
const scored = /* @__PURE__ */ new Map();
|
|
9926
|
+
for (const record of records) {
|
|
9927
|
+
if (record.type !== "predefined" || record.name == null || RETIRED_PATTERN.has(record.status ?? "active") || record.detection_config?.technique !== source.detection_config?.technique)
|
|
9928
|
+
continue;
|
|
9892
9929
|
const name = normalizedName(record.name);
|
|
9893
|
-
const score = name === wanted ?
|
|
9894
|
-
|
|
9895
|
-
}
|
|
9930
|
+
const score = name === wanted ? 2 : name.includes(wanted) || wanted.includes(name) ? 1 : 0;
|
|
9931
|
+
if (score > 0) scored.set(record.name, Math.max(scored.get(record.name) ?? 0, score));
|
|
9932
|
+
}
|
|
9933
|
+
return [...scored.entries()].sort(([aName, aScore], [bName, bScore]) => bScore - aScore || aName.localeCompare(bName)).slice(0, 3).map(([name]) => name);
|
|
9896
9934
|
}
|
|
9897
9935
|
function matchByName(records, name, retired, label) {
|
|
9898
9936
|
const matches = records.filter((record) => record.name === name && !retired(record));
|
|
@@ -10040,6 +10078,20 @@ async function planDlpResourcesRestore(api, input2, destinationTsgId, options =
|
|
|
10040
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>"`
|
|
10041
10079
|
);
|
|
10042
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
|
+
}
|
|
10043
10095
|
const name = `${prefix}${source.name}`;
|
|
10044
10096
|
assertName(name);
|
|
10045
10097
|
const body = patternRequest(source, name);
|
|
@@ -10262,7 +10314,9 @@ async function restoreDlpResources(api, plan, options = {}) {
|
|
|
10262
10314
|
const readBack = await api.dictionaries.get(created.id, { includeKeywords: true });
|
|
10263
10315
|
const echo = dictionaryEcho(item.metadata, readBack, item.keywords);
|
|
10264
10316
|
if (!echo.matches || readBack.name !== item.name)
|
|
10265
|
-
throw new Error(
|
|
10317
|
+
throw new Error(
|
|
10318
|
+
`Restored dictionary did not verify: ${item.name} (created id ${created.id}; inspect it before retrying)`
|
|
10319
|
+
);
|
|
10266
10320
|
if (echo.serverAdded.length)
|
|
10267
10321
|
result.serverAdded.push({ resource: `dictionary ${item.name}`, fields: echo.serverAdded });
|
|
10268
10322
|
bindings.set(item.sourceId, { id: created.id, name: item.name });
|
|
@@ -10297,7 +10351,9 @@ async function restoreDlpResources(api, plan, options = {}) {
|
|
|
10297
10351
|
const readBack = await api.patterns.get(created.id);
|
|
10298
10352
|
const echo = compareEcho(item.body, readBack);
|
|
10299
10353
|
if (!echo.matches || readBack.name !== item.name || RETIRED_PATTERN.has(readBack.status ?? "active"))
|
|
10300
|
-
throw new Error(
|
|
10354
|
+
throw new Error(
|
|
10355
|
+
`Restored data pattern did not verify: ${item.name} (created id ${created.id}; inspect it before retrying)`
|
|
10356
|
+
);
|
|
10301
10357
|
if (echo.serverAdded.length)
|
|
10302
10358
|
result.serverAdded.push({
|
|
10303
10359
|
resource: `data pattern ${item.name}`,
|
|
@@ -10348,7 +10404,9 @@ async function restoreDlpResources(api, plan, options = {}) {
|
|
|
10348
10404
|
const readBack = await api.profiles.get(created.id);
|
|
10349
10405
|
const echo = compareEcho(body, readBack);
|
|
10350
10406
|
if (!echo.matches || readBack.name !== item.name || RETIRED_PROFILE.has(readBack.profile_status ?? "active"))
|
|
10351
|
-
throw new Error(
|
|
10407
|
+
throw new Error(
|
|
10408
|
+
`Restored data profile did not verify: ${item.name} (created id ${created.id}; inspect it before retrying)`
|
|
10409
|
+
);
|
|
10352
10410
|
if (echo.serverAdded.length)
|
|
10353
10411
|
result.serverAdded.push({
|
|
10354
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.
|
|
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",
|