nsauditor-ai 0.1.91 β†’ 0.1.92

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
@@ -17,8 +17,9 @@ NSAuditor AI is the open-source core of a privacy-first security intelligence pl
17
17
 
18
18
  ## What's New
19
19
 
20
- **Latest: CE 0.1.91 + Enterprise 0.16.0** (May 2026)
20
+ **Latest: CE 0.1.92 + Enterprise 0.16.1** (May 2026)
21
21
 
22
+ - πŸ”Œ **MCP `NSA_ENV_FILE`: per-environment `.env` for the MCP server** (CE 0.1.92 β€” paired EE 0.16.1) β€” the MCP analog of 0.16.0's CLI `--env`. Point the MCP server at a specific account/cloud by setting **`NSA_ENV_FILE`** to a dotenv path in your Claude Desktop / Claude Code config instead of inlining every credential; the server loads that environment's cloud credentials + `CLOUD_PROVIDER` at startup. Loaded **after** the MCP auth gate + license (scan-target vars only; the auth key + license are ignored if present in the file), **fail-fast** on a missing / INI / set-but-empty path, and treated as the **authoritative scan target** β€” leftover ambient provider credentials the file doesn't set are cleared, closing a false-clean (a partial file could otherwise silently scan a leftover account) caught + folded by the `audit-cloud-plugin-false-negatives` review. Plugin count UNCHANGED at 28; all six matrices UNCHANGED.
22
23
  - 🎯 **Per-account scanning: `--env` / `--aws-profile` + sentinel-host plugin scoping** (CE 0.1.91 β€” paired EE 0.16.0) β€” audit many cloud accounts one at a time without shell-export juggling. **`--env <path>`** loads a per-scan dotenv credentials file (override-on; fail-fast on a missing file; an INI/`~/.aws/credentials` file is detected and redirected to `--aws-profile`). **`--aws-profile <name>`** uses a named profile from the OS-default `~/.aws/credentials` (clears stale explicit keys, sets `AWS_SDK_LOAD_CONFIG=1`, implies `CLOUD_PROVIDER=aws`). On a cloud-sentinel host, **`--host aws|gcp|azure` + `--plugins all` auto-scopes** to only that cloud's plugins (other clouds + non-cloud plugins are skipped and logged); explicit `--plugins` lists are unaffected. A host↔`CLOUD_PROVIDER` conflict fails fast (no silent empty "clean" report), and a zero-match sentinel scope warns loudly. EE 0.16.0 adds a declarative `cloudProvider` field to all 27 cloud plugins. Plugin count UNCHANGED at 28; all six matrices UNCHANGED.
23
24
  - πŸ›‘οΈ **Cross-cloud scope hotfix** (CE 0.1.90 β€” paired EE 0.15.9) β€” EE 0.15.9 moves the AWS-plugin `CLOUD_PROVIDER` gate from `preflight()` to `run()`: the 0.15.8 gate was placed where the scan orchestrator never calls it, so the cross-cloud bleed persisted; it's now on the load-bearing `run()` path (a `CLOUD_PROVIDER=gcp|azure` scan with AWS creds present no longer bleeds AWS resources into the GCP/Azure packs), validated via a run()-path test + a real-creds local proof. CE is a paired no-op bump. Plugin count UNCHANGED at 28; all six matrices UNCHANGED.
24
25
  - πŸ›‘οΈ **Cloud-plugin scoping fixes** (CE 0.1.89 β€” paired EE 0.15.8) β€” EE 0.15.8 closes two issues surfaced by the 0.15.7 full multi-cloud smoke: the AWS plugins now gate on `CLOUD_PROVIDER` (a `CLOUD_PROVIDER=gcp|azure` scan with AWS creds present no longer bleeds AWS resources into the GCP/Azure attestation packs), and the GCP IAM/storage auditor now surfaces an evidence-gap instead of a false-clean PASS when a policy can't be read. CE is a paired no-op bump (no CE code change). Plugin count UNCHANGED at 28; all six coverage matrices UNCHANGED.
@@ -486,10 +487,9 @@ Then add this to your `claude_desktop_config.json` (Settings β†’ Developer β†’ E
486
487
  "command": "nsauditor-ai-mcp",
487
488
  "env": {
488
489
  "NSA_MCP_AUTH_KEY": "keychain:NSA_MCP_AUTH_KEY",
490
+ "NSA_ENV_FILE": "~/envs/prod-aws.env",
489
491
  "AI_PROVIDER": "claude",
490
- "ANTHROPIC_API_KEY": "keychain:ANTHROPIC_API_KEY",
491
- "NSA_ALLOW_ALL_HOSTS": "1",
492
- "PLUGIN_TIMEOUT_MS": "5000"
492
+ "ANTHROPIC_API_KEY": "keychain:ANTHROPIC_API_KEY"
493
493
  }
494
494
  }
495
495
  }
@@ -503,6 +503,32 @@ The exact `NSA_MCP_AUTH_KEY` value to paste is printed by `nsauditor-ai mcp inst
503
503
  - `PLUGIN_TIMEOUT_MS=5000` β€” reduces per-plugin timeout to 5s so the full scan completes within Claude Desktop's 60s MCP limit
504
504
  - `AI_PROVIDER` and API key β€” optional, enables AI-powered analysis of scan results
505
505
 
506
+ #### `NSA_ENV_FILE` β€” point the MCP server at an environment file
507
+
508
+ Instead of inlining every scan variable in the config above, set **`NSA_ENV_FILE`** to a
509
+ dotenv file and keep the cloud credentials, `CLOUD_PROVIDER`, and scan tuning there. To scan
510
+ a different account or cloud, change the one path (or swap the file) and restart Claude Desktop β€”
511
+ no JSON editing.
512
+
513
+ ```bash
514
+ # ~/envs/prod-aws.env (chmod 600 β€” this holds credentials)
515
+ CLOUD_PROVIDER=aws
516
+ AWS_ACCESS_KEY_ID=AKIA...
517
+ AWS_SECRET_ACCESS_KEY=...
518
+ NSA_ALLOW_ALL_HOSTS=1
519
+ PLUGIN_TIMEOUT_MS=5000
520
+ ```
521
+
522
+ - The file is loaded at server startup; values in it **override** the same keys in the config `env` block.
523
+ - **Fail-fast:** if the path is missing or points at an AWS credentials/INI file, the server refuses
524
+ to start (it will not silently fall back to ambient credentials and scan the wrong account). The
525
+ error is written to the MCP server's stderr log.
526
+ - **The file is the authoritative scan target:** ambient provider credentials (e.g. an old account's
527
+ `AWS_*` keys still in the config `env` block) that the file does **not** set are cleared, so a partial
528
+ file can't silently scan a leftover account. Instance-role / ADC identity is untouched.
529
+ - `NSA_MCP_AUTH_KEY` and `NSAUDITOR_LICENSE_KEY` are resolved **before** the file and must stay
530
+ inline (or in `~/.nsauditor/.env`); if present in `NSA_ENV_FILE` they are ignored.
531
+
506
532
  ### Claude Code Setup
507
533
 
508
534
  ```bash
@@ -512,6 +538,15 @@ claude mcp add nsauditor-ai \
512
538
  -- npx nsauditor-ai-mcp
513
539
  ```
514
540
 
541
+ To target an environment via the file, add it as an env value:
542
+
543
+ ```bash
544
+ claude mcp add nsauditor-ai \
545
+ --env NSA_MCP_AUTH_KEY=keychain:NSA_MCP_AUTH_KEY \
546
+ --env NSA_ENV_FILE=~/envs/prod-aws.env \
547
+ -- npx nsauditor-ai-mcp
548
+ ```
549
+
515
550
  (On Linux/Windows, replace the `keychain:NSA_MCP_AUTH_KEY` placeholder with the literal key printed by `install-key`.)
516
551
 
517
552
  ### Troubleshooting MCP authentication
package/mcp_server.mjs CHANGED
@@ -592,6 +592,27 @@ export async function startStdioServer() {
592
592
  _tier = getTierFromEnv();
593
593
  _capabilities = resolveCapabilities(_tier);
594
594
 
595
+ // NSA_ENV_FILE: load the per-environment dotenv file AFTER auth + license
596
+ // (so it carries scan-target vars only and can alter neither) and BEFORE
597
+ // createServer()/connect. plugin_manager.mjs captures PLUGIN_TIMEOUT_MS in a
598
+ // module-level const at import (first tool call), so the file MUST be applied
599
+ // before any tool call can import it. Fail-fast: a missing / INI file refuses
600
+ // startup rather than silently scanning ambient credentials (wrong-account β†’
601
+ // a false "clean"). stdout is JSON-RPC, so diagnostics go to stderr.
602
+ try {
603
+ const fsm = await import('node:fs');
604
+ const { applyScanEnvFile } = await import('./utils/mcp_env_file.mjs');
605
+ applyScanEnvFile({
606
+ env: process.env,
607
+ fileExists: (p) => fsm.existsSync(p),
608
+ readFile: (p) => fsm.readFileSync(p, 'utf8'),
609
+ log: (m) => process.stderr.write(`[nsauditor-mcp] ${m}\n`),
610
+ });
611
+ } catch (err) {
612
+ process.stderr.write(`βœ— NSA_ENV_FILE: ${err.message}\n`);
613
+ process.exit(1);
614
+ }
615
+
595
616
  const server = createServer();
596
617
  const transport = new StdioServerTransport();
597
618
  await server.connect(transport);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nsauditor-ai",
3
- "version": "0.1.91",
3
+ "version": "0.1.92",
4
4
  "description": "Modular AI-assisted network security audit platform β€” Community Edition",
5
5
  "type": "module",
6
6
  "private": false,
@@ -0,0 +1,89 @@
1
+ // utils/mcp_env_file.mjs
2
+ //
3
+ // Loads the per-environment dotenv file named by NSA_ENV_FILE into env for the
4
+ // MCP server, reusing the CLI's resolveScanEnv() (same dotenv parse, tilde-
5
+ // expansion, INI-detection, fail-fast). Called from startStdioServer() AFTER
6
+ // auth + license are resolved, so the file carries SCAN-TARGET vars only (cloud
7
+ // creds / CLOUD_PROVIDER / scan tuning) and can neither bypass the auth gate nor
8
+ // escalate the license tier. NSA_MCP_AUTH_KEY / NSAUDITOR_LICENSE_KEY in the file
9
+ // are intentionally ignored (they resolve before the file is read).
10
+ //
11
+ // The file is the AUTHORITATIVE scan-target selector: any explicit provider
12
+ // credential present in the ambient env but NOT set by the file is "leftover"
13
+ // (e.g. a previous account's keys still in the Claude Desktop env block) and is
14
+ // CLEARED β€” otherwise a partial file would let the scan silently run against the
15
+ // wrong account and report "clean" (the worst outcome an audit tool can produce).
16
+ // Instance-role / ADC identity (which has no env var) is untouched. This mirrors
17
+ // how the CLI --aws-profile path already clears the explicit AWS keys.
18
+
19
+ import { resolveScanEnv } from './env_loader.mjs';
20
+
21
+ // Resolved before NSA_ENV_FILE loads β†’ stripped if present in the file.
22
+ const RESERVED_KEYS = new Set(['NSA_MCP_AUTH_KEY', 'NSAUDITOR_LICENSE_KEY']);
23
+
24
+ // Explicit per-provider credential env vars. Cleared if the file does not set them.
25
+ const PROVIDER_CRED_KEYS = [
26
+ 'AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'AWS_SESSION_TOKEN', 'AWS_PROFILE',
27
+ 'GOOGLE_APPLICATION_CREDENTIALS', 'GOOGLE_CLOUD_PROJECT', 'GOOGLE_CLOUD_PROJECT_ID', 'GCLOUD_PROJECT',
28
+ 'AZURE_CLIENT_ID', 'AZURE_TENANT_ID', 'AZURE_CLIENT_SECRET', 'AZURE_SUBSCRIPTION_ID',
29
+ ];
30
+
31
+ /**
32
+ * @param {object} a
33
+ * @param {Record<string,string>} a.env process.env (mutated in place; file wins)
34
+ * @param {(p:string)=>boolean} a.fileExists
35
+ * @param {(p:string)=>string} a.readFile
36
+ * @param {(m:string)=>void} [a.log] stderr sink β€” key NAMES only, never values
37
+ * @returns {{applied: string[], ignored: string[], cleared: string[]}}
38
+ * @throws if NSA_ENV_FILE is set-but-empty, or points at a missing/INI file
39
+ */
40
+ export function applyScanEnvFile({ env, fileExists, readFile, log = () => {} }) {
41
+ const envPath = env.NSA_ENV_FILE;
42
+ if (envPath == null) return { applied: [], ignored: [], cleared: [] }; // truly unset β†’ no-op
43
+ if (typeof envPath !== 'string' || envPath.trim() === '') {
44
+ // set-but-empty (e.g. an unresolved config-template var): the operator
45
+ // clearly intended a file. Fail-fast rather than silently scan ambient creds.
46
+ throw new Error(
47
+ 'NSA_ENV_FILE is set but empty β€” refusing to start; unset it or give a real path ' +
48
+ '(an empty value would silently scan ambient credentials)',
49
+ );
50
+ }
51
+
52
+ // Only envPath is set β†’ resolveScanEnv reduces to: resolve + tilde-expand β†’
53
+ // fail-fast if missing β†’ reject INI β†’ dotenv.parse β†’ { set, unset: [] }.
54
+ const { set } = resolveScanEnv({ envPath, env, fileExists, readFile });
55
+
56
+ const ignored = Object.keys(set).filter((k) => RESERVED_KEYS.has(k));
57
+ for (const k of ignored) delete set[k];
58
+
59
+ const applied = Object.keys(set);
60
+ Object.assign(env, set); // override-on β€” the file is the environment selector
61
+
62
+ // Anti-false-clean: clear leftover ambient provider creds the file did NOT set.
63
+ const cleared = [];
64
+ for (const k of PROVIDER_CRED_KEYS) {
65
+ if (!(k in set) && env[k] != null && env[k] !== '') {
66
+ delete env[k];
67
+ cleared.push(k);
68
+ }
69
+ }
70
+
71
+ if (ignored.length) {
72
+ log(
73
+ `ignored ${ignored.join(', ')} from NSA_ENV_FILE β€” auth/license resolve ` +
74
+ `before the file; set them inline or in ~/.nsauditor/.env`,
75
+ );
76
+ }
77
+ if (cleared.length) {
78
+ log(
79
+ `cleared ambient ${cleared.join(', ')} not set by NSA_ENV_FILE ` +
80
+ `(the file is the authoritative scan target β€” refusing to use leftover credentials)`,
81
+ );
82
+ }
83
+ log(
84
+ `Loaded scan environment from ${envPath} ` +
85
+ `(${applied.length} keys: ${applied.join(', ') || 'none'})`,
86
+ );
87
+
88
+ return { applied, ignored, cleared };
89
+ }