nsauditor-ai 0.2.57 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -35
- package/bin/nsauditor-ai.mjs +4 -2
- package/cli.mjs +410 -131
- package/mcp_server.mjs +286 -40
- package/package.json +7 -3
- package/plugin_manager.mjs +92 -7
- package/plugins/040_tls_cert_auditor.mjs +77 -12
- package/plugins/060_dns_sec_auditor.mjs +382 -117
- package/plugins/arp_scanner.mjs +6 -3
- package/plugins/host_up_check.mjs +5 -1
- package/plugins/http_probe.mjs +20 -2
- package/plugins/mcp_scanner.mjs +10 -1
- package/plugins/mdns_scanner.mjs +11 -5
- package/plugins/ping_checker.mjs +14 -7
- package/plugins/result_concluder.mjs +61 -5
- package/plugins/syn_scanner.mjs +4 -1
- package/plugins/tls_scanner.mjs +42 -2
- package/plugins/upnp_scanner.mjs +8 -2
- package/plugins/webapp_detector.mjs +22 -10
- package/plugins/wsd_scanner.mjs +8 -2
- package/utils/capabilities.mjs +2 -2
- package/utils/cloud_finding_summary.mjs +56 -6
- package/utils/conclusion_utils.mjs +26 -0
- package/utils/delta_reporter.mjs +1 -1
- package/utils/mcp_call_digest.mjs +6 -0
- package/utils/net_validation.mjs +26 -8
- package/utils/plugin_discovery.mjs +6 -2
- package/utils/report_inputs.mjs +8 -0
- package/utils/report_md.mjs +18 -0
- package/utils/scan_delta.mjs +72 -10
- package/utils/scan_history.mjs +24 -9
- package/utils/scan_pin.mjs +161 -0
- package/utils/service_flags.mjs +41 -6
- package/utils/validate.mjs +1 -1
- package/utils/webhook.mjs +126 -21
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ A modular, AI-assisted network security audit platform that scans, understands,
|
|
|
6
6
|
|
|
7
7
|
[](https://www.npmjs.com/package/nsauditor-ai)
|
|
8
8
|
[](LICENSE)
|
|
9
|
-
[](https://nodejs.org)
|
|
10
10
|
[](#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
|
|
21
|
-
The
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
Nmap plugin (024) alone records no port oracle, so an analysis agent's or the
|
|
31
|
-
measure can read RESOLVED and count as closed in MTTR, and the control it failed
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
20
|
+
**Latest: CE 1.1.0 + Enterprise 2.1.0 — *Node.js 24.22.1; scan_host stops at a time limit and names what it did not run; a saved response keeps its request beside it; a multi-entry cloud title says so; get_findings says what a scan holds*.**
|
|
21
|
+
⚠️ **The supported runtime floor rises to Node.js 24.22.1** — the October 2026 Node.js security release for the 24
|
|
22
|
+
line (`engines.node >=24.22.1`); npm only warns on an older runtime, so check `node --version`. Through the MCP server,
|
|
23
|
+
`scan_host` stops at a total time limit by default (`SCAN_HOST_WALL_MS`, 50 s; `0` turns it off) and names the plugins it
|
|
24
|
+
did not run; with `NSA_MCP_RESPONSE_DIR` set, each response is saved with its request beside it, and
|
|
25
|
+
`nsauditor-ai mcp verify-call` checks both; a CRITICAL, HIGH or evidence-gap title in `scan_cloud` says when its finding
|
|
26
|
+
holds more entries than it shows (` (+N more via get_findings)`); and a `get_findings` category filter that matches
|
|
27
|
+
nothing returns the categories the scan does hold.
|
|
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.1.0 pairs with this
|
|
34
|
+
release, and its peer floor stays at `nsauditor-ai >= 1.0.0`. The free last-vs-current webhook alerting delta is
|
|
35
|
+
untouched and stays free (see 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 —
|
|
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
|
-
>
|
|
177
|
+
> Requires Node.js 24.22.1 or newer (tested on Node 24.22.1). npm only warns on an older runtime, so check `node --version` yourself.
|
|
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
|
|
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 what each nameserver answered; 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
|
|
|
@@ -207,7 +208,7 @@ found". A higher tier does not add tools; it unlocks the ones already in the lis
|
|
|
207
208
|
|
|
208
209
|
| Tool | Purpose |
|
|
209
210
|
|---|---|
|
|
210
|
-
| `probe_service` | Run one specific plugin against a single `host:port` |
|
|
211
|
+
| `probe_service` | Run one specific plugin against a single `host:port` — a plugin with its own port set probes that set instead (the TLS scanner, 011: 443, 465, 563, 993 and 995), and the response's `probe` names the ports actually probed |
|
|
211
212
|
| `get_vulnerabilities` | Look up known CVEs for a CPE (Common Platform Enumeration) string via the NVD API |
|
|
212
213
|
|
|
213
214
|
**Unlocked by Enterprise:**
|
|
@@ -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
|
|
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 redirect hop the scanned target names is checked the same way by the plugin that would follow it, and a UPnP device's description URL is fetched only when it names the very address that answered the discovery — so a device that answers from a link-local or loopback address (`169.254.x.x`, `127.0.0.1`) has its description fetched there. 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. Every answer is checked, and each connection the scan makes lands on one of the checked answers: an in-process connection may use any of them (Node tries them in turn), and the system tools a scan runs (`ping`, `nping`, `hping3`, `nmap`, `curl`, and `arp` for an address on a local network) are handed one — the first IPv4 answer when there is one. 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. A `--webhook-url` alert does not share that pool: each alert opens its own connection to the address its check resolved, and closes it. And the address classifier does not recognise a NAT64 address (`64:ff9b::/96`) that embeds a blocked IPv4 address. 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 resolved, so the audit classifies it itself before connecting: loopback, link-local, cloud-metadata and unspecified addresses are refused in every configuration, `NSA_ALLOW_ALL_HOSTS` included, and RFC 1918, CGNAT and fc00::/7 addresses unless `NSA_ALLOW_ALL_HOSTS` is set. It connects to the literal it classified, and a refused nameserver is recorded as not attempted, never graded. 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,13 +321,25 @@ 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.
|
|
324
|
-
(DNS rebinding) is not
|
|
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).
|
|
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
|
|
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 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, which it classifies first — 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 records what each nameserver answered (transferred the zone, answered without transferring it, refused, or no answer) and why a nameserver it did not ask was not asked. The two zone-transfer checks ask different hosts: the DNS check asks the scanned host, the DNS-security audit asks each of the domain's nameservers. When the scanned host is itself one of the domain's nameservers and `DNS_AXFR_DOMAIN` names that domain, a report carries a finding from each — two measurements of one exposure, not two exposures. The same variables govern the CLI scan.
|
|
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 a real home gateway the plugins of one full scan took ~113 s between them at the default budget, and 54–122 s across five runs on another router. Without a call limit, 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. The call is bounded by `SCAN_HOST_WALL_MS` (below).
|
|
328
|
+
- `SCAN_HOST_WALL_MS` — the time limit for the whole `scan_host` CALL (default 50000; `0` turns it off). Three walls, side by side: `PLUGIN_TIMEOUT_MS` bounds each network plugin, `CLOUD_PLUGIN_TIMEOUT_MS` each cloud plugin, and `SCAN_HOST_WALL_MS` the `scan_host` call. It exists because the CLIENT may end a tool call at a limit of its own, and a call it ends returns nothing at all — never read that as a clean host: on 2026-10-21 Claude Desktop 3.x ended both of two measured `scan_host` calls at about 60 s, whatever `MCP_TIMEOUT` said. With the limit, the call ends first, keeping 3 s for the summary and the response: a plugin still running at the limit is stopped, and the plugins after it are not started. Each reads `timeout` in `manifest`; the response's `callWall` lists them in the order they would have run (`notStarted`, `cutShort`, beside `limitMs` and `reached`, present on every call); and the response's `note` and the head of `markdown` say the scan is partial — not a clean result. **What it costs, measured:** the MCP Scanner (070) runs early, at priority 70, and on one home router that dropped its probes it took 44.6 s. With `PLUGIN_TIMEOUT_MS` at 45 s or more, it runs into the limit and the plugins after it in priority order are not started — on an Enterprise install the Zero Trust Assessment (1023), then the DNS-security audit (060) through to the TLS Scanner (011) and the TLS Certificate & Cipher Auditor (040), so the TLS and certificate checks are the first lost. At the 30 s default, 070 stops at its own 30 s and the seconds left go to the plugins after it, in that order. For a complete scan of such a host, run the CLI, or raise `SCAN_HOST_WALL_MS` in a client that does not cut the call.
|
|
327
329
|
- `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
330
|
- `CLOUD_SCAN_CONCURRENCY` — max cloud plugins run at once by `scan_cloud` (default 20).
|
|
329
331
|
- `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.
|
|
332
|
+
- `NSA_MCP_RESPONSE_DIR` — off unless set. An **absolute** directory where the server saves each tool response,
|
|
333
|
+
exactly as it returned it, as `<call_id>.txt` (the directory is created `0700` if missing, each file `0600`, and an
|
|
334
|
+
existing file is never overwritten). It exists for `nsauditor-ai mcp verify-call <call_id> --response <file>`, which
|
|
335
|
+
needs the whole response: Claude Desktop shows long tool output truncated, and a truncated copy cannot be verified.
|
|
336
|
+
Beside each response the server saves its request as `<call_id>.request.json`: the tool's name and the call's own
|
|
337
|
+
arguments, verbatim (a `scan_host` host, `scan_cloud`'s providers and regions) — both files or neither, `0600`.
|
|
338
|
+
`verify-call <call_id> --response <file> --request <file>` checks both: the server logs a digest of each.
|
|
339
|
+
When the copies were saved, the response's receipt footer says so (it names the files, not the directory). The files
|
|
340
|
+
hold scan results and the targets you asked about — keep the directory private, and clear it yourself; nothing
|
|
341
|
+
removes them. It is a server setting, not a
|
|
342
|
+
scan target: set it in this `env` block (not in an `NSA_ENV_FILE` file), then fully quit and reopen Claude Desktop.
|
|
330
343
|
- `AI_PROVIDER` and API key — optional, enables AI-powered analysis of scan results
|
|
331
344
|
|
|
332
345
|
#### `NSA_ENV_FILE` — point the MCP server at an environment file
|
|
@@ -462,15 +475,20 @@ nsauditor-ai --version (or -v, or `version`)
|
|
|
462
475
|
| `--out <dir>` | Custom output directory — applies to the per-scan folder *and* to alternate-format files (SARIF/CSV/Markdown) | `out/` |
|
|
463
476
|
| `--parallel <n>` | Concurrent host scans | `1` |
|
|
464
477
|
| `--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 | — |
|
|
478
|
+
| `--fail-on <sev>` | Exit code 1 if any finding is ≥ severity (`critical\|high\|medium\|low\|info`). A flag the CLI refuses, or a scan target its address guard refuses, exits 2 with one `[ERROR]` line, so exit 1 stays this verdict. 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
479
|
| `--insecure-https` | Accept self-signed TLS certificates | `false` |
|
|
467
480
|
| `--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
481
|
| `--interval <min>` | Rescan interval in minutes (requires `--watch`) | `60` |
|
|
469
482
|
| `--webhook-url <url>` | Webhook URL for watch-mode alerts (see `--watch`) | — |
|
|
470
|
-
| `--alert-severity <sev>` | Minimum severity for webhook alerts | `high` |
|
|
483
|
+
| `--alert-severity <sev>` | Minimum severity for webhook alerts: `critical`, `high`, `medium`, `low` or `info`, in any case — any other value is refused | `high` |
|
|
471
484
|
| `--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` |
|
|
485
|
+
| `--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
486
|
| `--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
487
|
| `--compliance-scope <path>` | Optional JSON file describing the assessment scope (passed to the compliance engine for cover-page attestation) | — |
|
|
488
|
+
| `--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 | — |
|
|
489
|
+
| `--notify-format <fmt>` | `generic` · `slack` · `teams` (a Teams Workflows webhook, which takes an Adaptive Card) | `generic` |
|
|
490
|
+
| `--notify-severity <sev>` | Threshold for NEW violations only (`critical\|high\|medium\|low\|info`); a control going to fail or losing measurement always sends | `high` |
|
|
491
|
+
| `--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
492
|
| `--help`, `-h` | Print usage block (subcommands, flags, env vars, examples) and exit 0 | — |
|
|
475
493
|
| `--version`, `-v` | Print `nsauditor-ai <version>` and exit 0 | — |
|
|
476
494
|
|
|
@@ -553,7 +571,7 @@ HEALTHCHECK --interval=60s --timeout=5s --start-period=10s --retries=3 \
|
|
|
553
571
|
| `--format executive` | Self-contained, print-ready HTML report; no external network reference other than an `http(s)`/`mailto` `<a href>` | — |
|
|
554
572
|
| `--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
573
|
| `--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 | — |
|
|
574
|
+
| `--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
575
|
| `--brand <brand.json>` | Cover-page branding (company name, prepared-by, contact, logo) — `--format executive` only | unbranded |
|
|
558
576
|
| `--out <path>` | Write to this path instead of `report_<runId>.<ext>` beside the run record | `report_<runId>.<ext>` |
|
|
559
577
|
| `--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 +695,7 @@ No license key? Everything in this repository works perfectly without one. The C
|
|
|
677
695
|
| 024 | TCP SYN Scanner | TCP (Nmap) | SYN half-open scan via Nmap wrapper (optional) |
|
|
678
696
|
| 040 | TLS Certificate & Cipher Auditor | TCP:443+ | Cert expiry, chain integrity, hostname mismatch, weak ciphers, deprecated protocols, key strength |
|
|
679
697
|
| 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 |
|
|
698
|
+
| 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
699
|
| 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
700
|
|
|
683
701
|
### Discovery Plugins
|
|
@@ -705,7 +723,7 @@ See [Pro & Enterprise Activation](#pro--enterprise-activation) to install a lice
|
|
|
705
723
|
|
|
706
724
|
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
725
|
|
|
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.
|
|
726
|
+
**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
727
|
|
|
710
728
|
```bash
|
|
711
729
|
# Enable the scan-time push (Enterprise)
|
|
@@ -744,7 +762,7 @@ The Result Concluder (plugin 008) merges all plugin outputs into a normalized st
|
|
|
744
762
|
|
|
745
763
|
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
764
|
|
|
747
|
-
**Providers:** OpenAI (
|
|
765
|
+
**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
766
|
|
|
749
767
|
**What changes by tier is the prompt content, not the provider:**
|
|
750
768
|
|
|
@@ -752,7 +770,7 @@ NSAuditor AI supports three AI providers for vulnerability analysis. **All provi
|
|
|
752
770
|
- **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
771
|
- **Enterprise** — Pro prompts + compliance context
|
|
754
772
|
|
|
755
|
-
**Redaction:** On by default (`OPENAI_REDACT=false` turns it off)
|
|
773
|
+
**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
774
|
|
|
757
775
|
```ini
|
|
758
776
|
# .env
|
|
@@ -786,7 +804,8 @@ nsauditor-ai scan --host 192.168.1.0/24 --plugins all \
|
|
|
786
804
|
- **Scheduling** with configurable intervals and concurrency control
|
|
787
805
|
- **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
806
|
- **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
|
-
- **
|
|
807
|
+
- **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
|
|
808
|
+
- **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
809
|
- **Scan history** stored in `scan_history.jsonl` in the output directory (`--out`, default `out/`; 7-day retention in CE)
|
|
791
810
|
|
|
792
811
|
---
|
|
@@ -937,7 +956,7 @@ Commonly requested plugins: RDP, VNC, SMTP/POP3/IMAP, MySQL/PostgreSQL/MSSQL/Mon
|
|
|
937
956
|
|
|
938
957
|
For the full technical architecture, see [ARCHITECTURE.md](docs/architecture.md).
|
|
939
958
|
|
|
940
|
-
**Tech stack:** Node.js
|
|
959
|
+
**Tech stack:** Node.js 24.22.1+ · ES Modules (.mjs) · OpenAI + Anthropic SDKs · Node.js built-in test runner · MCP stdio transport
|
|
941
960
|
|
|
942
961
|
**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
962
|
|
package/bin/nsauditor-ai.mjs
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
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));
|