@cdot65/prisma-airs-cli 5.11.1 → 6.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 +8 -8
- package/dist/{chunk-37SN57XQ.js → chunk-KGMQY2IT.js} +46 -108
- package/dist/cli/index.js +634 -450
- package/dist/index.d.ts +11 -12
- package/dist/index.js +1 -1
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -24,10 +24,9 @@
|
|
|
24
24
|
- **Model Security** — ML model supply chain scanning with security groups, rules, and violation tracking
|
|
25
25
|
- **Unified automation output** — resource reads support `pretty`, `table`, `markdown`, `csv`, `json`, and `yaml`, with pipe-safe stdout; environment deliverables use HTML or Markdown
|
|
26
26
|
- **Complete pagination** — consistent `--limit`, `--offset`, and `--all` traversal with a configurable safety cap
|
|
27
|
-
- **`airs doctor`** — one-command diagnostics for
|
|
28
|
-
- **Tenant configuration** — `airs tenant create|set|
|
|
27
|
+
- **`airs doctor`** — one-command diagnostics for the selected tenant, its credentials, and API connectivity
|
|
28
|
+
- **Tenant configuration** — `airs tenant create|switch|set|unset|get|list|read|path|delete` is the only configuration surface: guided setup with hidden secret prompts, individual setting updates, and existing-file registration without changing read-only mounts
|
|
29
29
|
- **Profile migration** — `airs runtime profiles backup|restore` exports private JSON/YAML, previews cross-tenant restores, remaps topics and explicit DLP dependencies, and verifies restored policies ([guide](https://cdot65.github.io/prisma-airs-cli/runtime/profile-transfer/))
|
|
30
|
-
- **`airs config`** — manage the selected config file from the CLI (`list`, `get`, `set`, `unset`, `path`)
|
|
31
30
|
|
|
32
31
|
## Install
|
|
33
32
|
|
|
@@ -41,8 +40,9 @@ Requires **Node.js 20.17+, 22.13+, or 24+** (exact engine range: `^20.17.0 || ^2
|
|
|
41
40
|
## Quick Start
|
|
42
41
|
|
|
43
42
|
```bash
|
|
44
|
-
# Configure credentials
|
|
45
|
-
|
|
43
|
+
# Configure credentials (prompts for TSG ID, client ID, and a hidden secret)
|
|
44
|
+
airs tenant create dev
|
|
45
|
+
airs tenant switch dev
|
|
46
46
|
|
|
47
47
|
# Check your setup
|
|
48
48
|
airs doctor
|
|
@@ -101,13 +101,13 @@ Resource read commands share one contract (environment report files have their o
|
|
|
101
101
|
- JSON/YAML lists are bare arrays of complete normalized records; detail reads are complete objects.
|
|
102
102
|
- Table, Markdown, and CSV are stable human-oriented projections. CSV uses RFC 4180 quoting.
|
|
103
103
|
- Data is written to stdout; status, paging hints, warnings, and errors are written to stderr.
|
|
104
|
-
- Output precedence is command `--output`, global `--output`, `defaultOutput
|
|
104
|
+
- Output precedence is command `--output`, global `--output`, `defaultOutput`, then `pretty`.
|
|
105
105
|
- Paginated lists use `--limit`, `--offset`, and `--all`. Complete traversal is capped at 10,000 records by default; change it with `--max`, or use `--max 0` for no cap.
|
|
106
106
|
- Profile and topic lists return only the latest revision by default. Use `--all-versions` or `--revision` when historical revisions are needed.
|
|
107
107
|
|
|
108
108
|
```bash
|
|
109
109
|
airs --output json runtime profiles list --all | jq '.[].profileName'
|
|
110
|
-
|
|
110
|
+
airs --output yaml runtime topics get "My Topic"
|
|
111
111
|
airs model-security scans list --all --max 25000 --output csv > scans.csv
|
|
112
112
|
```
|
|
113
113
|
|
|
@@ -125,7 +125,7 @@ The full guides, complete CLI reference, configuration, and architecture live on
|
|
|
125
125
|
|
|
126
126
|
## Configuration
|
|
127
127
|
|
|
128
|
-
|
|
128
|
+
Configuration lives only in tenant files managed by `airs tenant`; environment variables are not read. Every management-plane product (Management, DLP, Red Team, Model Security, AgentGuard, AI Gateway) authenticates with the tenant's single SCM OAuth credential set (`mgmtClientId`, `mgmtClientSecret`, `mgmtTsgId`); scanning adds `airsApiKey`. Set `defaultOutput` with `airs tenant set <name> defaultOutput json` to choose a default read format. See the [configuration guide](https://cdot65.github.io/prisma-airs-cli/getting-started/configuration/) for the full list.
|
|
129
129
|
|
|
130
130
|
## License
|
|
131
131
|
|
|
@@ -2239,38 +2239,31 @@ import { join as join2 } from "path";
|
|
|
2239
2239
|
// src/config/schema.ts
|
|
2240
2240
|
import { z } from "zod";
|
|
2241
2241
|
var ConfigSchema = z.object({
|
|
2242
|
-
// AIRS Scanner (runtime scan API)
|
|
2242
|
+
// AIRS Scanner (runtime scan API) — separate key-based auth
|
|
2243
2243
|
airsApiKey: z.string().optional(),
|
|
2244
2244
|
airsApiToken: z.string().optional(),
|
|
2245
2245
|
airsApiEndpoint: z.string().optional(),
|
|
2246
2246
|
airsNumRetries: z.coerce.number().int().min(0).max(5).optional(),
|
|
2247
|
-
//
|
|
2247
|
+
// Shared SCM OAuth2 client credentials
|
|
2248
2248
|
mgmtClientId: z.string().optional(),
|
|
2249
2249
|
mgmtClientSecret: z.string().optional(),
|
|
2250
2250
|
mgmtTsgId: z.string().optional(),
|
|
2251
2251
|
mgmtEndpoint: z.string().optional(),
|
|
2252
2252
|
mgmtDashboardEndpoint: z.string().url().optional(),
|
|
2253
2253
|
mgmtTokenEndpoint: z.string().optional(),
|
|
2254
|
+
// Product base-URL overrides (authentication always comes from mgmt*)
|
|
2254
2255
|
dlpEndpoint: z.string().optional(),
|
|
2255
|
-
// Red Team (endpoints only; creds shared with mgmt*)
|
|
2256
2256
|
redTeamDataEndpoint: z.string().optional(),
|
|
2257
2257
|
redTeamMgmtEndpoint: z.string().optional(),
|
|
2258
|
-
redTeamTokenEndpoint: z.string().optional(),
|
|
2259
2258
|
redTeamNetworkBrokerEndpoint: z.string().optional(),
|
|
2260
|
-
// Model Security (endpoints only; creds shared with mgmt*)
|
|
2261
2259
|
modelSecDataEndpoint: z.string().optional(),
|
|
2262
2260
|
modelSecMgmtEndpoint: z.string().optional(),
|
|
2263
|
-
modelSecTokenEndpoint: z.string().optional(),
|
|
2264
2261
|
agentGuardDataEndpoint: z.string().url().optional(),
|
|
2265
2262
|
agentGuardMgmtEndpoint: z.string().url().optional(),
|
|
2266
|
-
agentGuardTokenEndpoint: z.string().url().optional(),
|
|
2267
|
-
// AI Gateway (endpoints only; creds shared with mgmt*)
|
|
2268
2263
|
aiGwDataEndpoint: z.string().optional(),
|
|
2269
2264
|
aiGwAdminEndpoint: z.string().optional(),
|
|
2270
|
-
aiGwTokenEndpoint: z.string().optional(),
|
|
2271
|
-
// SCM IAM (workspace scopes); creds shared with mgmt*
|
|
2272
2265
|
iamEndpoint: z.string().optional(),
|
|
2273
|
-
//
|
|
2266
|
+
// AI Gateway runtime inference authenticates with a workspace key, never SCM OAuth.
|
|
2274
2267
|
aiGwInferenceEndpoint: z.string().optional(),
|
|
2275
2268
|
aiGwInferenceApiKey: z.string().optional(),
|
|
2276
2269
|
aiGwInferenceModel: z.string().optional(),
|
|
@@ -2281,6 +2274,13 @@ var ConfigSchema = z.object({
|
|
|
2281
2274
|
// Persistence
|
|
2282
2275
|
dataDir: z.string().default("~/.prisma-airs/runs")
|
|
2283
2276
|
});
|
|
2277
|
+
var CONFIG_KEYS = Object.keys(ConfigSchema.shape);
|
|
2278
|
+
var RETIRED_CONFIG_KEYS = [
|
|
2279
|
+
"redTeamTokenEndpoint",
|
|
2280
|
+
"modelSecTokenEndpoint",
|
|
2281
|
+
"agentGuardTokenEndpoint",
|
|
2282
|
+
"aiGwTokenEndpoint"
|
|
2283
|
+
];
|
|
2284
2284
|
|
|
2285
2285
|
// src/config/tenants.ts
|
|
2286
2286
|
import { randomUUID } from "crypto";
|
|
@@ -2289,7 +2289,7 @@ import { mkdir, open, rename, unlink } from "fs/promises";
|
|
|
2289
2289
|
import { homedir } from "os";
|
|
2290
2290
|
import { dirname, isAbsolute, join, resolve } from "path";
|
|
2291
2291
|
import { z as z2 } from "zod";
|
|
2292
|
-
var NameSchema = z2.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/)
|
|
2292
|
+
var NameSchema = z2.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/);
|
|
2293
2293
|
var EntrySchema = z2.object({
|
|
2294
2294
|
name: NameSchema,
|
|
2295
2295
|
configPath: z2.string().refine(isAbsolute),
|
|
@@ -2314,9 +2314,6 @@ function tenantStorePath() {
|
|
|
2314
2314
|
function expandConfigPath(path2) {
|
|
2315
2315
|
return path2 === "~" ? homedir() : path2.startsWith("~/") ? join(homedir(), path2.slice(2)) : path2;
|
|
2316
2316
|
}
|
|
2317
|
-
function defaultTenantConfigPath() {
|
|
2318
|
-
return join(homedir(), ".prisma-airs", "config.json");
|
|
2319
|
-
}
|
|
2320
2317
|
function missing(error) {
|
|
2321
2318
|
return error?.code === "ENOENT";
|
|
2322
2319
|
}
|
|
@@ -2333,10 +2330,6 @@ function readTenantStore() {
|
|
|
2333
2330
|
);
|
|
2334
2331
|
}
|
|
2335
2332
|
}
|
|
2336
|
-
function selectedTenant() {
|
|
2337
|
-
const store = readTenantStore();
|
|
2338
|
-
return store.tenants.find((entry) => entry.name === store.active);
|
|
2339
|
-
}
|
|
2340
2333
|
function readTenantConfig(path2, expectedTsgId) {
|
|
2341
2334
|
return ConfigSchema.parse(readTenantConfigFile(path2, expectedTsgId));
|
|
2342
2335
|
}
|
|
@@ -2358,15 +2351,6 @@ function readTenantConfigFile(path2, expectedTsgId) {
|
|
|
2358
2351
|
);
|
|
2359
2352
|
return parsed;
|
|
2360
2353
|
}
|
|
2361
|
-
function assertTenantEnvironment() {
|
|
2362
|
-
const conflicts = Object.keys(process.env).filter(
|
|
2363
|
-
(key) => /^PANW_(?:MGMT_|MODEL_SEC_|RED_TEAM_|AGENT_GUARD_|AI_GW_|DLP_|AI_SEC_API_)/.test(key) && Boolean(process.env[key])
|
|
2364
|
-
);
|
|
2365
|
-
if (conflicts.length)
|
|
2366
|
-
throw new Error(
|
|
2367
|
-
`Named tenant selection conflicts with environment overrides: ${conflicts.sort().join(", ")}. Unset them or switch to default.`
|
|
2368
|
-
);
|
|
2369
|
-
}
|
|
2370
2354
|
async function changeStore(change) {
|
|
2371
2355
|
const path2 = tenantStorePath();
|
|
2372
2356
|
await mkdir(dirname(path2), { recursive: true, mode: 448 });
|
|
@@ -2407,7 +2391,7 @@ async function changeStore(change) {
|
|
|
2407
2391
|
function validateTenantName(name) {
|
|
2408
2392
|
if (!NameSchema.safeParse(name).success)
|
|
2409
2393
|
throw new Error(
|
|
2410
|
-
"Tenant name must be 1\u201364 letters, digits, hyphens or underscores
|
|
2394
|
+
"Tenant name must be 1\u201364 letters, digits, hyphens or underscores and start with a letter/digit"
|
|
2411
2395
|
);
|
|
2412
2396
|
}
|
|
2413
2397
|
async function createTenant(name, configPath) {
|
|
@@ -2433,15 +2417,6 @@ async function createTenant(name, configPath) {
|
|
|
2433
2417
|
async function switchTenant(name) {
|
|
2434
2418
|
let selected;
|
|
2435
2419
|
await changeStore((store) => {
|
|
2436
|
-
if (name === "default") {
|
|
2437
|
-
store.active = null;
|
|
2438
|
-
return;
|
|
2439
|
-
}
|
|
2440
|
-
if (process.env.PRISMA_AIRS_CONFIG_PATH)
|
|
2441
|
-
throw new Error(
|
|
2442
|
-
"Unset PRISMA_AIRS_CONFIG_PATH before switching tenants; it overrides named selection"
|
|
2443
|
-
);
|
|
2444
|
-
assertTenantEnvironment();
|
|
2445
2420
|
selected = store.tenants.find((entry) => entry.name === name);
|
|
2446
2421
|
if (!selected) throw new Error("Tenant not found");
|
|
2447
2422
|
readTenantConfig(selected.configPath, selected.tsgId);
|
|
@@ -2450,56 +2425,22 @@ async function switchTenant(name) {
|
|
|
2450
2425
|
return selected;
|
|
2451
2426
|
}
|
|
2452
2427
|
async function deleteTenant(name) {
|
|
2428
|
+
let selectionCleared = false;
|
|
2453
2429
|
await changeStore((store) => {
|
|
2454
|
-
if (name === "default") throw new Error("The default tenant cannot be deleted");
|
|
2455
|
-
if (store.active === name)
|
|
2456
|
-
throw new Error("Switch to another tenant or default before deleting the active tenant");
|
|
2457
2430
|
if (!store.tenants.some((entry) => entry.name === name)) throw new Error("Tenant not found");
|
|
2458
2431
|
store.tenants = store.tenants.filter((entry) => entry.name !== name);
|
|
2432
|
+
if (store.active === name) {
|
|
2433
|
+
store.active = null;
|
|
2434
|
+
selectionCleared = true;
|
|
2435
|
+
}
|
|
2459
2436
|
});
|
|
2437
|
+
return { selectionCleared };
|
|
2460
2438
|
}
|
|
2461
2439
|
|
|
2462
2440
|
// src/config/loader.ts
|
|
2463
2441
|
function expandHome(p) {
|
|
2464
2442
|
return p.startsWith("~") ? join2(homedir2(), p.slice(1)) : p;
|
|
2465
2443
|
}
|
|
2466
|
-
function fromEnv() {
|
|
2467
|
-
const env = process.env;
|
|
2468
|
-
return {
|
|
2469
|
-
airsApiKey: env.PANW_AI_SEC_API_KEY,
|
|
2470
|
-
airsApiToken: env.PANW_AI_SEC_API_TOKEN,
|
|
2471
|
-
airsApiEndpoint: env.PANW_AI_SEC_API_ENDPOINT,
|
|
2472
|
-
airsNumRetries: env.PANW_AI_SEC_NUM_RETRIES,
|
|
2473
|
-
mgmtClientId: env.PANW_MGMT_CLIENT_ID,
|
|
2474
|
-
mgmtClientSecret: env.PANW_MGMT_CLIENT_SECRET,
|
|
2475
|
-
mgmtTsgId: env.PANW_MGMT_TSG_ID,
|
|
2476
|
-
mgmtEndpoint: env.PANW_MGMT_ENDPOINT,
|
|
2477
|
-
mgmtDashboardEndpoint: env.PANW_MGMT_DASHBOARD_ENDPOINT,
|
|
2478
|
-
mgmtTokenEndpoint: env.PANW_MGMT_TOKEN_ENDPOINT,
|
|
2479
|
-
dlpEndpoint: env.PANW_DLP_ENDPOINT,
|
|
2480
|
-
redTeamDataEndpoint: env.PANW_RED_TEAM_DATA_ENDPOINT,
|
|
2481
|
-
redTeamMgmtEndpoint: env.PANW_RED_TEAM_MGMT_ENDPOINT,
|
|
2482
|
-
redTeamTokenEndpoint: env.PANW_RED_TEAM_TOKEN_ENDPOINT,
|
|
2483
|
-
redTeamNetworkBrokerEndpoint: env.PANW_RED_TEAM_NETWORK_BROKER_ENDPOINT,
|
|
2484
|
-
modelSecDataEndpoint: env.PANW_MODEL_SEC_DATA_ENDPOINT,
|
|
2485
|
-
modelSecMgmtEndpoint: env.PANW_MODEL_SEC_MGMT_ENDPOINT,
|
|
2486
|
-
modelSecTokenEndpoint: env.PANW_MODEL_SEC_TOKEN_ENDPOINT,
|
|
2487
|
-
agentGuardDataEndpoint: env.PANW_AGENT_GUARD_DATA_ENDPOINT,
|
|
2488
|
-
agentGuardMgmtEndpoint: env.PANW_AGENT_GUARD_MGMT_ENDPOINT,
|
|
2489
|
-
agentGuardTokenEndpoint: env.PANW_AGENT_GUARD_TOKEN_ENDPOINT,
|
|
2490
|
-
aiGwDataEndpoint: env.PANW_AI_GW_DATA_ENDPOINT,
|
|
2491
|
-
aiGwAdminEndpoint: env.PANW_AI_GW_ADMIN_ENDPOINT,
|
|
2492
|
-
aiGwTokenEndpoint: env.PANW_AI_GW_TOKEN_ENDPOINT,
|
|
2493
|
-
iamEndpoint: env.PANW_IAM_ENDPOINT,
|
|
2494
|
-
aiGwInferenceEndpoint: env.PANW_AI_GW_INFERENCE_ENDPOINT,
|
|
2495
|
-
aiGwInferenceApiKey: env.PANW_AI_GW_INFERENCE_API_KEY,
|
|
2496
|
-
aiGwInferenceModel: env.PANW_AI_GW_INFERENCE_MODEL,
|
|
2497
|
-
aiGwEmbeddingModel: env.PANW_AI_GW_EMBEDDING_MODEL,
|
|
2498
|
-
scanConcurrency: env.SCAN_CONCURRENCY,
|
|
2499
|
-
defaultOutput: env.PANW_CLI_OUTPUT,
|
|
2500
|
-
dataDir: env.DATA_DIR
|
|
2501
|
-
};
|
|
2502
|
-
}
|
|
2503
2444
|
async function fromFile(path2) {
|
|
2504
2445
|
try {
|
|
2505
2446
|
const data = await readFile(path2, "utf-8");
|
|
@@ -2511,45 +2452,43 @@ async function fromFile(path2) {
|
|
|
2511
2452
|
function stripUndefined(obj) {
|
|
2512
2453
|
return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== void 0 && v !== ""));
|
|
2513
2454
|
}
|
|
2514
|
-
function
|
|
2515
|
-
return
|
|
2455
|
+
function noTenantSelectedMessage(registered) {
|
|
2456
|
+
return registered.length ? `No tenant selected. Run 'airs tenant switch <name>' (registered: ${registered.join(", ")})` : "No tenant selected. Run 'airs tenant create <name>' and then 'airs tenant switch <name>'";
|
|
2516
2457
|
}
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2458
|
+
var NoTenantSelectedError = class extends Error {
|
|
2459
|
+
constructor(registered) {
|
|
2460
|
+
super(noTenantSelectedMessage(registered));
|
|
2461
|
+
this.name = "NoTenantSelectedError";
|
|
2462
|
+
}
|
|
2463
|
+
};
|
|
2464
|
+
function resolveConfigContext(configFilePath) {
|
|
2465
|
+
const registryPath = tenantStorePath();
|
|
2466
|
+
if (configFilePath) {
|
|
2467
|
+
return { path: expandHome(configFilePath), selection: "explicit", registryPath };
|
|
2468
|
+
}
|
|
2469
|
+
const store = readTenantStore();
|
|
2470
|
+
const tenant = store.tenants.find((entry) => entry.name === store.active);
|
|
2471
|
+
if (tenant) return { path: tenant.configPath, selection: "tenant", tenant, registryPath };
|
|
2472
|
+
return { selection: "none", registryPath, registered: store.tenants.map((entry) => entry.name) };
|
|
2523
2473
|
}
|
|
2524
2474
|
async function readConfigSource(configFilePath) {
|
|
2525
|
-
const
|
|
2526
|
-
if (
|
|
2527
|
-
|
|
2528
|
-
return readTenantConfigFile(
|
|
2475
|
+
const context = resolveConfigContext(configFilePath);
|
|
2476
|
+
if (context.selection === "none") throw new NoTenantSelectedError(context.registered);
|
|
2477
|
+
if (context.selection === "explicit") return fromFile(context.path);
|
|
2478
|
+
return readTenantConfigFile(context.path, context.tenant.tsgId);
|
|
2529
2479
|
}
|
|
2530
2480
|
async function loadConfig(cliOverrides = {}, configFilePath) {
|
|
2531
2481
|
const fileConfig = await readConfigSource(configFilePath);
|
|
2532
|
-
const
|
|
2533
|
-
const merged = {
|
|
2534
|
-
...stripUndefined(fileConfig),
|
|
2535
|
-
...stripUndefined(envConfig),
|
|
2536
|
-
...stripUndefined(cliOverrides)
|
|
2537
|
-
};
|
|
2482
|
+
const merged = { ...stripUndefined(fileConfig), ...stripUndefined(cliOverrides) };
|
|
2538
2483
|
const config = ConfigSchema.parse(merged);
|
|
2539
|
-
return {
|
|
2540
|
-
...config,
|
|
2541
|
-
dataDir: expandHome(config.dataDir)
|
|
2542
|
-
};
|
|
2484
|
+
return { ...config, dataDir: expandHome(config.dataDir) };
|
|
2543
2485
|
}
|
|
2544
2486
|
async function inspectConfig(configFilePath) {
|
|
2545
2487
|
const fileConfig = stripUndefined(await readConfigSource(configFilePath));
|
|
2546
|
-
const
|
|
2547
|
-
const merged = { ...fileConfig, ...envConfig };
|
|
2548
|
-
const config = ConfigSchema.parse(merged);
|
|
2488
|
+
const config = ConfigSchema.parse(fileConfig);
|
|
2549
2489
|
const entries = {};
|
|
2550
2490
|
for (const key of Object.keys(ConfigSchema.shape)) {
|
|
2551
|
-
|
|
2552
|
-
entries[key] = { value: config[key], source: source2 };
|
|
2491
|
+
entries[key] = { value: config[key], source: key in fileConfig ? "file" : "default" };
|
|
2553
2492
|
}
|
|
2554
2493
|
return entries;
|
|
2555
2494
|
}
|
|
@@ -4192,9 +4131,8 @@ export {
|
|
|
4192
4131
|
readBackupFile,
|
|
4193
4132
|
readBackupDir,
|
|
4194
4133
|
ConfigSchema,
|
|
4134
|
+
RETIRED_CONFIG_KEYS,
|
|
4195
4135
|
tenantStorePath,
|
|
4196
|
-
expandConfigPath,
|
|
4197
|
-
defaultTenantConfigPath,
|
|
4198
4136
|
readTenantStore,
|
|
4199
4137
|
readTenantConfig,
|
|
4200
4138
|
readTenantConfigFile,
|
|
@@ -4202,7 +4140,7 @@ export {
|
|
|
4202
4140
|
createTenant,
|
|
4203
4141
|
switchTenant,
|
|
4204
4142
|
deleteTenant,
|
|
4205
|
-
|
|
4143
|
+
resolveConfigContext,
|
|
4206
4144
|
loadConfig,
|
|
4207
4145
|
inspectConfig,
|
|
4208
4146
|
validateName,
|