nsauditor-ai 0.2.57 → 1.0.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 CHANGED
@@ -6,7 +6,7 @@ A modular, AI-assisted network security audit platform that scans, understands,
6
6
 
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
- [![Node.js 20+](https://img.shields.io/badge/node-20%2B-green.svg)](https://nodejs.org)
9
+ [![Node.js 24+](https://img.shields.io/badge/node-24%2B-green.svg)](https://nodejs.org)
10
10
  [![Tests](https://img.shields.io/badge/tests-passing-brightgreen.svg)](#tests)
11
11
 
12
12
  ---
@@ -17,22 +17,22 @@ 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.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).
20
+ **Latest: CE 1.0.0 + Enterprise 2.0.0 — *Node.js 24; a scan connects only to the addresses its host check resolved; the AI payload carries no AWS account ID; on TCP an authoritative probe's row must identify its service to be one*.**
21
+ A scan now connects only to the addresses its host check resolved — on the CLI and through the MCP server, for the target
22
+ and each redirect hop — so a name that answers one address to the check and another at connect (DNS rebinding) cannot
23
+ redirect it; two limits are stated in the [CHANGELOG](./CHANGELOG.md) (an HTTP proxy set with `NODE_USE_ENV_PROXY`, and an
24
+ idle pooled connection). The payload sent to AI no longer carries an AWS account ID, and the system prompt sent with it is
25
+ written beside it. A refused or unidentified TCP port is no longer listed as a service. Plugin 040 tells a weak certificate
26
+ from an untrusted chain. ⚠️ **Security:** `--watch` printed the full `--webhook-url` to stdout; it now prints the host — if
27
+ you ran `--watch` with a Slack or Teams URL where stdout is logged, rotate that URL. The supported runtime is **Node.js 24**.
28
+ Two limits from Enterprise 1.3.0 still hold in the Pro delta and Enterprise MTTR: a scan made before Enterprise 1.3.0
29
+ could not record a plugin left out of it, so in a comparison with one an agent's row that scan lacks is not refused;
30
+ and a scan that discovered ports with the Nmap plugin (024) alone records no port oracle, so an analysis agent's or the
31
+ CVE mapper's row on a port it did not measure can read RESOLVED and count as closed in MTTR, and the control it failed
32
+ can read PASS — include the port scanner (003).
33
+ Plugin count UNCHANGED at 27 Community + 29 Enterprise; every coverage matrix UNCHANGED; **Enterprise 2.0.0 requires this
34
+ release** (`nsauditor-ai >= 1.0.0`). The free last-vs-current webhook alerting delta is untouched and stays free (see
35
+ Continuous Monitoring below).
36
36
 
37
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.
38
38
 
@@ -77,7 +77,7 @@ NSAuditor AI is available in three editions: Community (free, MIT-licensed, no r
77
77
  | Zero Trust assessment | — | — | ✅ |
78
78
  | **Enterprise — compliance (8 frameworks)** | | | |
79
79
  | SOC 2 (AICPA TSC 2017) — 10 covered + 4 partial controls | — | — | ✅ |
80
- | HIPAA Security Rule §164.312 — Zero BAA required | — | — | ✅ |
80
+ | HIPAA Security Rule §164.312 — no BAA with Nsasoft required | — | — | ✅ |
81
81
  | NIST CSF 2.0 Core — Subcategory-level mapping (106 of 107 Subcategories) | — | — | ✅ |
82
82
  | PCI DSS v4.0.1 — Sub-requirement-level mapping for QSA RoC | — | — | ✅ |
83
83
  | ISO/IEC 27001:2022 — per-Annex-A-code mapping + SoA discipline (93 Annex A controls) | — | — | ✅ |
@@ -166,6 +166,7 @@ Results land in `./out/<host>_<timestamp>/` (two scans of one host whose plugin
166
166
  | `scan_conclusion_raw.json` | Full unredacted conclusion (admin reference) |
167
167
  | `scan_conclusion_raw.html` | Admin RAW HTML with filters and full detail |
168
168
  | `scan_response_ai_payload.json` | Redacted payload sent to AI |
169
+ | `scan_response_ai_system_prompt.txt` | System prompt sent to AI, the Pro/Enterprise intelligence block included (from Community 1.0.0) |
169
170
  | `scan_response_ai.json` | Raw AI API response |
170
171
  | `scan_response_ai.txt` | AI conclusion (markdown) |
171
172
  | `scan_response_ai.html` | Styled HTML report with CVE links and badges |
@@ -173,7 +174,7 @@ Results land in `./out/<host>_<timestamp>/` (two scans of one host whose plugin
173
174
  | `scan_results.csv` | CSV — only with `--output-format csv` |
174
175
  | `scan_report.md` | GitHub-flavored Markdown report — only with `--output-format md` (or `markdown`) |
175
176
 
176
- > Works on Node 20+ (tested on Node 22).
177
+ > Requires Node.js 24 or newer (tested on Node 24).
177
178
 
178
179
  ---
179
180
 
@@ -199,7 +200,7 @@ found". A higher tier does not add tools; it unlocks the ones already in the lis
199
200
 
200
201
  | Tool | Purpose |
201
202
  |---|---|
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) |
203
+ | `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 — the DNS-security audit tries a zone transfer against the domain's nameservers only with `DNS_AUDIT_AXFR_CHECK` set, also off by default, and records only an open transfer; 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 of the three record-level opt-in checks 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) |
203
204
  | `list_plugins` | List available scanner plugins with their IDs, priorities and requirements |
204
205
  | `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) |
205
206
 
@@ -233,7 +234,7 @@ found". A higher tier does not add tools; it unlocks the ones already in the lis
233
234
  > has no MCP client in the path — there you can raise `PLUGIN_TIMEOUT_MS` (e.g. `90000`) freely. On the CLI a
234
235
  > plugin that declares its own budget already gets it; `PLUGIN_TIMEOUT_CEILING_MS` caps those.
235
236
 
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.
237
+ 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. Since CE 1.0.0 a scan connects to the addresses its check resolved — the target's, and each redirect hop's — so a name that answers one address to the check and another when the scan connects (DNS rebinding) is not followed; the system tools a scan runs (`ping`, `nping`, `hping3`, `nmap`, `curl`) are handed the address. Two limits: with `NODE_USE_ENV_PROXY` and a proxy configured, an HTTP request goes through the proxy by name and the proxy resolves it; and a request can reuse an idle connection the same process opened earlier to the same name, for as long as that connection stays idle in the pool — a few seconds by default; a server's Keep-Alive hint holds it longer. One exception, off by default: with `DNS_AUDIT_AXFR_CHECK` set, the DNS-security audit (060) opens TCP/53 to each of the scanned domain's nameservers, at the first IPv4 address each nameserver's name resolves to — an address no host check looked at, which neither the guard nor the pin covers. Port validation (1–65535), CPE format enforcement, dependency injection for test isolation. **Server-startup authentication is required** — see next section.
237
238
 
238
239
  ### Authentication (required)
239
240
 
@@ -320,9 +321,9 @@ The exact `NSA_MCP_AUTH_KEY` value to paste is printed by `nsauditor-ai mcp inst
320
321
  - `NSA_ALLOW_ALL_HOSTS=1` — required to scan private/RFC 1918 addresses (e.g., `192.168.x.x`); only `1`, `true`, `yes`
321
322
  or `on` turn it on. It admits private ranges only: loopback, link-local and cloud-metadata addresses stay refused over
322
323
  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.
324
+ answer is checked. Since CE 1.0.0 the scan connects to the answers the check resolved, so a name whose answer changes
325
+ between the check and the scan (DNS rebinding) is not followed — the two limits are in the Security note above
326
+ - `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). `DNS_AUDIT_AXFR_CHECK=true` (or `1`, `yes`, `on`) — lets the DNS-security audit (060) try a zone transfer of the scanned domain against each of its nameservers (off by default; it connects to each nameserver's own address, outside the scan's host check — see the Security note above). `SMB_NULL_SESSION=true` — lets the NetBIOS check try an SMB null session (off by default). The FTP, DNS and NetBIOS checks, when off or when they ran and could not complete (a timeout, a dropped connection), are recorded and reported as not tested with their reason — never as refused; the DNS-security audit's attempt records only an open transfer, so its absence is not a measured "closed". The same variables govern the CLI scan.
326
327
  - `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.
327
328
  - `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.
328
329
  - `CLOUD_SCAN_CONCURRENCY` — max cloud plugins run at once by `scan_cloud` (default 20).
@@ -462,15 +463,20 @@ nsauditor-ai --version (or -v, or `version`)
462
463
  | `--out <dir>` | Custom output directory — applies to the per-scan folder *and* to alternate-format files (SARIF/CSV/Markdown) | `out/` |
463
464
  | `--parallel <n>` | Concurrent host scans | `1` |
464
465
  | `--output-format <fmt>` | Additional output format: `sarif` (CI/CD) · `csv` (spreadsheet) · `md` or `markdown` (chat/PR/Slack quotable) | — |
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 | — |
466
+ | `--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 (and, with `DNS_AUDIT_AXFR_CHECK` set, an open zone transfer) 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 (the DNS-security audit's own zone-transfer attempt only when `DNS_AUDIT_AXFR_CHECK` is 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 | — |
466
467
  | `--insecure-https` | Accept self-signed TLS certificates | `false` |
467
468
  | `--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` |
468
469
  | `--interval <min>` | Rescan interval in minutes (requires `--watch`) | `60` |
469
470
  | `--webhook-url <url>` | Webhook URL for watch-mode alerts (see `--watch`) | — |
470
471
  | `--alert-severity <sev>` | Minimum severity for webhook alerts | `high` |
471
472
  | `--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` |
473
+ | `--alert-payload <mode>` | `full` — the watch alert as it has always been · `minimal` — drops the host, the service and the description: a count, and each finding's port, protocol and severity | `full` |
472
474
  | `--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. | — |
473
475
  | `--compliance-scope <path>` | Optional JSON file describing the assessment scope (passed to the compliance engine for cover-page attestation) | — |
476
+ | `--notify-webhook <url>` | **Enterprise; one-shot scans with `--compliance` only, never `--watch`.** Posts the scan's drift (`scan_drift_<fw>.json`) when a control goes to fail or loses measurement, when a new violation is at or above `--notify-severity`, or when this run could not be compared with the previous one. Public `https` URLs only — anything else is refused with exit 2 | — |
477
+ | `--notify-format <fmt>` | `generic` · `slack` · `teams` (a Teams Workflows webhook, which takes an Adaptive Card) | `generic` |
478
+ | `--notify-severity <sev>` | Threshold for NEW violations only (`critical\|high\|medium\|low\|info`); a control going to fail or losing measurement always sends | `high` |
479
+ | `--notify-payload <mode>` | `minimal` — framework, counts, control ids, severities and scanner ids; no hostname, resource name, account id or finding text · `full` adds them | `minimal` |
474
480
  | `--help`, `-h` | Print usage block (subcommands, flags, env vars, examples) and exit 0 | — |
475
481
  | `--version`, `-v` | Print `nsauditor-ai <version>` and exit 0 | — |
476
482
 
@@ -553,7 +559,7 @@ HEALTHCHECK --interval=60s --timeout=5s --start-period=10s --retries=3 \
553
559
  | `--format executive` | Self-contained, print-ready HTML report; no external network reference other than an `http(s)`/`mailto` `<a href>` | — |
554
560
  | `--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. | — |
555
561
  | `--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 | — |
562
+ | `--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). When the TLS scanner (011) could not complete a handshake on a port the port scanner saw open, its row names the transport error (`ECONNRESET`, `ETIMEDOUT`, …) and Enterprise records the port as not measured, so an earlier TLS finding there is not comparable — except when another port of the same scan completed a TLS handshake: then the failed port is not read, and such a finding can read as resolved (a stated limit). 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 | — |
557
563
  | `--brand <brand.json>` | Cover-page branding (company name, prepared-by, contact, logo) — `--format executive` only | unbranded |
558
564
  | `--out <path>` | Write to this path instead of `report_<runId>.<ext>` beside the run record | `report_<runId>.<ext>` |
559
565
  | `--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` |
@@ -677,7 +683,7 @@ No license key? Everything in this repository works perfectly without one. The C
677
683
  | 024 | TCP SYN Scanner | TCP (Nmap) | SYN half-open scan via Nmap wrapper (optional) |
678
684
  | 040 | TLS Certificate & Cipher Auditor | TCP:443+ | Cert expiry, chain integrity, hostname mismatch, weak ciphers, deprecated protocols, key strength |
679
685
  | 050 | TRIBE v2 Neural API Security Probe | TCP/HTTP:8080 | Debug leak detection, stack traces in errors, header security, CORS misconfiguration, unauthenticated routes |
680
- | 060 | DNS Security Auditor | DNS/UDP:53 | SPF/DKIM/DMARC, dangling CNAMEs, DNSSEC, NS delegation, zone transfer exposure, MX security, CAA records |
686
+ | 060 | DNS Security Auditor | DNS/UDP:53 | SPF/DKIM/DMARC, dangling CNAMEs, DNSSEC, NS delegation, zone transfer exposure (opt-in: `DNS_AUDIT_AXFR_CHECK`), MX security, CAA records |
681
687
  | 070 | MCP Scanner | TCP/HTTP+SSE | Detects MCP (Model Context Protocol) servers on candidate ports (1967, 3000, 3005, 5173, 6274, 6277, 8000, 8090). Audits for cleartext transport (HTTP not HTTPS), missing/anonymous auth, anonymous tool enumeration, deprecated protocol versions, and Inspector exposure on non-loopback. Maps findings to CWE/OWASP/MITRE per the FindingSchema. STDIO-transport MCP servers are out of scope (no network port). |
682
688
 
683
689
  ### Discovery Plugins
@@ -705,7 +711,7 @@ See [Pro & Enterprise Activation](#pro--enterprise-activation) to install a lice
705
711
 
706
712
  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.
707
713
 
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.
714
+ **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. The push sends only what you opted into, and nothing goes to Nsasoft.
709
715
 
710
716
  ```bash
711
717
  # Enable the scan-time push (Enterprise)
@@ -744,7 +750,7 @@ The Result Concluder (plugin 008) merges all plugin outputs into a normalized st
744
750
 
745
751
  NSAuditor AI supports three AI providers for vulnerability analysis. **All providers work in all tiers** — CE, Pro, and Enterprise. AI is optional; the platform is fully functional without it.
746
752
 
747
- **Providers:** OpenAI (GPT-4o), Anthropic Claude (Sonnet/Opus), Ollama (fully local)
753
+ **Providers:** OpenAI (default model `gpt-6-luna` from Community 1.0.0; set `OPENAI_MODEL` for another), Anthropic Claude (Sonnet/Opus), Ollama (fully local). Setup, exactly what is sent and what is written: [docs/openai.md](./docs/openai.md).
748
754
 
749
755
  **What changes by tier is the prompt content, not the provider:**
750
756
 
@@ -752,7 +758,7 @@ NSAuditor AI supports three AI providers for vulnerability analysis. **All provi
752
758
  - **Pro** — intelligence-enriched prompts (CVE matches, MITRE ATT&CK technique annotations, composite risk scores injected into the prompt). Same API call, better-grounded output
753
759
  - **Enterprise** — Pro prompts + compliance context
754
760
 
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.
761
+ **Redaction:** On by default (`OPENAI_REDACT=false` turns it off). Before the scan payload reaches an AI API it masks the scanned host, IPv4 addresses (private ones in service and evidence text), colon-separated MAC addresses, full-form IPv6 addresses, serial numbers, a scanned service's Set-Cookie values, and any field named in `CONFIDENTIAL_KEYWORDS`. From Community 1.0.0 it also masks AWS account IDs found in ARNs and AWS URLs, everywhere they occur and inside resource names (the names are kept), and any other 12-digit number under a neutral label (0.2.57 and earlier send both). **It does not mask** cloud resource names, AWS access-key IDs, hostnames, e-mail addresses and certificate subjects in service text, compressed IPv6 addresses, or dash- and dot-form MAC addresses (measured 2026-10-11) — inspect `scan_response_ai_payload.json` before using the AI step on production data. On Pro and Enterprise the intelligence block travels in the system prompt, which the redactor does not see and the payload file does not record; [docs/openai.md](./docs/openai.md) has the details. Admin RAW reports retain full detail for internal review.
756
762
 
757
763
  ```ini
758
764
  # .env
@@ -786,7 +792,8 @@ nsauditor-ai scan --host 192.168.1.0/24 --plugins all \
786
792
  - **Scheduling** with configurable intervals and concurrency control
787
793
  - **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
794
  - **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
795
+ - **What watch alerts cover** — `--watch` alerts cover network-scan findings; cloud-plugin findings and compliance control status do not alert — use the drift file and its notification on a scheduled one-shot scan (Enterprise: `--notify-webhook`). `--alert-payload minimal` drops the host, the service and the description from each alert; `full` stays the default. The start-up line names the webhook's host and never its URL: a Slack or Teams webhook URL carries its own credential
796
+ - **SSRF protection** — private, loopback, and cloud metadata addresses blocked at the scan entry point and inside `sendWebhook()`, which connects to the address its own check resolved, so a webhook name that answers differently between the check and the connect is not followed (with `NODE_USE_ENV_PROXY` and a proxy configured, Node sends the webhook through that proxy by name and the proxy resolves it — the pin then covers only the hop to the proxy; since CE 1.0.0 a scanned host and each redirect hop are pinned the same way — see the MCP security note), and at every host a scanned target names: the webapp detector re-checks and pins 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 — the one exception is the DNS-security audit's zone-transfer attempt, off by default (see the MCP security note). `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
797
  - **Scan history** stored in `scan_history.jsonl` in the output directory (`--out`, default `out/`; 7-day retention in CE)
791
798
 
792
799
  ---
@@ -937,7 +944,7 @@ Commonly requested plugins: RDP, VNC, SMTP/POP3/IMAP, MySQL/PostgreSQL/MSSQL/Mon
937
944
 
938
945
  For the full technical architecture, see [ARCHITECTURE.md](docs/architecture.md).
939
946
 
940
- **Tech stack:** Node.js 20+ · ES Modules (.mjs) · OpenAI + Anthropic SDKs · Node.js built-in test runner · MCP stdio transport
947
+ **Tech stack:** Node.js 24+ · ES Modules (.mjs) · OpenAI + Anthropic SDKs · Node.js built-in test runner · MCP stdio transport
941
948
 
942
949
  **Design patterns:** Factory (PluginManager.create) · Strategy (orchestrated/legacy execution) · Context (shared state) · Adapter (plugin conclude()) · Guard Clause (requirement gating) · Capability gating (CE/Pro/EE) · Semaphore (concurrency control) · Delta (scan history diff) · Boundary Guard (SSRF/injection protection) · Finding Queue (structured intermediate format) · Parallel Agents (concurrent specialized analysis)
943
950
 
@@ -1,3 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { main } from '../cli.mjs';
3
- main().catch((err) => { console.error(err?.stack || err); process.exit(1); });
2
+ // The command npm installs. It imports main(), so cli.mjs is never the entry point here: a rejection goes to cli.mjs's
3
+ // own exitOnMainRejection, the one copy of the exit-code mapping (a flag refusal exits 2; anything else exits 1).
4
+ import { main, exitOnMainRejection } from '../cli.mjs';
5
+ main().catch((err) => exitOnMainRejection(err));