nsauditor-ai 0.2.55 → 0.2.57

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 (42) hide show
  1. package/README.md +76 -62
  2. package/cli.mjs +210 -179
  3. package/mcp_server.mjs +71 -31
  4. package/package.json +1 -1
  5. package/plugin_manager.mjs +49 -8
  6. package/plugins/040_tls_cert_auditor.mjs +176 -56
  7. package/plugins/050_tribe_health.mjs +13 -34
  8. package/plugins/060_dns_sec_auditor.mjs +24 -30
  9. package/plugins/dns_scanner.mjs +16 -8
  10. package/plugins/ftp_banner_check.mjs +21 -13
  11. package/plugins/host_up_check.mjs +31 -72
  12. package/plugins/http_probe.mjs +93 -25
  13. package/plugins/mcp_scanner.mjs +15 -1
  14. package/plugins/netbios_scanner.mjs +32 -13
  15. package/plugins/opensearch_scanner.mjs +4 -0
  16. package/plugins/port_scanner.mjs +29 -49
  17. package/plugins/result_concluder.mjs +122 -30
  18. package/plugins/snmp_scanner.mjs +20 -8
  19. package/plugins/syn_scanner.mjs +2 -1
  20. package/plugins/upnp_scanner.mjs +82 -13
  21. package/plugins/webapp_detector.mjs +82 -15
  22. package/utils/conclusion_utils.mjs +13 -1
  23. package/utils/cookie_redaction.mjs +50 -0
  24. package/utils/delta_reporter.mjs +70 -25
  25. package/utils/ee_load.mjs +45 -0
  26. package/utils/executive_report.mjs +48 -8
  27. package/utils/export_csv.mjs +14 -22
  28. package/utils/mcp_call_digest.mjs +31 -0
  29. package/utils/net_validation.mjs +107 -37
  30. package/utils/output_dir.mjs +70 -0
  31. package/utils/plugin_discovery.mjs +4 -2
  32. package/utils/ports_spec.mjs +46 -0
  33. package/utils/report_inputs.mjs +64 -1
  34. package/utils/report_md.mjs +51 -112
  35. package/utils/sarif.mjs +33 -93
  36. package/utils/scan_delta.mjs +514 -38
  37. package/utils/scan_delta_view.mjs +5 -3
  38. package/utils/scan_history.mjs +149 -0
  39. package/utils/scheduler.mjs +17 -5
  40. package/utils/service_flags.mjs +363 -0
  41. package/utils/watch_cycle.mjs +37 -0
  42. package/utils/webhook.mjs +2 -1
package/README.md CHANGED
@@ -7,31 +7,32 @@ A modular, AI-assisted network security audit platform that scans, understands,
7
7
  [![npm](https://img.shields.io/npm/v/nsauditor-ai.svg)](https://www.npmjs.com/package/nsauditor-ai)
8
8
  [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
9
9
  [![Node.js 20+](https://img.shields.io/badge/node-20%2B-green.svg)](https://nodejs.org)
10
- [![Tests](https://img.shields.io/badge/tests-1582%20passing-brightgreen.svg)](#tests)
10
+ [![Tests](https://img.shields.io/badge/tests-passing-brightgreen.svg)](#tests)
11
11
 
12
12
  ---
13
13
 
14
14
  NSAuditor AI is the open-source core of a privacy-first security intelligence platform built by [Nsasoft US LLC](https://www.nsauditor.com/ai/). It orchestrates 27 specialized scanning plugins against target hosts, fuses their results through an intelligent concluder, and optionally produces AI-powered vulnerability reports — all running entirely on your machine.
15
15
 
16
- **Zero Data Exfiltration by design — and stated precisely.** We never see your scan data: no customer data is collected, transmitted, or stored by Nsasoft US LLC, and the product has no telemetry or phone-home endpoint. Scanning, analysis, license verification and report generation all run on your machine. Two clarifications that matter operationally: AI enrichment is **opt-in** and uses your own API keys (point it at a local Ollama and nothing leaves the host), while **CVE correlation queries NIST's public NVD API by default** — set `NSAUDITOR_OFFLINE_ONLY=1` with a local NVD store to make it fully local, which reports an explicit coverage gap instead of a silent clean.
16
+ **Zero Data Exfiltration by design — and stated precisely.** We never see your scan data: no customer data is collected, transmitted, or stored by Nsasoft US LLC, and the product has no telemetry or phone-home endpoint. Scanning, analysis, license verification and report generation all run on your machine. Two clarifications that matter operationally: AI enrichment is **opt-in** and uses your own API keys (point it at a local Ollama and nothing leaves the host), while **CVE correlation queries NIST's public NVD API by default** — set `NSAUDITOR_OFFLINE_ONLY=1` with a local NVD store to make it fully local, which reports an explicit coverage gap in the scan instead of a silent clean (from Enterprise 1.3.0 that gap fails, as an evidence gap, the compliance controls the CVE rows map to).
17
17
 
18
18
  ## What's New
19
19
 
20
- **Latest: CE 0.2.55 + Enterprise 1.1.0 — *what changed since the last scan*.**
21
- `nsauditor-ai report --from <dir> --since <runId|prior>` (Pro/Enterprise) compares two scan runs:
22
- what is new, what is resolved, what changed severity — and, above all, **what could NOT be compared
23
- and why**. "Resolved" is the dangerous verdict: a finding that disappeared because the host was not
24
- scanned, the plugin did not run, the scanner lost permission, or the framework enumeration moved is
25
- reported as NOT COMPARABLE **with its reason**, never as remediation. Run records are sealed with a
26
- SHA-256 chain, so an altered baseline REFUSES the comparison instead of producing verdicts from it —
27
- tamper-EVIDENT against corruption, partial restore and unsophisticated edits, **not tamper-proof
28
- against host-level access and not non-repudiation**, and the report says so in the body. The delta
29
- renders into the client-facing HTML report, not only to stdout. Plugin counts UNCHANGED at 27
30
- Community + 29 Enterprise; every coverage matrix UNCHANGED; **the Enterprise peer floor MOVES to
31
- `>= 0.2.55`, because Enterprise now CALLS this release's code** — the per-plugin run statuses this
32
- version publishes on the scan context, which EE plugin 1023 requires before it will assert a Zero
33
- Trust posture and the CPE mapper uses to tell an empty service set from one whose discovery never
34
- ran. The free last-vs-current webhook alerting delta is untouched and stays free.
20
+ **Latest: CE 0.2.57 + Enterprise 1.3.0 — *a finding on a port, region or producer a scan did not measure is not counted as fixed — two measured limits stated*.**
21
+ The Pro/Enterprise `report --since` delta refuses two more ways a row could read as fixed without being fixed. A CVE
22
+ or end-of-life row on a TCP port whose service the other scan reached but could not identify (a program and a version)
23
+ is `port-not-measured`, never resolved or new. And from Enterprise 1.3.0 a scan that left out a plugin an analysis agent
24
+ reads records it, so that agent's rows there are refused as `evidence-gap`. Each new, resolved and changed row now
25
+ carries a **Basis** cell saying what the delta checked for that row. Two Enterprise agents changed identity at 1.3.0 —
26
+ the exposure agent's titles no longer name the service, the service agent's no longer name the version — and both are
27
+ declared in this release, so a row compared across the upgrade reads `identity-basis-changed`, never resolved or new.
28
+ **Two measured limits:** a scan made before EE 1.3.0 could not record a plugin left out of it, so in a comparison with
29
+ one, an agent's row that scan lacks is not refused — its Basis cell says so; and a scan that discovered ports with the
30
+ Nmap plugin (024) alone records no port oracle, so an analysis agent's or the CVE mapper's row on a port it did not
31
+ measure can read RESOLVED and count as closed in MTTR, and the control it failed can read PASS — include the port
32
+ scanner (003). The release also carries this cycle's scanner, report and security fixes; the
33
+ [CHANGELOG](./CHANGELOG.md) lists them. Plugin counts UNCHANGED at 27 Community + 29 Enterprise; every coverage matrix
34
+ UNCHANGED; **Enterprise 1.3.0 requires this release** (`nsauditor-ai >= 0.2.57`). The free last-vs-current webhook
35
+ alerting delta is untouched and stays free (see Continuous Monitoring below).
35
36
 
36
37
  For the full per-release history — every prior cycle, in detail — see [CHANGELOG.md](./CHANGELOG.md). This README keeps only the current release headline.
37
38
 
@@ -45,8 +46,8 @@ Scan → Analyze → Prioritize → Track → Act
45
46
  - **Smart result fusion** — the Result Concluder merges all plugin outputs into a normalized view with OS detection, service fingerprinting, and evidence linking
46
47
  - **Structured finding format** — all findings use a common schema with category, severity, evidence, and remediation — enabling consistent SARIF export and MCP integration
47
48
  - **AI-powered analysis** — send redacted scan results to OpenAI or Claude (your keys, your choice) for vulnerability assessments and remediation guidance
48
- - **Risk-scored prioritization (Pro/Enterprise)** — findings carry a composite risk score (CVSS weighted by verification status, with an uplift for initial-access techniques) and a status field, and an operator suppression workflow (accepted-risk / false-positive with expiry) keeps triaged findings out of the report until they expire
49
- - **Continuous monitoring (CTEM)** — watch mode rescans on a schedule, diffs against previous results, and fires webhook alerts on changes
49
+ - **Risk-scored prioritization (Pro/Enterprise)** — findings carry a composite risk score (CVSS weighted by verification status, with an uplift for initial-access techniques) and a status field. Enterprise adds an operator suppression workflow for compliance findings (accepted-risk / false-positive with expiry): the compliance report shows a suppressed finding as suppressed, naming its approver and rationale, and an expired suppression no longer applies
50
+ - **Continuous monitoring (CTEM)** — watch mode rescans on a schedule, compares each host with the previous cycle and alerts a host whose scan changed and that carries a finding at or above `--alert-severity`
50
51
  - **MCP integration** — expose scanning tools to AI assistants like Claude Code via Model Context Protocol
51
52
  - **CI/CD ready** — SARIF output with `--fail-on` severity gating for pipeline integration
52
53
 
@@ -158,7 +159,7 @@ npm install
158
159
  node --env-file=.env cli.mjs scan --host 192.168.1.1 --plugins all
159
160
  ```
160
161
 
161
- Results land in `./out/<host>_<timestamp>/`:
162
+ Results land in `./out/<host>_<timestamp>/` (two scans of one host whose plugin runs finished in the same second get distinct directories):
162
163
 
163
164
  | File | Contents |
164
165
  |---|---|
@@ -178,16 +179,17 @@ Results land in `./out/<host>_<timestamp>/`:
178
179
 
179
180
  ## MCP Server
180
181
 
181
- > **Heads-up on AI-client fabrication.** Some MCP clients (notably Claude Desktop) can silently substitute AI-generated responses if a `tools/call` times out, instead of surfacing the failure. Every response from this server now ends with a `── Verified MCP call ──` footer and a UUID. Run `nsauditor-ai mcp verify-call <id>` to confirm a response is genuine before acting on it. Full background and workflow: [docs/mcp-verification.md](./docs/mcp-verification.md). When in doubt, generate compliance evidence via the CLI (`nsauditor-ai scan ...`), which has no MCP client in the path.
182
+ > **Heads-up on AI-client fabrication.** Some MCP clients (notably Claude Desktop) can silently substitute AI-generated responses if a `tools/call` times out, instead of surfacing the failure. Every response from this server ends with an `── MCP call receipt ──` footer and a UUID. To check that a response is the text the server actually produced, save it (footer included) and run `nsauditor-ai mcp verify-call <id> --response <file>`: exit 0 means this exact text came from that call (the line also says how long ago it was issued), 1 means it did not (or the id was never issued), and 3 means the id was issued but nothing bound the text to it — the id alone never verifies a response, because a real id can be pasted under a fabricated one. Full background and workflow: [docs/mcp-verification.md](./docs/mcp-verification.md). When in doubt, generate compliance evidence via the CLI (`nsauditor-ai scan ...`), which has no MCP client in the path.
182
183
 
183
184
  Expose scanning capabilities to AI assistants via [Model Context Protocol](https://modelcontextprotocol.io):
184
185
 
185
186
  ```bash
186
187
  nsauditor-ai-mcp
187
- # or
188
- npx nsauditor-ai-mcp
189
188
  ```
190
189
 
190
+ Run the installed bin, never `npx nsauditor-ai-mcp`: `nsauditor-ai-mcp` is a bin inside the `nsauditor-ai` package, and when npx does not find that bin it looks the name up on the npm registry instead — which never starts this server. For Claude Desktop, paste the block
191
+ `nsauditor-ai mcp install-key` prints (Claude Desktop Setup, below).
192
+
191
193
  The server registers **seven** tools and lists all seven to every client. The licence is
192
194
  checked when a tool is CALLED, not when the list is served — so an unlicensed call returns
193
195
  an explicit 🔒 refusal naming the tier it needs, never an empty result that reads as "nothing
@@ -197,7 +199,7 @@ found". A higher tier does not add tools; it unlocks the ones already in the lis
197
199
 
198
200
  | Tool | Purpose |
199
201
  |---|---|
200
- | `scan_host` | Run a full plugin scan against a host — service detection, OS fingerprint, structured findings |
202
+ | `scan_host` | Run a full plugin scan against a host — service detection, OS fingerprint and the flags the service checks leave on each service record (weak SSH algorithms, SNMP community, weak TLS protocols / ciphers and a self-signed certificate, the MCP server checks; anonymous FTP login and zone transfer only when `FTP_CHECK_ANON` / `DNS_CHECK_AXFR` are set — both off by default). Its records also carry the HTTP probe's dangerous methods (006; `methodsTested` false means no Allow header was read — not tested, never "none"), SMB null sessions (014; only with `SMB_NULL_SESSION` set) and the TLS-certificate / debug-endpoint / DNS-security auditors' findings (040 / 050 / 060 — for a domain-name target the DNS-security audit lands on the 53/udp record when the scan found a 53/udp service, otherwise in the conclusion's evidence; for an IP-address target it declines, with its reason on the manifest and a NOT TESTED line in the markdown). Each opt-in check records whether it ran beside its result — `axfrTested` / `anonymousLoginTested` / `nullSessionTested` is `true` when measured, else the reason (`opt-in-off`, `no-domain`, `no-answer`) — and a null result is not tested, never "refused". It does NOT look up CVEs or run the analysis agents; with the Enterprise package the zero-trust assessment (1023) reaches it only as one score line in the evidence. So zero findings is not a clean verdict (use `get_vulnerabilities` per service CPE and `probe_service` for those plugins — both Pro — or the CLI scan with the Enterprise package and a Pro or Enterprise licence) |
201
203
  | `list_plugins` | List available scanner plugins with their IDs, priorities and requirements |
202
204
  | `compliance_matrix` | Return the shipped coverage matrix for a framework — Covered / Partial / Out of scope, with the per-group out-of-scope reasons. **Needs the Enterprise pack installed**: the matrices are its data, so on a Community-only install this fails closed with an install instruction rather than returning an empty matrix (an empty matrix is what gets filled in with a guess) |
203
205
 
@@ -216,23 +218,22 @@ found". A higher tier does not add tools; it unlocks the ones already in the lis
216
218
  | `get_findings` | Drill into the findings of the most recent `scan_cloud` run (per-provider session cache — not live state) |
217
219
 
218
220
  > `scan_cloud` runs the requested clouds' plugins **concurrently** (default up to 20 at once, 25s per-plugin
219
- > timeout) so a full multi-service cloud audit completes within Claude Desktop's ~60s tool-call limit. Tune with
221
+ > timeout; a plugin that runs out is listed as `timeout` in the result's `manifest`, i.e. not measured). Tune with
220
222
  > `CLOUD_SCAN_CONCURRENCY` (default 20) and `CLOUD_PLUGIN_TIMEOUT_MS` (default 25000) in the server env. The
221
223
  > network `PLUGIN_TIMEOUT_MS` governs `scan_host` and `probe_service`, and binds every plugin there — including
222
224
  > one that declares a longer budget of its own. Read the result's **`findingsSummary`**
223
225
  > (per-provider severity counts + a CRITICAL/HIGH list) for the findings; `audited:false` / `notes` / `pluginsRan:0`
224
226
  > still mean a cloud was NOT audited (never a clean pass). Pass `providers:["aws"]` to audit only the cloud named.
225
227
 
226
- > **Full all-region AWS coverage fits Desktop's limit automatically.** When you ask for "all regions" / "full
227
- > coverage", the agent scans the enabled regions in small **region-group batches** (each within the ~60s window)
228
- > rather than one long `regions:["all"]` call — so it completes without timing out, and you do **not** raise any
229
- > timeout for it. Keep `CLOUD_PLUGIN_TIMEOUT_MS` **under** Desktop's ~60s tool-call cap (default `25000`; raise to
230
- > ~`45000` only for very large accounts — a higher per-plugin cap can let one plugin run past Desktop's wall and
231
- > cause a hard timeout). For unbounded multi-region scans use the **CLI** (`nsauditor-ai scan … --aws-region all`),
232
- > which has no MCP tool-call cap — there you can raise `PLUGIN_TIMEOUT_MS` (e.g. `90000`) freely. On the CLI a
228
+ > **All-region AWS coverage.** The server does not split a `regions:["all"]` call into batches; the agent skill
229
+ > (`nsauditor-ai-agent-skill`) teaches the assistant to cover the enabled regions in batches of 3–5 region codes per
230
+ > call. `CLOUD_PLUGIN_TIMEOUT_MS` (default `25000`) bounds each plugin, not the call: raising it can give a slow
231
+ > plugin more time and lengthen the call. A call that times out in the client returned no result — never read it as
232
+ > a clean account. For unbounded multi-region scans use the **CLI** (`nsauditor-ai scan … --aws-region all`), which
233
+ > has no MCP client in the path — there you can raise `PLUGIN_TIMEOUT_MS` (e.g. `90000`) freely. On the CLI a
233
234
  > plugin that declares its own budget already gets it; `PLUGIN_TIMEOUT_CEILING_MS` caps those.
234
235
 
235
- Security: SSRF protection on all host inputs (blocks RFC 1918, loopback, fc00::/7, cloud metadata), port validation (1–65535), CPE format enforcement, dependency injection for test isolation. **Server-startup authentication is required** — see next section.
236
+ Security: SSRF protection on every host the MCP server is handed — the address it resolves to is checked (every answer), loopback, link-local and cloud-metadata addresses are always refused, and RFC 1918, CGNAT and fc00::/7 are refused unless `NSA_ALLOW_ALL_HOSTS` is set; a host the scanned target names — a redirect hop, a UPnP device's description URL — is checked the same way by the plugin that would follow it. A checked name is resolved again when it is scanned (no DNS pin). Port validation (1–65535), CPE format enforcement, dependency injection for test isolation. **Server-startup authentication is required** — see next section.
236
237
 
237
238
  ### Authentication (required)
238
239
 
@@ -292,13 +293,16 @@ npm install -g nsauditor-ai
292
293
  nsauditor-ai mcp install-key # required before MCP server will start
293
294
  ```
294
295
 
295
- Then add this to your `claude_desktop_config.json` (Settings → Developer → Edit Config):
296
+ Then paste the `mcpServers` block that `install-key` prints into your `claude_desktop_config.json` (Settings →
297
+ Developer → Edit Config). It names node and the server script by absolute path, because Claude Desktop may not see
298
+ the directory your shell finds `nsauditor-ai-mcp` in. Add any other variables to its `env` block, for example:
296
299
 
297
300
  ```json
298
301
  {
299
302
  "mcpServers": {
300
303
  "nsauditor-ai": {
301
- "command": "nsauditor-ai-mcp",
304
+ "command": "<from: nsauditor-ai mcp install-key — the absolute path to node>",
305
+ "args": ["<from: nsauditor-ai mcp install-key — the absolute path to bin/nsauditor-ai-mcp.mjs>"],
302
306
  "env": {
303
307
  "NSA_MCP_AUTH_KEY": "keychain:NSA_MCP_AUTH_KEY",
304
308
  "NSA_ENV_FILE": "~/envs/prod-aws.env",
@@ -313,11 +317,16 @@ Then add this to your `claude_desktop_config.json` (Settings → Developer → E
313
317
  The exact `NSA_MCP_AUTH_KEY` value to paste is printed by `nsauditor-ai mcp install-key` — on macOS it's the `keychain:NSA_MCP_AUTH_KEY` placeholder shown above; on Linux/Windows it's the literal key value (and you should `chmod 600` your config file).
314
318
 
315
319
  - `NSA_MCP_AUTH_KEY` — **required** (see Authentication section above)
316
- - `NSA_ALLOW_ALL_HOSTS=1` — required to scan private/RFC 1918 addresses (e.g., `192.168.x.x`)
317
- - `PLUGIN_TIMEOUT_MS=5000` — the per-plugin budget for `scan_host` and `probe_service` (default 30000), and it binds every plugin there, including one that declares a longer budget of its own. It bounds each PLUGIN, not the call: `scan_host` runs a host's plugins one after another, so the call takes roughly the sum of their times, and fits inside Claude Desktop's ~60 s tool-call limit only when most of them finish quickly. On a real home gateway the plugins of one full scan took ~113 s between them at the default budget.
320
+ - `NSA_ALLOW_ALL_HOSTS=1` — required to scan private/RFC 1918 addresses (e.g., `192.168.x.x`); only `1`, `true`, `yes`
321
+ or `on` turn it on. It admits private ranges only: loopback, link-local and cloud-metadata addresses stay refused over
322
+ MCP whether they are written as an address in any spelling or reached through what a host name resolves to — every
323
+ answer is checked. The check does not pin the answer, so a name whose answer changes between the check and the scan
324
+ (DNS rebinding) is not caught
325
+ - `FTP_CHECK_ANON=true` — lets the FTP check try an anonymous login (off by default; without it `scan_host` never reports anonymous FTP). `DNS_CHECK_AXFR=true` with `DNS_AXFR_DOMAIN=<zone>` — lets the DNS check try a zone transfer of that zone (off by default). `SMB_NULL_SESSION=true` — lets the NetBIOS check try an SMB null session (off by default). A check that is off, or that ran and could not complete (a timeout, a dropped connection), is recorded and reported as not tested with its reason — never as refused. The same variables govern the CLI scan.
326
+ - `PLUGIN_TIMEOUT_MS=5000` — the per-plugin budget for `scan_host` and `probe_service` (default 30000), and it binds every plugin there, including one that declares a longer budget of its own. It bounds each PLUGIN, not the call: `scan_host` runs a host's plugins one after another, so the call takes roughly the sum of their times. On one router a `scan_host` call timed out in Claude Desktop on 2026-08-10 and one returned within 138 s on 2026-09-30, so neither outcome is promised: a call that times out returned no result — never read it as a clean host. On a real home gateway the plugins of one full scan took ~113 s between them at the default budget.
318
327
  - `PLUGIN_TIMEOUT_CEILING_MS` — the upper bound on any plugin's DECLARED budget (default 120000). A few plugins declare their own budget because their cost grows with what they scan (the UPnP and MCP scanners here; several cloud auditors in Enterprise). On the CLI a declaration outranks `PLUGIN_TIMEOUT_MS`, so this is the setting that caps every plugin there. The MCP tools already bind declarations with their own limits.
319
328
  - `CLOUD_SCAN_CONCURRENCY` — max cloud plugins run at once by `scan_cloud` (default 20).
320
- - `CLOUD_PLUGIN_TIMEOUT_MS` — per-plugin timeout for `scan_cloud` (default 25000; independent of the network `PLUGIN_TIMEOUT_MS`). Keep it **under** Desktop's ~60s tool-call cap (raise to ~`45000` only for very large accounts); full all-region coverage is delivered by automatic region-batching, so it needs **no** timeout increase.
329
+ - `CLOUD_PLUGIN_TIMEOUT_MS` — per-plugin timeout for `scan_cloud` (default 25000; independent of the network `PLUGIN_TIMEOUT_MS`). It bounds each plugin, not the call — see the all-region note above before raising it.
321
330
  - `AI_PROVIDER` and API key — optional, enables AI-powered analysis of scan results
322
331
 
323
332
  #### `NSA_ENV_FILE` — point the MCP server at an environment file
@@ -352,7 +361,7 @@ PLUGIN_TIMEOUT_MS=5000
352
361
  nsauditor-ai mcp install-key # required before MCP server will start
353
362
  claude mcp add nsauditor-ai \
354
363
  --env NSA_MCP_AUTH_KEY=keychain:NSA_MCP_AUTH_KEY \
355
- -- npx nsauditor-ai-mcp
364
+ -- nsauditor-ai-mcp
356
365
  ```
357
366
 
358
367
  To target an environment via the file, add it as an env value:
@@ -361,7 +370,7 @@ To target an environment via the file, add it as an env value:
361
370
  claude mcp add nsauditor-ai \
362
371
  --env NSA_MCP_AUTH_KEY=keychain:NSA_MCP_AUTH_KEY \
363
372
  --env NSA_ENV_FILE=~/envs/prod-aws.env \
364
- -- npx nsauditor-ai-mcp
373
+ -- nsauditor-ai-mcp
365
374
  ```
366
375
 
367
376
  (On Linux/Windows, replace the `keychain:NSA_MCP_AUTH_KEY` placeholder with the literal key printed by `install-key`.)
@@ -380,7 +389,7 @@ claude mcp add nsauditor-ai \
380
389
 
381
390
  **`mcp status` shows `⚠ Created: ... — > 90d threshold`** → key is older than the 90-day rotation cadence. Run `nsauditor-ai mcp rotate-key --confirm` and update Claude Desktop config with the new key. Server emits the same warning to stderr at every startup.
382
391
 
383
- **Claude Desktop reports "Current tier: CE" despite `nsauditor-ai license --status` showing Enterprise** → first run `nsauditor-ai mcp tier` to get the ground-truth tier the MCP server actually resolves at startup. If `mcp tier` reports `enterprise` but Claude Desktop's `list_plugins` says CE, the AI client is synthesizing the response without actually calling the tool — see [docs/mcp-verification.md](./docs/mcp-verification.md) and verify any suspicious response with `nsauditor-ai mcp verify-call <id>`.
392
+ **Claude Desktop reports "Current tier: CE" despite `nsauditor-ai license --status` showing Enterprise** → first run `nsauditor-ai mcp tier` to get the ground-truth tier the MCP server actually resolves at startup. If `mcp tier` reports `enterprise` but Claude Desktop's `list_plugins` says CE, the AI client is synthesizing the response without actually calling the tool — see [docs/mcp-verification.md](./docs/mcp-verification.md) and verify any suspicious response with `nsauditor-ai mcp verify-call <id> --response <file>`.
384
393
 
385
394
  If `mcp tier` itself reports CE → genuine resolution failure. Inspect the license storage:
386
395
 
@@ -436,7 +445,7 @@ nsauditor-ai validate
436
445
  nsauditor-ai feed bundle --from <dir-of-NVD-feeds> --out <bundle.json.gz> [--kev <f>] [--epss <f>]
437
446
  nsauditor-ai feed import --file <feed-or-bundle> [--cache-dir <d>] [--extras-dir <d>] [--append] # the feeds you downloaded
438
447
  nsauditor-ai compliance <attest|suppress|review|renew|keygen|sign-pack|verify-pack> (Enterprise)
439
- nsauditor-ai report --from <dir> --format executive|jira [--run <id>] [--brand <brand.json>] [--out <path>] [--allow-partial] (Pro/Enterprise)
448
+ nsauditor-ai report --from <dir> --format executive|jira [--run <id>] [--since <runId|prior>] [--brand <brand.json>] [--out <path>] [--allow-partial] (Pro/Enterprise)
440
449
  nsauditor-ai mcp
441
450
  nsauditor-ai --help (or -h, or `help`)
442
451
  nsauditor-ai --version (or -v, or `version`)
@@ -449,16 +458,17 @@ nsauditor-ai --version (or -v, or `version`)
449
458
  | `--host <target>` | Target: IP, hostname, CIDR, dash range. Aliases: `--ip`, `--target` | *required*\* |
450
459
  | `--host-file <path>` | File with one host per line (`#` comments, blank lines OK) | — |
451
460
  | `--plugins <list>` | Comma-separated plugin IDs or `all` | `all` |
452
- | `--ports <list>` | **Additional** ports to scan, merged into the default config-derived list. Comma-separated. Optional `/tcp` or `/udp` suffix per entry (default: `tcp`). Examples: `8090` · `8090,9090` · `8090/tcp,5353/udp`. Use this to scan custom services on non-standard ports (e.g. MCP servers on `8090`, dev servers on `3000–9000`) | — |
461
+ | `--ports <list>` | **Additional** ports to scan, merged into the default config-derived list. Comma-separated. Optional `/tcp` or `/udp` suffix per entry (default: `tcp`). Examples: `8090` · `8090,9090` · `8090/tcp,5353/udp`. Use this to scan custom services on non-standard ports (e.g. MCP servers on `8090`). A range such as `1-1000` is not parsed and adds nothing | — |
453
462
  | `--out <dir>` | Custom output directory — applies to the per-scan folder *and* to alternate-format files (SARIF/CSV/Markdown) | `out/` |
454
463
  | `--parallel <n>` | Concurrent host scans | `1` |
455
464
  | `--output-format <fmt>` | Additional output format: `sarif` (CI/CD) · `csv` (spreadsheet) · `md` or `markdown` (chat/PR/Slack quotable) | — |
456
- | `--fail-on <sev>` | Exit code 2 if findings ≥ severity: `critical\|high\|medium\|low\|info` | — |
465
+ | `--fail-on <sev>` | Exit code 1 if any finding is ≥ severity (`critical\|high\|medium\|low\|info`). It reads the same findings the Markdown, SARIF and CSV reports count — one shared table grades them, one finding per item: anonymous FTP login, DNS zone transfer and MCP unauthenticated access or tool listing are critical; an SNMP default community, an expired certificate, an SMB null session, MCP cleartext transport and a deprecated MCP protocol are high; weak SSH algorithms, weak TLS protocols and negotiated ciphers, dangerous HTTP methods, a self-signed certificate and an exposed MCP Inspector are medium; the TLS-certificate (040), debug-endpoint (050) and DNS-security (060) audits grade each finding at its own severity, and 060 grades the scanned domain's SPF / DMARC / NS posture whether or not the host answered on 53/udp. **Since 1.3.0 it reads all of these — a pipeline that passed before may fail on findings that were always there.** Anonymous FTP login, zone transfer and the SMB null session are tested only when `FTP_CHECK_ANON=true` / `DNS_CHECK_AXFR=true` with `DNS_AXFR_DOMAIN` / `SMB_NULL_SESSION=true` are set; dangerous HTTP methods are read only where the HTTP probe read an Allow header; where it read none they are not tested, and that never trips the gate. Any concluded scan counts as info — so `--fail-on info` fails every scan that concludes. It does not read Enterprise's CVE rows or agent findings, so exit 0 is not a clean host; exit 2 for an unknown severity or no conclusion | — |
457
466
  | `--insecure-https` | Accept self-signed TLS certificates | `false` |
458
- | `--watch` | CTEM continuous **alerting** loop — re-scan on `--interval`, diff, webhook on `--alert-severity`. Not an evidence cadence: no retention, no cross-run aggregation, skips SARIF/CSV/Markdown + `--fail-on`, dies with the process. Use a scheduler for SOC 2 Type II history. | `false` |
467
+ | `--watch` | CTEM continuous **alerting** loop — re-scan on `--interval`, compare each host with the previous cycle, webhook for a host whose scan changed and that carries a finding at or above `--alert-severity`; the first cycle sets the baseline and alerts nobody (see Continuous Monitoring). Not an evidence cadence: no retention, no cross-run aggregation, skips SARIF/CSV/Markdown + `--fail-on`, dies with the process. Scans each distinct host once per cycle (a one-shot `--host X,X` scans `X` twice). Use a scheduler for SOC 2 Type II history. | `false` |
459
468
  | `--interval <min>` | Rescan interval in minutes (requires `--watch`) | `60` |
460
- | `--webhook-url <url>` | Webhook URL for delta alerts | — |
469
+ | `--webhook-url <url>` | Webhook URL for watch-mode alerts (see `--watch`) | — |
461
470
  | `--alert-severity <sev>` | Minimum severity for webhook alerts | `high` |
471
+ | `--alert-every-cycle` | Alert every host with a finding at or above `--alert-severity` on every watch cycle, the first included (default: only hosts whose scan changed since the previous cycle) | `false` |
462
472
  | `--compliance <fw>` | Compliance framework to map findings into. Accepts CSV for multi-framework runs (e.g. `soc2`, `hipaa`, `nist-csf`, `pci-dss`, `iso-27001`, `cis-v8`, `gdpr`, `nist-800-171`, or any combination like `soc2,hipaa,nist-csf,pci-dss,iso-27001,cis-v8,gdpr,nist-800-171` — `all` expands to the same eight). **Enterprise license required.** Supported frameworks as of EE 0.40.0 — **eight**: `soc2` (AICPA TSC 2017) + `hipaa` (HIPAA Security Rule §164.312 Technical Safeguards) + `nist-csf` (NIST Cybersecurity Framework 2.0 Core, CSWP 29 Feb 2024) + `pci-dss` (PCI DSS v4.0.1, PCI SSC June 2024 errata) + `iso-27001` (ISO/IEC 27001:2022, ISO + IEC October 2022) + `cis-v8` (CIS Critical Security Controls v8, CIS May 2021 / v8.1 errata June 2024) + `gdpr` (GDPR Article 32 / Security of Processing, Regulation (EU) 2016/679 — Art. 32 infrastructure substrate, **not** GDPR compliance) + `nist-800-171` (NIST SP 800-171 Rev 2 — evidence substrate for **CMMC Level 2 preparation**, **not** a CMMC certification, **not** a FedRAMP authorization, and no MET/NOT MET determination or SPRS score). See `@nsasoft/nsauditor-ai-ee` README for per-framework coverage details. | — |
463
473
  | `--compliance-scope <path>` | Optional JSON file describing the assessment scope (passed to the compliance engine for cover-page attestation) | — |
464
474
  | `--help`, `-h` | Print usage block (subcommands, flags, env vars, examples) and exit 0 | — |
@@ -489,7 +499,8 @@ nsauditor-ai scan --host 192.168.1.0/24 --plugins all --parallel 10
489
499
  # Targeted scan: TLS + HTTP + DNS + OS detection
490
500
  nsauditor-ai scan --host 192.168.1.8 --plugins 011,006,009,013,008
491
501
 
492
- # SARIF output for CI/CD, fail on high+ findings
502
+ # SARIF output for CI/CD. --fail-on high exits 1 on any finding graded high or critical — an SNMP default community,
503
+ # an expired certificate, MCP exposure, a 040 / 050 / 060 audit finding … — see the --fail-on row; exit 0 is not a clean host
493
504
  nsauditor-ai scan --host 10.0.0.5 --plugins all --output-format sarif --fail-on high
494
505
 
495
506
  # Markdown report — paste straight into a GitHub issue, Slack thread, or chat
@@ -542,15 +553,17 @@ HEALTHCHECK --interval=60s --timeout=5s --start-period=10s --retries=3 \
542
553
  | `--format executive` | Self-contained, print-ready HTML report; no external network reference other than an `http(s)`/`mailto` `<a href>` | — |
543
554
  | `--format jira` | Jira-importer CSV (`Summary`/`Description`/`Priority`/`Labels`/`External ID`); the import mapping is done in Jira and has not been verified against a live Jira instance. **`pass`-tier records are EXCLUDED** — a Jira issue is a work item and a passing check is not work; every other tier is kept, INFO included, because INFO carries the evidence gaps and scope boundaries. Passing checks appear in the `executive` report under their own **PASS** tier. | — |
544
555
  | `--run <id>` | Report a specific run id instead of the newest one under `--from` | newest run |
556
+ | `--since <runId\|prior>` | Compare the reported run with a baseline run under `--from`: `prior` is the run that started immediately before it, or name a run id. The `executive` report lists each new, resolved and changed finding, and each finding that could NOT be compared with its reason; stdout prints the counts, the changed and not-comparable rows and the limits; the `jira` CSV carries none of it. Each new, resolved and changed row carries a **Basis** cell saying what made it comparable: for a plugin's row, the host, plugin and scope present in both runs; for an Enterprise analysis agent's or the CVE mapper's row, that the run lacking it recorded no evidence gap from that producer covering it, and — for a CVE or end-of-life row on a TCP port — the measurement itself (the service identified, or the port closed). Where the run lacking an agent row predates EE 1.3.0, the cell says that run could not record an input plugin left out of the scan, so the row is not refused. When the baseline was made before Enterprise 1.3.0, keep `--plugins` identical between the two scans (see What's New). Every refusal exits 2: if the baseline was found but cannot support a comparison, the `executive` report is still written, states the refusal and lists no new, resolved or changed finding; if no baseline is found, if the reported run's own record fails verification, or with `--format jira`, nothing is written | — |
545
557
  | `--brand <brand.json>` | Cover-page branding (company name, prepared-by, contact, logo) — `--format executive` only | unbranded |
546
558
  | `--out <path>` | Write to this path instead of `report_<runId>.<ext>` beside the run record | `report_<runId>.<ext>` |
547
559
  | `--allow-partial` | Render even if some requested hosts were never written, or the run never recorded completion — the caveat is stated on the cover, never hidden | `false` |
548
560
 
549
- A value-less `--from`/`--brand`/`--run`/`--out` (nothing after the flag, or a shell glob that swallowed the argument) is a fatal error, not a silent default.
561
+ A value-less `--from`/`--brand`/`--run`/`--out`/`--since` (nothing after the flag, or a shell glob that swallowed the argument) is a fatal error, not a silent default.
550
562
 
551
563
  **Exit contract** — `0` a report was rendered · `1` fix the RUN (no run record under `--from`, or an
552
564
  incomplete run without `--allow-partial`) · `2` fix the REQUEST or its environment (an unknown or
553
- missing flag value, an unwritable `--out`, a `--brand` file that is refused).
565
+ missing flag value, an unwritable `--out`, a `--brand` file that is refused, or a `--since` comparison
566
+ that could not be made — see `--since` above).
554
567
 
555
568
  ⚠️ `--brand` with `--format jira` is **refused**, not ignored: a Jira CSV has nowhere to put branding,
556
569
  and silently dropping a flag the caller passed is how a report goes out unbranded without anyone
@@ -680,7 +693,7 @@ No license key? Everything in this repository works perfectly without one. The C
680
693
 
681
694
  ### Pro/Enterprise Plugins
682
695
 
683
- Pro and Enterprise add 29 cloud and posture auditors on top of the Community scanners — AWS, Azure and GCP, mapped to eight compliance frameworks. They require `@nsasoft/nsauditor-ai-ee` and a licence.
696
+ Enterprise adds 29 cloud and posture auditors on top of the Community scanners — AWS, Azure and GCP, mapped to eight compliance frameworks. They require `@nsasoft/nsauditor-ai-ee` and an Enterprise licence; a Pro licence does not unlock them.
684
697
 
685
698
  **→ [Pro / Enterprise plugin catalog](./docs/enterprise-plugins.md)** — every plugin with its id, what it audits, and the AWS region-scoping rules for the regional auditors (`--aws-region <one|csv|all>`).
686
699
 
@@ -692,14 +705,14 @@ See [Pro & Enterprise Activation](#pro--enterprise-activation) to install a lice
692
705
 
693
706
  Every compliance scan already produces a GRC-ready JSON evidence artifact. The **GRC connectors** take the next step: they map each NSAuditor compliance finding to your GRC platform's own evidence/test records and **push them at scan time** — so your Vanta, Drata, or Secureframe workspace reflects the latest cloud posture without a manual export/import round-trip.
694
707
 
695
- **Opt-in, and Zero-Data-Exfiltration by default.** The push is off unless you set the environment variables below. When it runs, egress is redaction-gated: resource identifiers can be hashed or removed, the persisted audit log stores a body **fingerprint** (never the raw payload), and your API token is never written to any artifact. Nothing leaves your infrastructure that you didn't opt into.
708
+ **Opt-in, and Zero-Data-Exfiltration by default.** The push is off unless you set the environment variables below. When it runs, finding text and target identifiers go to your platform as written unless you set `COMPLIANCE_GRC_REDACTION` to `hash` or `remove` (redaction is off by default); the persisted audit log stores a body **fingerprint** (never the raw payload), and your API token is never written to any artifact. Nothing leaves your infrastructure that you didn't opt into.
696
709
 
697
710
  ```bash
698
711
  # Enable the scan-time push (Enterprise)
699
712
  COMPLIANCE_GRC_PROVIDER=vanta # or: drata | secureframe
700
713
  COMPLIANCE_GRC_TOKEN=<your API key> # never serialized to artifacts
701
714
  # Optional:
702
- # COMPLIANCE_GRC_REDACTION=hash # off | hash | remove (egress identifier redaction)
715
+ # COMPLIANCE_GRC_REDACTION=hash # off by default | hash | remove (scrubs finding text and targets before egress)
703
716
  # COMPLIANCE_GRC_CONTROL_MAP=/path/to/config.json # provider config: Vanta control→test map, Drata connection ({connectionId, resourceId, schemaMap}), or Secureframe ({workspaceId, collectionId, schemaMap})
704
717
  ```
705
718
 
@@ -739,7 +752,7 @@ NSAuditor AI supports three AI providers for vulnerability analysis. **All provi
739
752
  - **Pro** — intelligence-enriched prompts (CVE matches, MITRE ATT&CK technique annotations, composite risk scores injected into the prompt). Same API call, better-grounded output
740
753
  - **Enterprise** — Pro prompts + compliance context
741
754
 
742
- **Redaction:** Before any data reaches an AI API, the redaction pipeline masks IP addresses, MAC addresses, serial numbers, and configurable confidential keywords. Admin RAW reports retain full detail for internal review.
755
+ **Redaction:** On by default (`OPENAI_REDACT=false` turns it off), the redaction pipeline masks IP addresses, MAC addresses, serial numbers, and configurable confidential keywords before the payload reaches an AI API. Admin RAW reports retain full detail for internal review.
743
756
 
744
757
  ```ini
745
758
  # .env
@@ -771,10 +784,10 @@ nsauditor-ai scan --host 192.168.1.0/24 --plugins all \
771
784
  ```
772
785
 
773
786
  - **Scheduling** with configurable intervals and concurrency control
774
- - **Delta detection** — new, removed, and changed services highlighted between cycles
775
- - **Webhook alerts** — JSON POST with retry (exponential backoff, no retry on 4xx)
776
- - **SSRF protection** — private, loopback, and cloud metadata addresses blocked at the scan entry point and inside `sendWebhook()`. Set `NSA_ALLOW_ALL_HOSTS=1` to scan RFC 1918 ranges (local network auditing)
777
- - **Scan history** stored in `.scan_history/` (JSONL format, 7-day retention in CE)
787
+ - **Change detection** — each host's scan is compared with that host's previous line in `scan_history.jsonl` and printed as a `[ScanHistory]` line: new, removed and changed services, the findings delta, and — since 1.3.0 — each service-check finding that appeared, cleared or was first observed, and each one that could not be compared, with the reason (a check that did not run this time is never read as cleared). The first scan after upgrading says the service checks were not compared, because the earlier line did not record them
788
+ - **Webhook alerts** — a JSON POST, retried up to twice 1 s apart on a 5xx or network error, not on a 4xx. Each cycle is compared with the one before, host by host, and printed as an `=== Delta Report ===`. A host has CHANGED when a service appeared, went or changed, its finding count moved or could not be compared, a service check appeared, cleared or could not be compared, or the two cycles could not be compared at all. An alert is posted for each changed host carrying a finding at or above `--alert-severity` — one detail per finding with its own grade, from the table `--fail-on` reads (at `info`, every finding counts; a host with none gets no alert). The first cycle establishes the baseline and does not alert, whatever it finds; `--alert-every-cycle` alerts every host carrying such a finding on every cycle, the first included. A host whose scan failed is reported on stdout and does not alert in this release.
789
+ - **SSRF protection** — private, loopback, and cloud metadata addresses blocked at the scan entry point and inside `sendWebhook()`, and at every host a scanned target names: the webapp detector re-checks each redirect hop (at most 5) and sends your `HTTP_EXTRA_HEADERS` only to the origin you named, the UPnP scanner fetches a device description only from the device that answered, and the OpenSearch scanner does not follow redirects. `NSA_ALLOW_ALL_HOSTS=1` lifts the scan-entry guard for local network auditing — on the CLI the whole guard, loopback and metadata included; over MCP, the private-range check only, so loopback, link-local and metadata stay refused — and never the webhook guard
790
+ - **Scan history** stored in `scan_history.jsonl` in the output directory (`--out`, default `out/`; 7-day retention in CE)
778
791
 
779
792
  ---
780
793
 
@@ -784,7 +797,7 @@ Configuration is entirely environment-based — a `.env` file, `--env <file>`, o
784
797
 
785
798
  **→ [Configuration reference](./docs/configuration.md)** — every environment variable, its default and its effect.
786
799
 
787
- The two that change behaviour most: `NSAUDITOR_OFFLINE_ONLY=1` forbids outbound CVE lookups and reads a local NVD store instead (reporting an explicit coverage gap rather than a silent clean), and `OPENAI_API_KEY` / `ANTHROPIC_API_KEY` / a local Ollama endpoint enable the opt-in AI analysis pass.
800
+ The two that change behaviour most: `NSAUDITOR_OFFLINE_ONLY=1` forbids outbound CVE lookups and reads a local NVD store instead (reporting an explicit coverage gap in the scan rather than a silent clean; from Enterprise 1.3.0 that gap fails, as an evidence gap, the compliance controls the CVE rows map to), and `OPENAI_API_KEY` / `ANTHROPIC_API_KEY` / a local Ollama endpoint enable the opt-in AI analysis pass.
788
801
 
789
802
  ## Developing Plugins
790
803
 
@@ -857,7 +870,7 @@ export default {
857
870
 
858
871
  ## Tests
859
872
 
860
- Run all 1582 tests:
873
+ Run the full suite:
861
874
 
862
875
  ```bash
863
876
  npm test
@@ -888,8 +901,9 @@ Tests use Node.js built-in `--test` runner with the `assert` module — no exter
888
901
  | RPC not detected | Ensure port 111 is accessible and RPC portmapper is running |
889
902
  | WS-Discovery timeout | Check network config and firewall for multicast on UDP 3702 |
890
903
  | SYN scan requires root | Run with `sudo` or use TCP connect scanner (plugin 003) instead |
891
- | Webhook URL rejected | Private/loopback/cloud metadata blocked by SSRF guard. Use `NSA_ALLOW_ALL_HOSTS=1` to allow RFC 1918 scan targets |
904
+ | Webhook URL rejected | Private/loopback/cloud metadata blocked by SSRF guard. `NSA_ALLOW_ALL_HOSTS` does not lift this guard: use a public webhook URL |
892
905
  | EE plugins not loading | Verify `@nsasoft/nsauditor-ai-ee` is installed and license key is set |
906
+ | An Enterprise package that fails to load (e.g. installed beside a Community older than its `peerDependencies` floor) | Since CE 0.2.56 it is named on stderr and recorded on the scan's conclusion; before that it left its plugins running while the scan skipped its intelligence, analysis-agent and compliance stages without a word. Install the Community version Enterprise requires |
893
907
 
894
908
  ---
895
909
 
@@ -935,9 +949,9 @@ NSAuditor AI is built on a **Zero Data Exfiltration (ZDE)** architecture:
935
949
 
936
950
  - **No telemetry.** No analytics. No usage tracking. No phone-home.
937
951
  - **No data processing.** Nsasoft US LLC never sees, stores, or processes your scan results.
938
- - **AI is opt-in.** External AI calls use your own API keys. Redaction runs locally first.
952
+ - **AI is opt-in.** External AI calls use your own API keys. Redaction runs locally first, on by default (`OPENAI_REDACT=false` turns it off).
939
953
  - **License validation is offline.** JWT signature verified locally with an embedded public key.
940
- - **Air-gappable, once configured for it.** Scanning, analysis, license verification and evidence-pack generation all run with no outbound network access; Enterprise adds offline CVE matching from a local NVD store under `NSAUDITOR_OFFLINE_ONLY=1`, which emits an explicit coverage gap rather than a silent clean when the store cannot answer. Stated precisely because it matters operationally: a **default** scan still attempts NVD egress unless that variable is set. The other outbound paths — AI enrichment, the GRC push, the continuous-monitoring webhook, the opt-in RFC 3161 timestamping path (`NSAUDITOR_TSA_URL`, no default ever), and the AWS KMS signing path that ships but is not yet wired — are opt-in and off by default; the paths that are *not* optional are the scan target itself, your own cloud provider's APIs during a cloud scan, and DNS resolution of the target. All of them are enumerated with their trigger and default state in the egress register (EE `docs/architecture.md` §14.1.1), which is generated from code and guarded in both directions — deliberately, so this sentence never again has to carry a completeness claim that prose alone cannot keep true. Populating the local NVD store is the operator's; no feed data is delivered with the product. The prior absolute form of this bullet — *"Fully air-gappable. Every feature works without internet access (Enterprise includes offline NVD feeds)"* — is **WITHDRAWN** (CE 0.2.33): quoted here so the withdrawal record stays on this page now that release history lives in the [CHANGELOG](./CHANGELOG.md).
954
+ - **Air-gappable, once configured for it.** Scanning, analysis, license verification and evidence-pack generation all run with no outbound network access; Enterprise adds offline CVE matching from a local NVD store under `NSAUDITOR_OFFLINE_ONLY=1`, which emits an explicit coverage gap in the scan rather than a silent clean when the store cannot answer (from Enterprise 1.3.0 that gap fails, as an evidence gap, the compliance controls the CVE rows map to). Stated precisely because it matters operationally: a **default** scan still attempts NVD egress unless that variable is set. The other outbound paths — AI enrichment, the GRC push, the continuous-monitoring webhook, the opt-in RFC 3161 timestamping path (`NSAUDITOR_TSA_URL`, no default ever), and the AWS KMS signing path that ships but is not yet wired — are opt-in and off by default; the paths that are *not* optional are the scan target itself, your own cloud provider's APIs during a cloud scan, and DNS resolution of the target. All of them are enumerated with their trigger and default state in the egress register (EE `docs/architecture.md` §14.1.1), which is generated from code and guarded in both directions — deliberately, so this sentence never again has to carry a completeness claim that prose alone cannot keep true. Populating the local NVD store is the operator's; no feed data is delivered with the product. The prior absolute form of this bullet — *"Fully air-gappable. Every feature works without internet access (Enterprise includes offline NVD feeds)"* — is **WITHDRAWN** (CE 0.2.33): quoted here so the withdrawal record stays on this page now that release history lives in the [CHANGELOG](./CHANGELOG.md).
941
955
 
942
956
  Nsasoft US LLC is not a data processor, data controller, or business associate under any data protection regulation. You own and control all data produced by NSAuditor AI.
943
957