nsauditor-ai 0.2.55 → 0.2.56
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 +71 -58
- package/cli.mjs +49 -42
- package/mcp_server.mjs +15 -2
- package/package.json +1 -1
- package/plugins/mcp_scanner.mjs +2 -1
- package/plugins/result_concluder.mjs +3 -2
- package/utils/ee_load.mjs +45 -0
- package/utils/executive_report.mjs +16 -3
- package/utils/output_dir.mjs +68 -0
- package/utils/report_inputs.mjs +64 -1
- package/utils/report_md.mjs +16 -1
- package/utils/scan_delta.mjs +359 -38
- package/utils/scan_delta_view.mjs +5 -3
package/README.md
CHANGED
|
@@ -7,31 +7,35 @@ A modular, AI-assisted network security audit platform that scans, understands,
|
|
|
7
7
|
[](https://www.npmjs.com/package/nsauditor-ai)
|
|
8
8
|
[](LICENSE)
|
|
9
9
|
[](https://nodejs.org)
|
|
10
|
-
[](#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 (in Enterprise 1.2.0 that gap reaches no compliance control, so a control only the missing CVE rows would fail can read PASS).
|
|
17
17
|
|
|
18
18
|
## What's New
|
|
19
19
|
|
|
20
|
-
**Latest: CE 0.2.
|
|
21
|
-
`
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
20
|
+
**Latest: CE 0.2.56 + Enterprise 1.2.0 — *the delta refuses five more ways a finding could read as fixed without being fixed*.**
|
|
21
|
+
The Pro/Enterprise `report --since` delta (introduced in CE 0.2.55) now refuses five more ways a
|
|
22
|
+
finding could read as fixed without being fixed: a UDP service that did not answer in the
|
|
23
|
+
other run (`port-not-measured`, listed as `<port>/udp`), a CVE row whose lookup failed there, a
|
|
24
|
+
finding of an analysis agent that did not run, and — new in this release — **an Enterprise package
|
|
25
|
+
that failed to load**, which used to leave its plugins running while the scan skipped its intelligence,
|
|
26
|
+
analysis-agent and compliance stages without a word. It is now
|
|
27
|
+
named on stderr and recorded on the scan's conclusion, and the delta refuses that host's agent and
|
|
28
|
+
CVE-mapper rows as `evidence-gap`. And a CVE row that vanished while the same program and version
|
|
29
|
+
still answer is `vulnerability-data-changed` — the vulnerability data moved, not the estate.
|
|
30
|
+
**Not refused in this release:** when the two scans ran different `--plugins`, a row that an analysis agent or
|
|
31
|
+
the CVE mapper derived from the output of a plugin one scan did not request can read resolved (and in Enterprise,
|
|
32
|
+
with SLA tracking on, MTTR counts it closed and the control it failed can read PASS) or new — keep `--plugins`
|
|
33
|
+
identical between compared scans. Every
|
|
34
|
+
reason's detail names its run absolutely ("this run", "the baseline run"), and two scans of one host
|
|
35
|
+
whose plugin runs finished in the same second get distinct directories. Plugin counts UNCHANGED at 27 Community + 29 Enterprise; every coverage matrix UNCHANGED; **Enterprise 1.2.0
|
|
36
|
+
requires this release** (`nsauditor-ai >= 0.2.56`). The free last-vs-current webhook alerting delta
|
|
37
|
+
is untouched and stays free; in this release it does not fire on a service, version or finding change (see
|
|
38
|
+
Continuous Monitoring below).
|
|
35
39
|
|
|
36
40
|
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
41
|
|
|
@@ -45,8 +49,8 @@ Scan → Analyze → Prioritize → Track → Act
|
|
|
45
49
|
- **Smart result fusion** — the Result Concluder merges all plugin outputs into a normalized view with OS detection, service fingerprinting, and evidence linking
|
|
46
50
|
- **Structured finding format** — all findings use a common schema with category, severity, evidence, and remediation — enabling consistent SARIF export and MCP integration
|
|
47
51
|
- **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
|
|
49
|
-
- **Continuous monitoring (CTEM)** — watch mode rescans on a schedule
|
|
52
|
+
- **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
|
|
53
|
+
- **Continuous monitoring (CTEM)** — watch mode rescans on a schedule and compares each host's scan with its previous one; in this release its webhook alert does not fire on a service, version or finding change
|
|
50
54
|
- **MCP integration** — expose scanning tools to AI assistants like Claude Code via Model Context Protocol
|
|
51
55
|
- **CI/CD ready** — SARIF output with `--fail-on` severity gating for pipeline integration
|
|
52
56
|
|
|
@@ -184,10 +188,11 @@ Expose scanning capabilities to AI assistants via [Model Context Protocol](https
|
|
|
184
188
|
|
|
185
189
|
```bash
|
|
186
190
|
nsauditor-ai-mcp
|
|
187
|
-
# or
|
|
188
|
-
npx nsauditor-ai-mcp
|
|
189
191
|
```
|
|
190
192
|
|
|
193
|
+
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
|
|
194
|
+
`nsauditor-ai mcp install-key` prints (Claude Desktop Setup, below).
|
|
195
|
+
|
|
191
196
|
The server registers **seven** tools and lists all seven to every client. The licence is
|
|
192
197
|
checked when a tool is CALLED, not when the list is served — so an unlicensed call returns
|
|
193
198
|
an explicit 🔒 refusal naming the tier it needs, never an empty result that reads as "nothing
|
|
@@ -197,7 +202,7 @@ found". A higher tier does not add tools; it unlocks the ones already in the lis
|
|
|
197
202
|
|
|
198
203
|
| Tool | Purpose |
|
|
199
204
|
|---|---|
|
|
200
|
-
| `scan_host` | Run a full plugin scan against a host — service detection, OS fingerprint,
|
|
205
|
+
| `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). It does NOT look up CVEs, run the analysis agents, or return the HTTP probe's dangerous methods (006), SMB null sessions (014), the TLS-certificate / debug-endpoint / DNS-security auditors' findings (040 / 050 / 060) or Enterprise's zero-trust assessment (1023), 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
206
|
| `list_plugins` | List available scanner plugins with their IDs, priorities and requirements |
|
|
202
207
|
| `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
208
|
|
|
@@ -216,20 +221,19 @@ found". A higher tier does not add tools; it unlocks the ones already in the lis
|
|
|
216
221
|
| `get_findings` | Drill into the findings of the most recent `scan_cloud` run (per-provider session cache — not live state) |
|
|
217
222
|
|
|
218
223
|
> `scan_cloud` runs the requested clouds' plugins **concurrently** (default up to 20 at once, 25s per-plugin
|
|
219
|
-
> timeout
|
|
224
|
+
> timeout; a plugin that runs out is listed as `timeout` in the result's `manifest`, i.e. not measured). Tune with
|
|
220
225
|
> `CLOUD_SCAN_CONCURRENCY` (default 20) and `CLOUD_PLUGIN_TIMEOUT_MS` (default 25000) in the server env. The
|
|
221
226
|
> network `PLUGIN_TIMEOUT_MS` governs `scan_host` and `probe_service`, and binds every plugin there — including
|
|
222
227
|
> one that declares a longer budget of its own. Read the result's **`findingsSummary`**
|
|
223
228
|
> (per-provider severity counts + a CRITICAL/HIGH list) for the findings; `audited:false` / `notes` / `pluginsRan:0`
|
|
224
229
|
> still mean a cloud was NOT audited (never a clean pass). Pass `providers:["aws"]` to audit only the cloud named.
|
|
225
230
|
|
|
226
|
-
> **
|
|
227
|
-
>
|
|
228
|
-
>
|
|
229
|
-
>
|
|
230
|
-
>
|
|
231
|
-
>
|
|
232
|
-
> which has no MCP tool-call cap — there you can raise `PLUGIN_TIMEOUT_MS` (e.g. `90000`) freely. On the CLI a
|
|
231
|
+
> **All-region AWS coverage.** The server does not split a `regions:["all"]` call into batches; the agent skill
|
|
232
|
+
> (`nsauditor-ai-agent-skill`) teaches the assistant to cover the enabled regions in batches of 3–5 region codes per
|
|
233
|
+
> call. `CLOUD_PLUGIN_TIMEOUT_MS` (default `25000`) bounds each plugin, not the call: raising it can give a slow
|
|
234
|
+
> plugin more time and lengthen the call. A call that times out in the client returned no result — never read it as
|
|
235
|
+
> a clean account. For unbounded multi-region scans use the **CLI** (`nsauditor-ai scan … --aws-region all`), which
|
|
236
|
+
> has no MCP client in the path — there you can raise `PLUGIN_TIMEOUT_MS` (e.g. `90000`) freely. On the CLI a
|
|
233
237
|
> plugin that declares its own budget already gets it; `PLUGIN_TIMEOUT_CEILING_MS` caps those.
|
|
234
238
|
|
|
235
239
|
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.
|
|
@@ -292,13 +296,16 @@ npm install -g nsauditor-ai
|
|
|
292
296
|
nsauditor-ai mcp install-key # required before MCP server will start
|
|
293
297
|
```
|
|
294
298
|
|
|
295
|
-
Then
|
|
299
|
+
Then paste the `mcpServers` block that `install-key` prints into your `claude_desktop_config.json` (Settings →
|
|
300
|
+
Developer → Edit Config). It names node and the server script by absolute path, because Claude Desktop may not see
|
|
301
|
+
the directory your shell finds `nsauditor-ai-mcp` in. Add any other variables to its `env` block, for example:
|
|
296
302
|
|
|
297
303
|
```json
|
|
298
304
|
{
|
|
299
305
|
"mcpServers": {
|
|
300
306
|
"nsauditor-ai": {
|
|
301
|
-
"command": "nsauditor-ai-
|
|
307
|
+
"command": "<from: nsauditor-ai mcp install-key — the absolute path to node>",
|
|
308
|
+
"args": ["<from: nsauditor-ai mcp install-key — the absolute path to bin/nsauditor-ai-mcp.mjs>"],
|
|
302
309
|
"env": {
|
|
303
310
|
"NSA_MCP_AUTH_KEY": "keychain:NSA_MCP_AUTH_KEY",
|
|
304
311
|
"NSA_ENV_FILE": "~/envs/prod-aws.env",
|
|
@@ -313,11 +320,14 @@ Then add this to your `claude_desktop_config.json` (Settings → Developer → E
|
|
|
313
320
|
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
321
|
|
|
315
322
|
- `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
|
-
|
|
323
|
+
- `NSA_ALLOW_ALL_HOSTS=1` — required to scan private/RFC 1918 addresses (e.g., `192.168.x.x`). It turns off the MCP
|
|
324
|
+
server's check of what a host name resolves to, so a name that resolves to a loopback or cloud-metadata address then gets
|
|
325
|
+
through too; targets written as `localhost`, `127.x`, `::1`, `169.254.x` or `metadata.google…` stay refused
|
|
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). 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 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
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.
|
|
319
329
|
- `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`).
|
|
330
|
+
- `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
331
|
- `AI_PROVIDER` and API key — optional, enables AI-powered analysis of scan results
|
|
322
332
|
|
|
323
333
|
#### `NSA_ENV_FILE` — point the MCP server at an environment file
|
|
@@ -352,7 +362,7 @@ PLUGIN_TIMEOUT_MS=5000
|
|
|
352
362
|
nsauditor-ai mcp install-key # required before MCP server will start
|
|
353
363
|
claude mcp add nsauditor-ai \
|
|
354
364
|
--env NSA_MCP_AUTH_KEY=keychain:NSA_MCP_AUTH_KEY \
|
|
355
|
-
--
|
|
365
|
+
-- nsauditor-ai-mcp
|
|
356
366
|
```
|
|
357
367
|
|
|
358
368
|
To target an environment via the file, add it as an env value:
|
|
@@ -361,7 +371,7 @@ To target an environment via the file, add it as an env value:
|
|
|
361
371
|
claude mcp add nsauditor-ai \
|
|
362
372
|
--env NSA_MCP_AUTH_KEY=keychain:NSA_MCP_AUTH_KEY \
|
|
363
373
|
--env NSA_ENV_FILE=~/envs/prod-aws.env \
|
|
364
|
-
--
|
|
374
|
+
-- nsauditor-ai-mcp
|
|
365
375
|
```
|
|
366
376
|
|
|
367
377
|
(On Linux/Windows, replace the `keychain:NSA_MCP_AUTH_KEY` placeholder with the literal key printed by `install-key`.)
|
|
@@ -436,7 +446,7 @@ nsauditor-ai validate
|
|
|
436
446
|
nsauditor-ai feed bundle --from <dir-of-NVD-feeds> --out <bundle.json.gz> [--kev <f>] [--epss <f>]
|
|
437
447
|
nsauditor-ai feed import --file <feed-or-bundle> [--cache-dir <d>] [--extras-dir <d>] [--append] # the feeds you downloaded
|
|
438
448
|
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)
|
|
449
|
+
nsauditor-ai report --from <dir> --format executive|jira [--run <id>] [--since <runId|prior>] [--brand <brand.json>] [--out <path>] [--allow-partial] (Pro/Enterprise)
|
|
440
450
|
nsauditor-ai mcp
|
|
441
451
|
nsauditor-ai --help (or -h, or `help`)
|
|
442
452
|
nsauditor-ai --version (or -v, or `version`)
|
|
@@ -449,15 +459,15 @@ nsauditor-ai --version (or -v, or `version`)
|
|
|
449
459
|
| `--host <target>` | Target: IP, hostname, CIDR, dash range. Aliases: `--ip`, `--target` | *required*\* |
|
|
450
460
|
| `--host-file <path>` | File with one host per line (`#` comments, blank lines OK) | — |
|
|
451
461
|
| `--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
|
|
462
|
+
| `--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
463
|
| `--out <dir>` | Custom output directory — applies to the per-scan folder *and* to alternate-format files (SARIF/CSV/Markdown) | `out/` |
|
|
454
464
|
| `--parallel <n>` | Concurrent host scans | `1` |
|
|
455
465
|
| `--output-format <fmt>` | Additional output format: `sarif` (CI/CD) · `csv` (spreadsheet) · `md` or `markdown` (chat/PR/Slack quotable) | — |
|
|
456
|
-
| `--fail-on <sev>` | Exit code
|
|
466
|
+
| `--fail-on <sev>` | Exit code 1 if a flag it gates on is ≥ severity (`critical\|high\|medium\|low\|info`): anonymous FTP login and DNS zone transfer are critical, weak SSH algorithms medium, and any concluded scan counts as info — so `--fail-on info` fails every scan that concludes. Anonymous FTP login and zone transfer are tested only when `FTP_CHECK_ANON=true` / `DNS_CHECK_AXFR=true` with `DNS_AXFR_DOMAIN` are set, so on a default scan `--fail-on high` and `--fail-on critical` never exit 1. It also reads dangerous HTTP methods (medium), but no scan's conclusion carries that flag today. It does not read the SNMP default community, weak TLS protocols / ciphers, a self-signed certificate, the MCP server checks, 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
467
|
| `--insecure-https` | Accept self-signed TLS certificates | `false` |
|
|
458
|
-
| `--watch` | CTEM continuous **alerting** loop — re-scan on `--interval`,
|
|
468
|
+
| `--watch` | CTEM continuous **alerting** loop — re-scan on `--interval`, compare each host with its previous scan (`[ScanHistory]` lines), webhook on `--alert-severity` — but not on a service, version or finding change in this release (see Continuous Monitoring). 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` |
|
|
459
469
|
| `--interval <min>` | Rescan interval in minutes (requires `--watch`) | `60` |
|
|
460
|
-
| `--webhook-url <url>` | Webhook URL for
|
|
470
|
+
| `--webhook-url <url>` | Webhook URL for watch-mode alerts (see `--watch`) | — |
|
|
461
471
|
| `--alert-severity <sev>` | Minimum severity for webhook alerts | `high` |
|
|
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) | — |
|
|
@@ -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
|
|
502
|
+
# SARIF output for CI/CD. --fail-on high exits 1 only on anonymous FTP / zone transfer, which are tested only with
|
|
503
|
+
# FTP_CHECK_ANON / DNS_CHECK_AXFR set — 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. 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
|
-
|
|
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,
|
|
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 (
|
|
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:**
|
|
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
|
-
- **
|
|
775
|
-
- **Webhook alerts** — JSON POST
|
|
776
|
-
- **SSRF protection** — private, loopback, and cloud metadata addresses blocked at the scan entry point and inside `sendWebhook()`.
|
|
777
|
-
- **Scan history** stored in
|
|
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, and the findings delta
|
|
788
|
+
- **Webhook alerts** — a JSON POST, retried up to twice 1 s apart on a 5xx or network error, not on a 4xx. ⚠️ **In this release the webhook does NOT fire on a service, version or finding change:** the cycle comparison it is gated on finds no services in the scan results it is handed, so its `=== Delta Report ===` shows no change for any host. The gate opens only when a cycle scanned a different set of hosts from the cycle before (in practice, one cut short by stopping the loop); an alert is then posted for each host whose services carry a flag at or above `--alert-severity` (at `info`, every service counts). Read changes from the `[ScanHistory]` lines.
|
|
789
|
+
- **SSRF protection** — private, loopback, and cloud metadata addresses blocked at the scan entry point and inside `sendWebhook()`. `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 check of what a host name resolves to — 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; in Enterprise 1.2.0 that gap reaches no compliance control, so a control only the missing CVE rows would fail can read PASS), 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
|
|
873
|
+
Run the full suite:
|
|
861
874
|
|
|
862
875
|
```bash
|
|
863
876
|
npm test
|
|
@@ -888,7 +901,7 @@ 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.
|
|
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 |
|
|
893
906
|
|
|
894
907
|
---
|
|
@@ -935,9 +948,9 @@ NSAuditor AI is built on a **Zero Data Exfiltration (ZDE)** architecture:
|
|
|
935
948
|
|
|
936
949
|
- **No telemetry.** No analytics. No usage tracking. No phone-home.
|
|
937
950
|
- **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.
|
|
951
|
+
- **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
952
|
- **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).
|
|
953
|
+
- **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 (in Enterprise 1.2.0 that gap reaches no compliance control, so a control only the missing CVE rows would fail can read PASS). 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
954
|
|
|
942
955
|
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
956
|
|
package/cli.mjs
CHANGED
|
@@ -28,7 +28,8 @@ import { scrubByKey } from './utils/redact.mjs';
|
|
|
28
28
|
import { isBlockedIp, resolveAndValidate } from './utils/net_validation.mjs';
|
|
29
29
|
import { getAllTechniques } from './utils/attack_map.mjs';
|
|
30
30
|
import { TOOL_VERSION } from './utils/tool_version.mjs';
|
|
31
|
-
import { resolveBaseOutDir } from './utils/output_dir.mjs';
|
|
31
|
+
import { resolveBaseOutDir, createHostOutDir, safeHost } from './utils/output_dir.mjs';
|
|
32
|
+
import { loadEnterprise } from './utils/ee_load.mjs';
|
|
32
33
|
import { deriveFindingsCount } from './utils/report_inputs.mjs';
|
|
33
34
|
import { toCleanPath } from './utils/path_helpers.mjs';
|
|
34
35
|
import { newRunId, writeRunStart, appendHostWritten, finalizeRunRecord, pruneRunRecordsForCE, aggregateNvdCache, abortReasonOf } from './utils/run_record.mjs';
|
|
@@ -45,19 +46,7 @@ const parseBool = (val, def = false) => {
|
|
|
45
46
|
if (!s && def != null) return !!def;
|
|
46
47
|
return ['true', '1', 'yes', 'on', 'y'].includes(s);
|
|
47
48
|
};
|
|
48
|
-
|
|
49
|
-
const d = new Date();
|
|
50
|
-
const pad = (n) => String(n).padStart(2, '0');
|
|
51
|
-
return (
|
|
52
|
-
d.getFullYear().toString() +
|
|
53
|
-
pad(d.getMonth() + 1) +
|
|
54
|
-
pad(d.getDate()) + '_' +
|
|
55
|
-
pad(d.getHours()) +
|
|
56
|
-
pad(d.getMinutes()) +
|
|
57
|
-
pad(d.getSeconds())
|
|
58
|
-
);
|
|
59
|
-
};
|
|
60
|
-
const safeHost = (h) => String(h ?? 'unknown').replace(/[\/\\?%*:|"<>]/g, '_');
|
|
49
|
+
// `nowStamp` and `safeHost` live in utils/output_dir.mjs beside `createHostOutDir`, so the directory name has one definition.
|
|
61
50
|
// toCleanPath imported from ./utils/path_helpers.mjs (consolidated in v0.1.20)
|
|
62
51
|
|
|
63
52
|
/**
|
|
@@ -157,17 +146,12 @@ async function maybeSendToOpenAI({ host, results, conclusion, promptMode = 'basi
|
|
|
157
146
|
: await resolveSecret(process.env.OPENAI_API_KEY);
|
|
158
147
|
const key = keyRaw ? String(keyRaw).trim() : null;
|
|
159
148
|
|
|
160
|
-
// Per-scan folder.
|
|
161
|
-
//
|
|
162
|
-
//
|
|
163
|
-
|
|
164
|
-
if (!outDir)
|
|
165
|
-
|
|
166
|
-
await fsp.mkdir(baseOutDir, { recursive: true });
|
|
167
|
-
const ts = nowStamp();
|
|
168
|
-
const runDir = `${safeHost(host)}_${ts}`;
|
|
169
|
-
outDir = path.join(baseOutDir, runDir);
|
|
170
|
-
}
|
|
149
|
+
// Per-scan folder — REQUIRED. Its only caller, scanSingleHost, creates it exclusively (createHostOutDir) so that EE
|
|
150
|
+
// enrichment, compliance artifacts and the AI outputs share one folder. ⚠️ The fallback that used to compose a
|
|
151
|
+
// directory here (`${safeHost(host)}_${nowStamp()}`, recursive mkdir) had no caller and was the same-second
|
|
152
|
+
// collision's shape kept warm (1.1.1, audit seat's fold); it is gone, and a missing outDir is an error.
|
|
153
|
+
if (!presetOutDir) throw new Error('maybeSendToOpenAI: outDir is required — scanSingleHost creates it with createHostOutDir');
|
|
154
|
+
const outDir = presetOutDir;
|
|
171
155
|
await fsp.mkdir(outDir, { recursive: true });
|
|
172
156
|
|
|
173
157
|
// Paths (fixed names inside per-scan folder)
|
|
@@ -930,11 +914,11 @@ async function scanSingleHost(pm, host, plugins, opts, promptMode) {
|
|
|
930
914
|
// Pre-compute the per-scan output folder so EE enrichment, compliance
|
|
931
915
|
// artifacts, and AI outputs all land in the same directory. maybeSendToOpenAI
|
|
932
916
|
// will reuse this presetOutDir below.
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
await
|
|
917
|
+
//
|
|
918
|
+
// ⚠️ AN EXCLUSIVE CREATE (1.1.1 board): two scans of one host reaching this line in the same second used to share
|
|
919
|
+
// one directory and the later one overwrote the earlier one's evidence — see createHostOutDir. `opts.nowStamp` is
|
|
920
|
+
// a test seam (testHooks.nowStamp), absent in every real invocation.
|
|
921
|
+
const outDir = await createHostOutDir(resolveBaseOutDir(), host, opts?.nowStamp ? { stamp: opts.nowStamp } : {});
|
|
938
922
|
|
|
939
923
|
// EE enrichment hook — no-op if @nsasoft/nsauditor-ai-ee is not installed
|
|
940
924
|
// or the license tier doesn't grant intelligenceEngine. Compliance + outDir
|
|
@@ -971,11 +955,20 @@ async function scanSingleHost(pm, host, plugins, opts, promptMode) {
|
|
|
971
955
|
let epssDataAsOf = null;
|
|
972
956
|
// Enterprise's report of WHERE the NVD store/cache it read lives (board item 23) — null without EE.
|
|
973
957
|
let nvdCache = null;
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
958
|
+
// ⚠️ "NOT INSTALLED" AND "INSTALLED BUT BROKEN" ARE TWO EVENTS (1.1.1, the audit seat's T1-c ruling). This used to be
|
|
959
|
+
// a bare `catch {}` read as "EE not installed — the ONLY silent case", and every import failure landed in it — measured
|
|
960
|
+
// at EE 174212f: Enterprise imported names this Community did not export, so below the floor the whole EE stage
|
|
961
|
+
// vanished without a word and the Pro delta read its rows RESOLVED. The package is RESOLVED first (utils/ee_load.mjs):
|
|
962
|
+
// absence stays silent; a failed load is named on stderr and recorded on the conclusion, beside `eeEnrichmentError`.
|
|
963
|
+
// `opts.importEE` / `opts.resolveEE`: test-only seams — absent in every real invocation.
|
|
964
|
+
const eeLoad = await loadEnterprise({ importEE: opts?.importEE ?? null, resolveEE: opts?.resolveEE ?? null });
|
|
965
|
+
ee = eeLoad.ee;
|
|
966
|
+
if (eeLoad.loadError) {
|
|
967
|
+
console.error(`[EE] Enterprise is installed but FAILED TO LOAD — this scan has no compliance report, no `
|
|
968
|
+
+ `intelligence enrichment and no analysis-agent findings: ${eeLoad.loadError}`);
|
|
969
|
+
conclusion.result = conclusion.result || {};
|
|
970
|
+
conclusion.result.eeLoadError = eeLoad.loadError;
|
|
971
|
+
}
|
|
979
972
|
// ⚠️ HOISTED DELIBERATELY, and only this value. `eeEnrichment` is `const` INSIDE the try below,
|
|
980
973
|
// so reading it at the `appendHostWritten` call ~100 lines on is a ReferenceError — which the
|
|
981
974
|
// suite caught as 16 failures rather than one, because every history comparison swallowed it as
|
|
@@ -1276,7 +1269,7 @@ async function readSecretFromStdin(keyName) {
|
|
|
1276
1269
|
});
|
|
1277
1270
|
}
|
|
1278
1271
|
|
|
1279
|
-
function maxSeverityInConclusion(conclusion) {
|
|
1272
|
+
export function maxSeverityInConclusion(conclusion) {
|
|
1280
1273
|
const services = conclusion?.result?.services || [];
|
|
1281
1274
|
let max = 0;
|
|
1282
1275
|
|
|
@@ -1398,7 +1391,9 @@ export async function preflightGrcIfRequested(env, opts = {}) {
|
|
|
1398
1391
|
// test drive the real scan pipeline (including this function's own run-record wiring) against
|
|
1399
1392
|
// a controlled, injected EE module — e.g. one whose `enrichScan` reports a specific KEV/EPSS
|
|
1400
1393
|
// `dataAsOf` — without depending on the real `@nsasoft/nsauditor-ai-ee` package, a real store
|
|
1401
|
-
// file, or the machine's own license/tier state being any particular thing.
|
|
1394
|
+
// file, or the machine's own license/tier state being any particular thing. An injected `importEE` counts as an
|
|
1395
|
+
// INSTALLED package (utils/ee_load.mjs): to describe one that is NOT installed, pass `testHooks.resolveEE` throwing —
|
|
1396
|
+
// a throwing `importEE` alone is now a package that is installed and FAILED to load, which the scan reports.
|
|
1402
1397
|
// Reconstructs a single tier word ('ce' | 'pro' | 'enterprise') from the resolved capability
|
|
1403
1398
|
// map — never from getTierFromEnv()'s own global cache. `runReport` below deliberately never
|
|
1404
1399
|
// reads that cache: `caps` is passed in as an argument specifically so Pro is reachable from the
|
|
@@ -1682,22 +1677,30 @@ Scan options:
|
|
|
1682
1677
|
incomplete-coverage notice); CloudTrail trail discovery,
|
|
1683
1678
|
GuardDuty/Inspector and EC2 instances attempt every region.
|
|
1684
1679
|
--plugins <list|all> Plugins to run (e.g. 001,003,020 or "all"; default: all)
|
|
1685
|
-
--ports <
|
|
1680
|
+
--ports <list> Ports ADDED to the default set (e.g. 22,80,443 or 161/udp;
|
|
1681
|
+
a range such as 1-1000 is not parsed and adds nothing)
|
|
1686
1682
|
--out <dir> Output directory for scan artifacts
|
|
1687
1683
|
--parallel <n> Parallel host concurrency (default 1)
|
|
1688
|
-
--fail-on <severity> Exit
|
|
1684
|
+
--fail-on <severity> Exit 1 if a gated flag ≥ severity: anonymous FTP / zone
|
|
1685
|
+
transfer (critical; tested only with FTP_CHECK_ANON /
|
|
1686
|
+
DNS_CHECK_AXFR set), weak SSH algorithms (medium); any
|
|
1687
|
+
concluded scan is info. Dangerous HTTP methods are read
|
|
1688
|
+
but never reach the conclusion today. Not SNMP community,
|
|
1689
|
+
weak TLS, MCP checks, CVEs or agent findings — exit 0 is
|
|
1690
|
+
not a clean host. Exit 2: unknown severity or no conclusion.
|
|
1689
1691
|
--output-format <fmt> Additional report format: sarif | csv | md
|
|
1690
1692
|
--insecure-https Skip TLS validation on probed HTTPS targets
|
|
1691
1693
|
--watch CTEM continuous ALERTING mode: re-scan on --interval,
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
+
compare each host with its previous scan ([ScanHistory]
|
|
1695
|
+
lines); --webhook-url does NOT fire on a service, version or
|
|
1696
|
+
finding change in this release. NOT an evidence cadence —
|
|
1694
1697
|
it adds no retention or cross-run aggregation, skips SARIF/
|
|
1695
1698
|
CSV/Markdown output and --fail-on, and dies with this process.
|
|
1696
1699
|
Each tick is an ordinary scan, so with --compliance it writes
|
|
1697
1700
|
that tick's artifacts; nothing relates them across ticks. For
|
|
1698
1701
|
SOC 2 Type II history use a scheduler (cron/systemd/CI).
|
|
1699
1702
|
--interval <minutes> Watch interval (default 60)
|
|
1700
|
-
--webhook-url <url> Send
|
|
1703
|
+
--webhook-url <url> Send --watch alerts (must be public; private/loopback blocked)
|
|
1701
1704
|
--alert-severity <sev> Min severity to alert on (default: high)
|
|
1702
1705
|
--compliance <framework> Map findings to controls. 'all' = all 8 frameworks, or a CSV of
|
|
1703
1706
|
soc2,hipaa,nist-csf,pci-dss,iso-27001,cis-v8,gdpr,nist-800-171
|
|
@@ -3156,6 +3159,10 @@ Docs: https://www.nsauditor.com/ai/ | Pricing: https://www.nsauditor.com/ai/
|
|
|
3156
3159
|
|
|
3157
3160
|
const opts = { insecureHttps };
|
|
3158
3161
|
if (testHooks.importEE) opts.importEE = testHooks.importEE;
|
|
3162
|
+
if (testHooks.resolveEE) opts.resolveEE = testHooks.resolveEE;
|
|
3163
|
+
// `testHooks.nowStamp`: a test seam, absent in every real invocation — it pins the per-host directory's second so a
|
|
3164
|
+
// leg can drive the same-second collision through the shipped scan path.
|
|
3165
|
+
if (testHooks.nowStamp) opts.nowStamp = testHooks.nowStamp;
|
|
3159
3166
|
if (ports) opts.ports = ports;
|
|
3160
3167
|
if (compliance) opts.compliance = compliance;
|
|
3161
3168
|
if (complianceScope) opts.complianceScope = complianceScope;
|
package/mcp_server.mjs
CHANGED
|
@@ -244,7 +244,20 @@ export const TOOLS = [
|
|
|
244
244
|
{
|
|
245
245
|
name: 'scan_host',
|
|
246
246
|
description:
|
|
247
|
-
'Run a full plugin scan on a target host and return structured results
|
|
247
|
+
'Run a full plugin scan on a target host and return structured results: service detection, OS fingerprinting, and'
|
|
248
|
+
+ ' the flags the service checks leave on each service record — weak SSH algorithms, SNMP default community, weak '
|
|
249
|
+
+ 'TLS protocols / ciphers and a self-signed certificate, the MCP server checks, and anonymous FTP login and DNS '
|
|
250
|
+
+ 'zone transfer only when the server\'s environment enables those two checks (FTP_CHECK_ANON; DNS_CHECK_AXFR with '
|
|
251
|
+
+ 'DNS_AXFR_DOMAIN — both off by default). It runs, when their requirements are met, but does NOT return the '
|
|
252
|
+
+ 'results of the HTTP probe (006, dangerous HTTP methods), the NetBIOS/SMB scanner (014, null sessions and '
|
|
253
|
+
+ 'shares), the TLS-certificate (040), debug-endpoint (050) and DNS-security (060) auditors — use probe_service '
|
|
254
|
+
+ '(Pro) with that plugin — or, with the Enterprise package, its zero-trust assessment (1023). It does NOT look up '
|
|
255
|
+
+ 'CVEs and does NOT run the Enterprise analysis agents or exploit intelligence — so zero findings here is NOT a '
|
|
256
|
+
+ 'statement that the host has no known vulnerabilities. For CVEs, call get_vulnerabilities (Pro) with each '
|
|
257
|
+
+ 'service\'s cpe where it names a concrete version; a service whose cpe is null gets no lookup, so its CVE coverage'
|
|
258
|
+
+ ' is unknown, not clean. With the Enterprise package and a Pro or Enterprise licence, the CLI scan (nsauditor-ai '
|
|
259
|
+
+ 'scan --host <host>) runs the CVE mapper and the analysis agents, and joins exploit intelligence when a KEV / '
|
|
260
|
+
+ 'EPSS store is configured.',
|
|
248
261
|
inputSchema: {
|
|
249
262
|
type: 'object',
|
|
250
263
|
properties: {
|
|
@@ -335,7 +348,7 @@ export const TOOLS = [
|
|
|
335
348
|
},
|
|
336
349
|
pluginName: {
|
|
337
350
|
type: 'string',
|
|
338
|
-
description: 'Plugin
|
|
351
|
+
description: 'Plugin ID (e.g. "002") or its full name as list_plugins returns it, in any case (e.g. "SSH Scanner")',
|
|
339
352
|
},
|
|
340
353
|
},
|
|
341
354
|
required: ['host', 'port', 'pluginName'],
|
package/package.json
CHANGED
package/plugins/mcp_scanner.mjs
CHANGED
|
@@ -454,7 +454,8 @@ export function conclude({ result }) {
|
|
|
454
454
|
detection.tools.length ? `tools=${detection.tools.length}` : null,
|
|
455
455
|
].filter(Boolean).join(' '),
|
|
456
456
|
authoritative: true,
|
|
457
|
-
// Security flags
|
|
457
|
+
// Security flags. scan_host returns them on this record; the Markdown report, SARIF, CSV and --fail-on do NOT read
|
|
458
|
+
// them yet (1.2.0 build 3 states it; boarded for 1.2.1).
|
|
458
459
|
...flags,
|
|
459
460
|
// Evidence fields per N.5 FindingSchema
|
|
460
461
|
evidence: { cwe, owasp, mitre },
|
|
@@ -8,8 +8,9 @@ function pickResultsFromArgs(args) {
|
|
|
8
8
|
return [];
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
// Stable slug from plugin name, falling back to IDs
|
|
12
|
-
|
|
11
|
+
// Stable slug from plugin name, falling back to IDs. Exported so tests/concluder_drops_honesty.test.mjs derives the
|
|
12
|
+
// adapter census with THIS rule rather than a copy of it.
|
|
13
|
+
export function slugify(name, id) {
|
|
13
14
|
const base = String(name || '').toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '');
|
|
14
15
|
if (base) return base;
|
|
15
16
|
const map = { '001':'ping_checker','002':'ssh_scanner','003':'port_scanner','004':'ftp_banner_check','005':'host_up_check','006':'http_probe','007':'snmp_scanner','009':'dns_scanner','010':'webapp_detector','011':'tls_scanner','012':'opensearch_scanner','013':'os_detector','014':'netbios__smb_scanner','015':'sunrpc_scanner','024':'syn_scanner','025':'db_scanner','026':'arp_scanner','027':'mdns_scanner','028':'upnp_scanner' };
|