nsauditor-ai 0.2.40 → 0.2.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -17,11 +17,15 @@ NSAuditor AI is the open-source core of a privacy-first security intelligence pl
17
17
 
18
18
  ## What's New
19
19
 
20
- **The suppression-approval commands land in the CLI (CE 0.2.40 / Enterprise 0.35.0).** `compliance suppress | review | renew | keygen` are new subcommands — thin forwards, the same discipline `compliance attest` established: Enterprise owns validation, defaults, the signing decision and every refusal; CE contributes flags and an exit code. `keygen` writes an approval keypair for a capability that is not yet proven — Ed25519, private half `0600` — and prints an identity-registry member to paste, refusing to overwrite an existing signing key — regenerating one makes every signature it ever produced unverifiable, and nothing reports that until an auditor checks an archived approval. `suppress` signs the approval it writes when `NSAUDITOR_SIGNING_KEY` names a local Ed25519 key, and a malformed key fails at the command with nothing written. `renew` warns that renewing a signed approval invalidates its signature, because the expiry and the renewal record are inside the signed payload. Ed25519 suppression signing is thereby reachable and **not yet proven** — its verification gate has not run against published bytes, so a produced signature is not yet evidence anything vouches for.
20
+ **The `feed` commands: offline CVE data can be hand-carried (CE 0.2.42 / Enterprise 0.37.0).** `nsauditor-ai feed bundle` merges the NVD feed files you downloaded on a connected host into one portable archive; `feed import` reads one into the offline store on an isolated host — delivered as a **restricted** distribution rather than a public `npm install`, and amd64 only. The implementations are Enterprise-side (**requires `@nsasoft/nsauditor-ai-ee` >= 0.37.0**); the commands, flags and usage text are here, because this is the package with the `bin`. Optional `--kev` / `--epss` carry **your own** downloads from CISA and FIRST inside the archive, and `--extras-dir` places them on import and prints the environment lines to set — **no exploit data ships with either package**. `bundle` bundles the FEEDS YOU DOWNLOADED, never "your database": the offline store is a lossy derivation of an NVD feed and cannot be turned back into one. Import now names **why** it skipped records — about a quarter of a real NVD year file is skipped by design (withdrawn CVEs, entries with no CPE data) and a bare `skipped 9353` reads as data loss; malformed records are called out separately because those are **not** expected. ⚠️ **A bundle is integrity-checked, not authenticated** — the recorded SHA-256 detects alteration in transit but cannot establish authorship, because it travels inside the archive it covers. Also here: **`--flag=value` is now refused by name** instead of being parsed as a valueless flag with the value discarded, which made `--severity=high` run a default-severity scan and report success — a wrong answer that looked like a right one; and the **mDNS module-shape fallback was a TDZ self-reference**, so it never once fell back. **The air-gap delivery claims are earned back at this release**, with two conditions that travel with them: the dependency-complete bundle is a **restricted** distribution rather than a public install, and it is **amd64** only — no arm64 image is published.
21
+
22
+ **The compliance report now verifies the signatures it renders (CE 0.2.41 / Enterprise 0.36.0).** Paired with Enterprise 0.36.0 — **no CE behaviour change; the peer floor stays `>=0.2.40`, unraised, because nothing in this cycle needs new CE code.** Enterprise-side, a registry member may now carry the approver's public key beside its fingerprint, and when it does, every suppression signature in a compliance report is cryptographically checked — the Ed25519 suppression-signing capability this exercises is **proven as of Enterprise 0.36.0**, its verification gate having run against the published bytes and passed with a tamper negative control, and verified for approvers whose registry entry carries **key material**. Verdicts name the exact bytes they verified, a revoked key's bytes are still checked so signing-after-revocation becomes a cryptographic finding, and a MISSING verdict reads "not checked" rather than "failed" — a report must not accuse an operator of forgery for not having migrated. **Ed25519 suppression signing was `not yet proven` when this shipped**: it shipped the code that made the verification gate satisfiable, and that gate then passed at Enterprise 0.36.0.
23
+
24
+ **The suppression-approval commands land in the CLI (CE 0.2.40 / Enterprise 0.35.0).** `compliance suppress | review | renew | keygen` are new subcommands — thin forwards, the same discipline `compliance attest` established: Enterprise owns validation, defaults, the signing decision and every refusal; CE contributes flags and an exit code. `keygen` writes an approval keypair for a capability that was not yet proven at that release — Ed25519, private half `0600` — and prints an identity-registry member to paste, refusing to overwrite an existing signing key — regenerating one makes every signature it ever produced unverifiable, and nothing reports that until an auditor checks an archived approval. `suppress` signs the approval it writes when `NSAUDITOR_SIGNING_KEY` names a local Ed25519 key, and a malformed key fails at the command with nothing written. `renew` warns that renewing a signed approval invalidates its signature, because the expiry and the renewal record are inside the signed payload. Ed25519 suppression signing was thereby reachable and, **at that release, not yet proven** — its verification gate had not run against published bytes. It passed at Enterprise 0.36.0.
21
25
 
22
26
  **Exploit intelligence lands in Pro, and a risk-score claim gets corrected here (CE 0.2.39 / Enterprise 0.34.0).** Paired with Enterprise 0.34.0 — **no CE behaviour change.** Enterprise findings that carry CVEs are now joined by CVE-ID against a local **CISA KEV** catalog and a local **FIRST EPSS** scores file, banded and ordered exploit-first, so a KEV-listed MEDIUM outranks an unexploited CRITICAL. Both stores are **operator-populated — no feed data ships** — and fail closed when stale, so an out-of-date catalog never reports "not exploited". The CE-side change is a correction this README owed you: *"a composite risk score (severity × exploitability × impact × exposure)"* described inputs the code never had. It computes **CVSS weighted by verification status, with an uplift for initial-access techniques**, and that is what it now says. `prioritize()` in the documented FindingQueue sorts by **severity rank alone** — the exploit-aware ordering is Enterprise-side. Community Edition scanning, plugins and output are unchanged.
23
27
 
24
- **The opt-in RFC 3161 register stops understating itself (CE 0.2.38 / Enterprise 0.33.1).** Paired with Enterprise 0.33.1 — **no CE behaviour change; wording only, and it is the wording npm freezes.** Trusted timestamping is opt-in, became operator-configurable in 0.33.0, and has since been proven end to end against a real Time-Stamp Authority twice over: on the npm path, and separately from inside the published Marketplace container image. Six places in this README still told you the container leg was unverified. They now state what is true — **RFC 3161 timestamping is opt-in via `NSAUDITOR_TSA_URL`, with no default ever and an outbound call to the authority you name** — and keep the one caveat that survives: retained images at `:0.32.11` and earlier carry no `openssl` at all, so nothing here speaks for them. **Ed25519 suppression signing was unchanged at that release and still not reachable then** — it became reachable in EE 0.35.0 via `compliance suppress` and is **not yet proven**.
28
+ **The opt-in RFC 3161 register stops understating itself (CE 0.2.38 / Enterprise 0.33.1).** Paired with Enterprise 0.33.1 — **no CE behaviour change; wording only, and it is the wording npm freezes.** Trusted timestamping is opt-in, became operator-configurable in 0.33.0, and has since been proven end to end against a real Time-Stamp Authority twice over: on the npm path, and separately from inside the published Marketplace container image. Six places in this README still told you the container leg was unverified. They now state what is true — **RFC 3161 timestamping is opt-in via `NSAUDITOR_TSA_URL`, with no default ever and an outbound call to the authority you name** — and keep the one caveat that survives: retained images at `:0.32.11` and earlier carry no `openssl` at all, so nothing here speaks for them. **Ed25519 suppression signing was unchanged at that release and still not reachable then** — it became reachable in EE 0.35.0 via `compliance suppress` and is **proven as of EE 0.36.0**, verified for approvers whose registry entry carries **key material**.
25
29
 
26
30
  See **[CHANGELOG.md](./CHANGELOG.md)** for the full per-release history.
27
31
 
@@ -62,7 +66,7 @@ If you're heading into a **SOC 2, HIPAA, NIST CSF 2.0, PCI DSS, ISO 27001, CIS C
62
66
  - **ISO/IEC 27001:2022** (ISO + IEC, Oct 2022; 2013 edition retired Oct 31, 2025) — **17 covered + 14 partial + 62 OOS across 93 Annex A controls** (the complete Annex A universe); per-Annex-A-code mapping auditor-canonical for ISO/IEC 17021-1 certification body assessors; Statement of Applicability per Clause 6.1.3.d discipline + ISMS Clauses 4-10 OOS-by-design with 7 Major Nonconformity classes
63
67
  - **CIS Critical Security Controls v8** (CIS, May 2021; v8.1 errata June 2024) — **17 covered + 23 partial + 113 OOS across 153 Safeguards / 18 Controls**; per-Safeguard mapping with the **Implementation Group cumulative discipline** (IG1=56 cyber-insurance baseline / IG2 cumulative=130 / IG3 cumulative=153); no-certification-body attestation discipline (INPUT to your CSAT / CIS-CAT Pro self-attestation, never "CIS certified"); Cloud Companion Guide v8 shared-responsibility + CIS-Hardened-Image substrate-evidence credit (4.1/4.2/4.6)
64
68
  - **GDPR Article 32 (Security of Processing)** (Regulation (EU) 2016/679) — **4 covered + 5 partial + 2 OOS across 11 Art. 32 sub-measure units**; **GDPR Article 32 infrastructure substrate only — NOT GDPR compliance** (GDPR is a 99-article legal regime; Art. 32 security-of-processing is the only article an infrastructure scanner can substrate-evidence; the rest is operator-side, out of scope by design). Four-factor proportionality (substrate *for* your "appropriate to the risk" determination, never an absolute pass/fail); personal-data-scope attestation (pair with your Art. 30 records of processing); **Art. 83(4) lower fine tier** (€10M/2%, not the €20M/4% headline tier); Art. 32(3)/Art. 42 cloud-provider certification-inheritance
65
- - 🔐 **Evidence with a verifiable chain of custody** — SHA-256 sidecars on every artifact, verifiable offline. RFC 3161 trusted timestamps are **opt-in since EE 0.33.0** via `NSAUDITOR_TSA_URL`, require an outbound call to the authority you name plus the `openssl` binary, and were exercised against a live Time-Stamp Authority on the npm path on 2026-08-07: the auditor procedure `openssl ts -verify` returned OK, with a one-byte-mutated copy returning FAILED as the control. The `:0.33.0` Marketplace image is proven too: on 2026-08-08 the same round-trip was driven from inside the pushed image against a real authority, verified with the image’s own `openssl`. **Retained images at `:0.32.11` and earlier carry no `openssl` at all**, so nothing here speaks for them. **Ed25519 suppression signing became reachable in EE 0.35.0 via `compliance suppress`** and is **not yet proven** — its verification gate has not run against published bytes, so do not plan around a produced signature as verified evidence.
69
+ - 🔐 **Evidence with a verifiable chain of custody** — SHA-256 sidecars on every artifact, verifiable offline. RFC 3161 trusted timestamps are **opt-in since EE 0.33.0** via `NSAUDITOR_TSA_URL`, require an outbound call to the authority you name plus the `openssl` binary, and were exercised against a live Time-Stamp Authority on the npm path on 2026-08-07: the auditor procedure `openssl ts -verify` returned OK, with a one-byte-mutated copy returning FAILED as the control. The `:0.33.0` Marketplace image is proven too: on 2026-08-08 the same round-trip was driven from inside the pushed image against a real authority, verified with the image’s own `openssl`. **Retained images at `:0.32.11` and earlier carry no `openssl` at all**, so nothing here speaks for them. **Ed25519 suppression signing became reachable in EE 0.35.0 via `compliance suppress`** and is **proven as of EE 0.36.0 for approvers whose registry entry carries key material** — a shipped entry point signs, and a one-character length-preserving tamper makes the report render `signature FAILED verification`. A fingerprint-only registry entry reads `not checked by this report`, which records that no check ran and never that one failed.
66
70
  - 🏛️ **Zero Data Exfiltration architecture** — no scan data is collected, transmitted, or stored by Nsasoft; reports are written to your local disk. Air-gapped operation once configured for it (`NSAUDITOR_OFFLINE_ONLY=1` plus a local NVD store; a default run queries NIST's public CVE API). AI analysis happens locally (Ollama) or via your own API keys. Important for PCI DSS CDE-isolation threat models.
67
71
  - 🔗 **GRC connectors — Vanta + Drata + Secureframe (Enterprise)** — map compliance findings to your GRC platform's evidence/test records and push them at **scan time** (opt-in). Suppression-aware outcome mapping (Vanta) / structured records (Drata + Secureframe), idempotent retries, rate-limit handling, token redaction, and Zero-Data-Exfiltration egress redaction. Early-access, single-workspace; live validation against production tenants is in progress. See **[GRC Connectors](#grc-connectors-vanta-drata-secureframe)** below.
68
72
  - 🗄️ **WORM posture VALIDATION** — we read your bucket's S3 Object Lock configuration per CloudTrail trail bucket and fail the control when it is absent, in GOVERNANCE mode or under-retained. That is substrate FOR a SEC Rule 17a-4(f) / FINRA 4511 retention claim, not the claim itself: the engine WRITING artifacts into the immutable store is built and **not reachable** (no caller), so the retention guarantee stays yours to make about your own archive
@@ -110,7 +114,7 @@ How Marketplace fulfillment works (ZDE and air-gap preserved — no runtime AWS
110
114
  | Multi-framework `--compliance soc2,hipaa,nist-csf,pci-dss,iso-27001,cis-v8,gdpr` from one scan | — | — | ✅ |
111
115
  | **Enterprise — auditor-grade evidence** | | | |
112
116
  | Evidence packs with SHA-256 chain-of-custody (RFC 3161 timestamps opt-in via `NSAUDITOR_TSA_URL`, exercised against a live TSA on both the npm path and the `:0.33.0` container image; retained images `:0.32.11` and earlier carry no `openssl`) | — | — | ✅ |
113
- | Suppression workflow with approver identity verification (Ed25519 SIGNING reachable 0.35.0, not yet proven) | — | — | ✅ |
117
+ | Suppression workflow with approver identity verification (Ed25519 SIGNING reachable 0.35.0, proven 0.36.0, verified per approver holding **key material**) | — | — | ✅ |
114
118
  | Chain-of-custody manifests | — | — | ✅ |
115
119
  | SLA / MTTR tracking + compensating controls | — | — | ✅ |
116
120
  | Recurring-scan attestation (Type II operating-effectiveness) | — | — | ✅ |
@@ -118,7 +122,7 @@ How Marketplace fulfillment works (ZDE and air-gap preserved — no runtime AWS
118
122
  | **Enterprise — integration + deployment** | | | |
119
123
  | GRC connectors — Vanta + Drata + Secureframe push (scan-time, opt-in) | — | — | ✅ |
120
124
  | Tabletop simulation + SIEM correlation — built, **not reachable** (no caller) | — | — | 🚧 |
121
- | Air-gapped **operation** once installed — offline licence validation, offline CVE matching under `NSAUDITOR_OFFLINE_ONLY=1`. (Air-gapped DELIVERY is WITHDRAWN: no offline installer, image bundle or feed bundle ships.) | — | — | ✅ |
125
+ | Air-gapped **operation** once installed — offline licence validation, offline CVE matching under `NSAUDITOR_OFFLINE_ONLY=1`. (Air-gapped DELIVERY ships as of EE 0.37.0 — an offline installation tarball, an install script and a `feed bundle` pipeline — as a **restricted** distribution, amd64 only. No container image bundle ships, and arm64 images remain WITHDRAWN.) | — | — | ✅ |
122
126
 
123
127
  **This repository is the Community Edition** — fully functional, MIT-licensed, no restrictions, no telemetry. Pro and Enterprise features ship via the [`@nsasoft/nsauditor-ai-ee`](https://www.nsauditor.com/ai/pricing) package and install alongside the CE binary once licensed.
124
128
 
@@ -302,8 +306,8 @@ Results land in `./out/<host>_<timestamp>/`:
302
306
  | 1220 | Azure Storage Account Data-Protection (**EE 0.13.2**) | Enterprise | Dedicated Azure Storage Account encryption / transit / authorization auditor — orthogonal to the 1022 scanner's network-exposure dims (no double-emission; mirrors the AWS 1020 + 1120 two-plugin S3 split). HTTPS-only transit (`enableHttpsTrafficOnly`) + minimum TLS version + Shared Key authorization (`allowSharedKeyAccess` — bypasses Azure AD; absent = enabled, never silent-PASS) + infrastructure (double) encryption + encryption key source incl. **customer-managed-key reachability + rotation** (`keyVaultProperties` — a disabled/revoked/version-pinned CMK degrades, not silent-PASS). Conservative classifier: indeterminate field / AccessDenied → evidence-gap; single-subscription scope surfaced explicitly. **CC6.7 / CC6.1 / C1.1** |
303
307
  | 1221 | Azure NSG Perimeter (**EE 0.14.0**; UDP lane **EE 0.14.1**) | Enterprise | The Azure analog of AWS 1170 — a CC6.6 network-segmentation perimeter auditor for Azure Network Security Groups. Evaluates each NSG's inbound rules in Azure priority order (first match wins; DenyAllInbound default): all-protocol public Allow + public-source (`*`/`0.0.0.0/0`/`Internet`) to a restricted **TCP** management/data-tier port (SSH/RDP/MSSQL/MySQL/Postgres/Redis/Mongo/SMB/WinRM/etc.) + `::/0` IPv6-wildcard to a restricted port (the dimension 1022's flat lint misses) + **public-source / `::/0` to a restricted UDP service** (SNMP/CLDAP/NTP/rpcbind/IPMI/IKE/Memcached etc. — Dim 2u/3u, EE 0.14.1) + public→non-restricted INFO + PASS substrate. **Attachment-aware** (attached → CRITICAL effective; orphaned → MEDIUM latent) + effective priority/deny-override resolution + `0.0.0.0/1` split-range coverage. Non-overlapping-by-depth with 1022's coarse per-rule NSG lint. Conservative classifier: denied/indeterminate → evidence-gap; one malformed NSG degrades per-resource. **CC6.6** |
304
308
  | 1222 | Azure Key Vault Deep Auditor (**EE 0.15.0**) | Enterprise | The third dedicated Azure auditor (after 1220 storage + 1221 NSG) — the KV analog of how 1221 deepens 1022's flat NSG dim. Enumerates each vault's keys, role assignments, and diagnostic settings across 4 dims: (1) key **auto-rotation policy** + (2) key **expiry** (epoch-s/ms/Date/string coerced) + (3) **diagnostic logging → Log Analytics** (`@azure/arm-monitor`) + (4) **privileged-access depth** (RBAC `roleAssignments` admin/data-plane/scope-aware + legacy `accessPolicies` export/wide-crypto breadth). Orthogonal to 1022's vault-property dims (purge/soft-delete/network-ACL/RBAC-mode) — no double-emission. Secret/cert expiry is a deliberate data-plane scope boundary. Conservative classifier: indeterminate field / AccessDenied / arm-monitor absent → evidence-gap; one malformed vault degrades per-resource. **CC6.3 / C1.1 / CC6.1 / CC7.2** |
305
- | — | SOC 2 Compliance Engine | Enterprise | AICPA TSC 2017 mapping (10 covered + 4 partial controls), SHA-256 chain-of-custody, opt-in RFC 3161 timestamps, exercised against a live TSA on both the npm path and the `:0.33.0` container image, and the suppression workflow. (Ed25519 SIGNING of suppressions is reachable from EE 0.35.0 and not yet proven — the gate has not run against published bytes.) |
306
- | — | **HIPAA Compliance Engine (EE 0.9.0)** | Enterprise | HIPAA Security Rule §164.312 Technical Safeguards mapping (7 covered + 3 partial + 45 OOS within §164.312 + entire §164.308 + entire §164.310). HHS Required/Addressable discipline per control. Same evidence infrastructure as SOC 2 (SHA-256 chain-of-custody; opt-in RFC 3161 timestamps; suppression signing reachable from EE 0.35.0 but not yet proven). Use `--compliance hipaa` or `--compliance soc2,hipaa` for dual-framework reports from a single scan. **Zero BAA required** — Zero Data Exfiltration architecture means ePHI never leaves customer infrastructure. |
309
+ | — | SOC 2 Compliance Engine | Enterprise | AICPA TSC 2017 mapping (10 covered + 4 partial controls), SHA-256 chain-of-custody, opt-in RFC 3161 timestamps, exercised against a live TSA on both the npm path and the `:0.33.0` container image, and the suppression workflow. (Ed25519 SIGNING of suppressions is reachable from EE 0.35.0 and PROVEN at EE 0.36.0, verified per approver holding **key material**.) |
310
+ | — | **HIPAA Compliance Engine (EE 0.9.0)** | Enterprise | HIPAA Security Rule §164.312 Technical Safeguards mapping (7 covered + 3 partial + 45 OOS within §164.312 + entire §164.308 + entire §164.310). HHS Required/Addressable discipline per control. Same evidence infrastructure as SOC 2 (SHA-256 chain-of-custody; opt-in RFC 3161 timestamps; suppression signing reachable from EE 0.35.0, proven at EE 0.36.0 and verified per approver holding **key material**). Use `--compliance hipaa` or `--compliance soc2,hipaa` for dual-framework reports from a single scan. **Zero BAA required** — Zero Data Exfiltration architecture means ePHI never leaves customer infrastructure. |
307
311
  | — | **NIST CSF 2.0 Compliance Engine (EE 0.10.0)** | Enterprise | NIST Cybersecurity Framework 2.0 Core mapping at the auditor-canonical Subcategory level — 13 covered + 10 partial + 83 OOS across 106 of CSF 2.0's 107 Subcategories. Govern function OOS-by-design (GV.SC-04 partial as substrate exception); Respond function OOS-entirely; Implementation Tiers 1-4 OOS as organizational-maturity claims. NIST SP 800-53 Rev. 5 + CIS Critical Security Controls v8 cross-references baked into `informativeReferences`. Use `--compliance nist-csf` or `--compliance soc2,hipaa,nist-csf` for triple-framework reports from a single scan. |
308
312
  | — | **PCI DSS v4.0.1 Compliance Engine (EE 0.11.0)** | Enterprise | PCI DSS v4.0.1 (PCI SSC, June 2024 errata; supersedes v4.0 March 2022; v3.2.1 retired March 31, 2024) mapping at the auditor-canonical sub-requirement level for QSA Report on Compliance workflow — **19 covered + 9 partial + 39 OOS across 67 of ~250 sub-requirements (MVP-67 density)** (Req 7.2.2 down-rated covered→partial in EE 0.19.4 — access-by-job-classification is process/HR-gated). Req 12 Information Security Program OOS-by-design entirely. Req 5 anti-malware + Req 9 physical OOS-entirely. **Defined-vs-Customized Approach discipline per Appendix E** — 15 Defined-only sub-requirements enforced at schema layer. **Cardholder Data Environment (CDE) scope operator-attested** via CDE Data Flow Diagram per Req 1.2.4 + Req 12.5.1. **Card-brand AOC enforcement priority view** (Visa CISP / Mastercard SDP / Amex DSOP / Discover DISC). **4 load-bearing schema enrichments** per control: `controlType` + `approachEligibility` + `cloudProviderAttestation` (AWS / Azure / GCP currently-named AOCs) + `cdeScope`. CAO MVP-deferred to EE 0.11.1. Use `--compliance pci-dss` or `--compliance soc2,hipaa,nist-csf,pci-dss` for quad-framework reports from a single scan. |
309
313
  | — | **ISO/IEC 27001:2022 Compliance Engine (EE 0.12.0)** | Enterprise | ISO/IEC 27001:2022 (ISO + IEC, October 2022; 2013 edition retired October 31, 2025) Annex A mapping at the auditor-canonical per-Annex-A-code level for ISO/IEC 17021-1 certification body assessors — **17 covered + 14 partial + 62 OOS across 93 Annex A controls** (the complete Annex A universe across 4 themes: A.5 Organizational 37 + A.6 People 8 + A.7 Physical 14 + A.8 Technological 34). **Statement of Applicability per Clause 6.1.3.d discipline** — engine produces substrate for INCLUDED controls; SoA inclusion/exclusion is operator-side. **ISMS Clauses 4-10 OOS-by-design** with 7 Major Nonconformity classes (absence of internal audit per Clause 9.2 OR management review per Clause 9.3 = auto-fail Stage 2). 11 NEW 2022 controls + 5-attribute taxonomy (cybersecurityConcepts 5 categories, NOT 6 like NIST CSF) + 2013-to-2022 transition discipline + Cloud-Provider Certificate Inheritance Matrix. Use `--compliance iso-27001` or any combination for multi-framework reports from a single scan. |
@@ -352,7 +356,7 @@ nsauditor-ai scan --host aws --plugins all --compliance soc2 --aws-region all
352
356
  - **Global services** (IAM, account-level S3 enumeration) are audited once regardless of `--aws-region`; the S3 auditors resolve **each bucket's own region** and skip + disclose buckets outside the scoped set (closing latent cross-region false-cleans).
353
357
  - **MCP `scan_cloud` (Claude Desktop / Claude Code):** the same scoping is a `regions` argument — *omit* it to scan the server-configured `AWS_REGION`, or pass `["all"]` (or a region-code list like `["us-east-1","eu-west-1"]`) to fan out. Omitting does **not** fan out, so a single tool-call stays within Desktop's timeout.
354
358
 
355
- The auditor evidence pack is emitted under `out/` — cover-page Scope Attestation, SHA-256 chain-of-custody sidecars, the suppression workflow and approver identity verification, plus opt-in RFC 3161 trusted-timestamps (`NSAUDITOR_TSA_URL`), exercised against a live TSA on both the npm path and the `:0.33.0` container image. Ed25519 suppression SIGNING is built and not reachable. EE is available at [`www.nsauditor.com/ai/pricing`](https://www.nsauditor.com/ai/pricing).
359
+ The auditor evidence pack is emitted under `out/` — cover-page Scope Attestation, SHA-256 chain-of-custody sidecars, the suppression workflow and approver identity verification, plus opt-in RFC 3161 trusted-timestamps (`NSAUDITOR_TSA_URL`), exercised against a live TSA on both the npm path and the `:0.33.0` container image. Ed25519 suppression SIGNING is reachable from EE 0.35.0 and PROVEN at EE 0.36.0, verified per approver holding **key material**. EE is available at [`www.nsauditor.com/ai/pricing`](https://www.nsauditor.com/ai/pricing).
356
360
 
357
361
  ---
358
362
 
@@ -673,6 +677,10 @@ nsauditor-ai license install <KEY>
673
677
  nsauditor-ai license <--status | --capabilities | --plugins>
674
678
  nsauditor-ai security <set|delete|list|get> <KEY>
675
679
  nsauditor-ai validate
680
+ nsauditor-ai feed bundle --from <dir-of-NVD-feeds> --out <bundle.json.gz> [--kev <f>] [--epss <f>]
681
+ nsauditor-ai feed import --file <feed-or-bundle> [--cache-dir <d>] [--extras-dir <d>] [--append]
682
+ nsauditor-ai compliance <attest|suppress|review|renew|keygen> (Enterprise)
683
+ nsauditor-ai mcp
676
684
  nsauditor-ai --help (or -h, or `help`)
677
685
  nsauditor-ai --version (or -v, or `version`)
678
686
  ```
@@ -844,7 +852,7 @@ explanatory message if it is absent. CLI-only; the MCP surface does not reach th
844
852
  # (pass --email/--role/--team too, or the member prints REPLACE ME placeholders)
845
853
  nsauditor-ai compliance keygen --key ~/.nsauditor/approver.pem --approver "Ann Approver"
846
854
 
847
- # Record an approval; SIGNS it when NSAUDITOR_SIGNING_KEY names a local Ed25519 key (not yet proven)
855
+ # Record an approval; SIGNS it when NSAUDITOR_SIGNING_KEY names a local Ed25519 key (verified per approver holding key material)
848
856
  NSAUDITOR_SIGNING_KEY=~/.nsauditor/approver.pem \
849
857
  nsauditor-ai compliance suppress --suppressions ./out/suppressions.json \
850
858
  --source auth_agent --title-pattern "SSH password authentication enabled" \
@@ -867,8 +875,12 @@ auditor checks an archived approval); a malformed signing key fails the command
867
875
  it warns loudly and tells you to re-approve.
868
876
 
869
877
  ⚠️ **`--flag=value` is not supported**, here or on any flag this CLI has: use `--flag value`.
870
- Ed25519 suppression signing is reachable from Enterprise 0.35.0 and **not yet proven** — its
871
- verification gate has not run against published bytes.
878
+ Ed25519 suppression signing is reachable from Enterprise 0.35.0, and **proven at 0.36.0 for approvers whose registry entry carries key material**.
879
+ Its verification gate ran against the published bytes and passed, tamper negative control included.
880
+ A fingerprint-only registry entry reads `not checked by this report`, which records that no check
881
+ ran and never that one failed.
882
+ Verification runs for approvers whose registry entry carries key material; a fingerprint-only entry
883
+ reads `not checked by this report`, which records that no check ran and never that one failed.
872
884
 
873
885
  **Compliance evidence (`NSAUDITOR_*` — Enterprise, all opt-in):**
874
886
 
@@ -897,15 +909,15 @@ NSAUDITOR_SIGNING_KEY= # A REFERENCE, never key material: keychain:LA
897
909
  # (mode 0600) | awskms:alias/… — see the honesty note below.
898
910
  ```
899
911
 
900
- One of these is deliberately **not** a capability yet and the other is proven on one path only —
901
- saying which is which is the point:
912
+ Both of these are capabilities now, and each was proven later than it was wired — saying exactly
913
+ when, on which delivery vehicle, and for whom is the point:
902
914
 
903
- - **`NSAUDITOR_SIGNING_KEY` is CONSUMED from EE 0.35.0, and the note below used to say otherwise.** EE 0.33.0 stopped parsing it at option-resolution time — a malformed value used to abort the EE stage for a setting nothing read — so it is carried through unparsed; `parseSigningRef` / `resolveSigner` hold the parse and the offline veto at the point a key is used. As of EE 0.35.0 `compliance suppress` signs the approval it writes when this variable names a local Ed25519 key — a capability that is **not yet proven**, because its verification gate has
904
- not run against published bytes, so a produced signature is not yet evidence anything vouches
905
- for. The record's `algorithm` and `backend` fields were frozen *before* the first
915
+ - **`NSAUDITOR_SIGNING_KEY` is CONSUMED from EE 0.35.0, and the note below used to say otherwise.** EE 0.33.0 stopped parsing it at option-resolution time — a malformed value used to abort the EE stage for a setting nothing read — so it is carried through unparsed; `parseSigningRef` / `resolveSigner` hold the parse and the offline veto at the point a key is used. As of EE 0.35.0 `compliance suppress` signs the approval it writes when this variable names a local Ed25519 key — proven as of EE 0.36.0 and verified per approver holding key material.
916
+ Its verification gate ran against the published bytes and passed, so a produced signature IS
917
+ evidence for those approvers; a fingerprint-only registry entry reads `not checked by this report`. The record's `algorithm` and `backend` fields were frozen *before* the first
906
918
  signature could reach a customer archive, because retrofitting that later breaks every
907
919
  auditor holding one.
908
- - **`NSAUDITOR_TSA_URL` is wired, and since 2026-08-07 it is proven — on the npm path only.**
920
+ - **`NSAUDITOR_TSA_URL` is wired, and it is proven on both delivery vehicles — the npm path on 2026-08-07, and from inside the `:0.33.0` container image on 2026-08-08.**
909
921
  The live check this note used to say was outstanding has been run: through the installed
910
922
  binary, against a real Time-Stamp Authority, the auditor procedure `openssl ts -verify`
911
923
  returns OK on the compliance report, the scope attestation and the chain of custody; the
@@ -1124,7 +1136,7 @@ NSAuditor AI is built on a **Zero Data Exfiltration (ZDE)** architecture:
1124
1136
  - **No data processing.** Nsasoft US LLC never sees, stores, or processes your scan results.
1125
1137
  - **AI is opt-in.** External AI calls use your own API keys. Redaction runs locally first.
1126
1138
  - **License validation is offline.** JWT signature verified locally with an embedded public key.
1127
- - **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, and the timestamping and signing paths on the roadmap — 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).
1139
+ - **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).
1128
1140
 
1129
1141
  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.
1130
1142
 
package/cli.mjs CHANGED
@@ -603,6 +603,40 @@ export async function parseArgs(argv) {
603
603
  }
604
604
  if (a.length && !a[0].startsWith('--')) args.cmd = a[0];
605
605
 
606
+ // ⚠️ REFUSE `--flag=value` LOUDLY RATHER THAN IGNORING IT SILENTLY.
607
+ //
608
+ // `get()` below is an exact-token `indexOf('--' + name)`, so `--plugins=port_scanner` is a
609
+ // DIFFERENT token and returns undefined. The consequences were not uniformly loud, which is
610
+ // what made this worth a refusal rather than a note: `--host=1.2.3.4` yields undefined and the
611
+ // CLI refuses because a missing host is checked, but `--plugins=port_scanner` fell through to
612
+ // the `all` default and ran EVERY plugin against whatever the operator was scanning — a
613
+ // materially different scan than they asked for, and one they cannot see.
614
+ //
615
+ // Measured before adding this: ZERO documented `=` usages anywhere in either repo. The shape
616
+ // never worked, so nothing can depend on it, and refusing costs no working invocation.
617
+ //
618
+ // ⚠️ THIS IS A REFUSAL, NOT SUPPORT. Full `=` parsing touches every flag and every test that
619
+ // depends on current parsing; it is its own lane. A loud refusal is strictly better than silent
620
+ // divergence for every operator typing `=` today, and it leaves that lane a clean starting state.
621
+ {
622
+ const eqFlag = a.find((t) => typeof t === 'string' && /^--[a-z0-9][a-z0-9-]*=/i.test(t));
623
+ if (eqFlag) {
624
+ const [name, ...rest] = eqFlag.slice(2).split('=');
625
+ // ⚠️ THROW, DO NOT `process.exit()` — and this was learned the hard way ONE HOUR AFTER
626
+ // finding the same shape elsewhere. The first version called `process.exit(2)` here,
627
+ // matching the webhook-rejection precedent a few lines below. `parseArgs` is EXPORTED and
628
+ // called in-process by the test suite, so that exit killed the runner mid-file:
629
+ // `cli_approval_surface.test.mjs` reported `tests 1 · pass 0` — a count of the FILE, with
630
+ // every other case in it silently not running. An exported function that can kill its
631
+ // caller's process is untestable by construction. The entry point below maps this code to
632
+ // exit 2, so the operator-facing contract is unchanged.
633
+ const err = new Error(`${eqFlag}: the --flag=value form is not supported. `
634
+ + `Use a space: --${name} ${rest.join('=') || '<value>'}`);
635
+ err.code = 'EFLAGSHAPE';
636
+ throw err;
637
+ }
638
+ }
639
+
606
640
  const get = (name) => {
607
641
  const i = a.indexOf(`--${name}`);
608
642
  if (i === -1) return undefined;
@@ -681,9 +715,39 @@ export async function parseArgs(argv) {
681
715
 
682
716
  // ── the approval surface (EE 0.35.0) ────────────────────────────────────────
683
717
  // Parsed HERE with every other flag rather than read from `process.argv` in the
684
- // subcommand, so one parser owns the `--flag value` / `--flag=value` / boolean
685
- // shapes. `attest` taught this: a second parser is a second set of edge cases.
718
+ // subcommand, so ONE parser owns the flag shapes. `attest` taught this: a second parser is a
719
+ // second set of edge cases.
720
+ //
721
+ // ⚠️ CORRECTED 2026-08-15 — THIS COMMENT USED TO CLAIM THE PARSER OWNS THE `--flag=value` SHAPE.
722
+ // It does not, and never has: `get()` is an exact-token `indexOf('--' + name)`, so `--flag=value`
723
+ // is a DIFFERENT token and returns undefined. Measured: `--suppressions=/tmp/s.json` -> null,
724
+ // while `--suppressions /tmp/s.json` -> "/tmp/s.json". The documented intent was right and the
725
+ // code did the other thing, with nothing comparing them.
726
+ // Consequences differ per flag and are NOT uniformly loud: `--host=1.2.3.4` yields undefined and
727
+ // the CLI refuses ("Fatal: --host or --host-file is required"), but `--plugins=port_scanner`
728
+ // falls through to the `all` default and SILENTLY runs every plugin instead of the one asked for.
729
+ // Adding `=` support is a CLI-wide change touching every flag and belongs in its own measured
730
+ // commit, not smuggled in beside a feature — boarded rather than done here.
686
731
  const str = (name) => { const v = get(name); return (v && v !== true) ? v : null; };
732
+ // F1 air-gapped delivery: the hand-carry feed pipeline's flags. Parsed HERE for the same
733
+ // reason `approvalArgs` is — a second parser is a second set of edge cases, and the approval
734
+ // surface already paid for that with a ReferenceError on its first real invocation.
735
+ args.feedArgs = {
736
+ from: str('from'),
737
+ out: str('out'),
738
+ file: str('file'),
739
+ cacheDir: str('cache-dir') ?? str('cache_dir'),
740
+ // Boolean: present means true. NOT defaulted to true — an accumulating default means a
741
+ // re-import never REPLACES, so a store the operator believes they refreshed silently keeps
742
+ // every stale record it ever held.
743
+ append: get('append') === true || str('append') === 'true',
744
+ // D-95.3: the operator's OWN exploit-intelligence downloads ride the archive. The product
745
+ // ships no KEV/EPSS data of its own; in an air-gapped enclave there is no other way to get it
746
+ // across. `--extras-dir` is where import PLACES them, and the env lines are printed after.
747
+ kev: str('kev'),
748
+ epss: str('epss'),
749
+ extrasDir: str('extras-dir') ?? str('extras_dir'),
750
+ };
687
751
  args.approvalArgs = {
688
752
  suppressions: str('suppressions'),
689
753
  keyPath: str('key'),
@@ -1041,7 +1105,7 @@ export async function preflightGrcIfRequested(env, opts = {}) {
1041
1105
 
1042
1106
  export async function main() {
1043
1107
  const args = await parseArgs(process.argv);
1044
- const { cmd, host, plugins, insecureHttps, hostFile, parallel, failOn, outputFormat, watch, intervalMinutes, webhookUrl, alertSeverity, ports, compliance, complianceScope, complianceHistory, slaPolicy, attestWindow, framework, awsRegion, approvalArgs } = args;
1108
+ const { cmd, host, plugins, insecureHttps, hostFile, parallel, failOn, outputFormat, watch, intervalMinutes, webhookUrl, alertSeverity, ports, compliance, complianceScope, complianceHistory, slaPolicy, attestWindow, framework, awsRegion, approvalArgs, feedArgs } = args;
1045
1109
 
1046
1110
  // Version: handled before license verification so it works without a key.
1047
1111
  // CE-0.1.30.1 — closes the discovery-flag UX gap where pre-fix
@@ -1108,6 +1172,16 @@ Scan options:
1108
1172
 
1109
1173
  Compliance subcommands:
1110
1174
  nsauditor-ai compliance attest --history <dir> [--framework <fw>] [--window 6m|12m|90d]
1175
+ nsauditor-ai feed bundle --from <dir-of-downloaded-NVD-feeds> --out <bundle.json.gz>
1176
+ [--kev <known_exploited_vulnerabilities.json>]
1177
+ [--epss <epss_scores.csv.gz>]
1178
+ --kev / --epss carry YOUR OWN downloads from CISA and FIRST inside the archive. No
1179
+ exploit data ships with this product; in an air-gapped enclave the archive is the only
1180
+ way across. Both are validated here, on the connected host, where a bad file can still
1181
+ be re-downloaded.
1182
+ nsauditor-ai feed import --file <feed-or-bundle.json.gz> [--cache-dir <dir>] [--append]
1183
+ [--extras-dir <dir>]
1184
+ --extras-dir places any carried KEV/EPSS and prints the env lines to set.
1111
1185
  nsauditor-ai compliance keygen --key <path> [--approver <name>] [--email <e>] [--role <r>]
1112
1186
  [--team <t>] [--force]
1113
1187
  Generate an Ed25519 approval keypair (private 0600) and print
@@ -2172,6 +2246,98 @@ Docs: https://www.nsauditor.com/ai/ | Pricing: https://www.nsauditor.com/ai/
2172
2246
  // veto, framework-alias resolution and every default. CE contributes the flag
2173
2247
  // surface and the exit code — nothing here can drift out of sync with the engine
2174
2248
  // because nothing here decides anything.
2249
+ if (cmd === 'feed') {
2250
+ const sub = process.argv[3];
2251
+
2252
+ // ── THE AIR-GAP HAND-CARRY PIPELINE (EE F1) ────────────────────────────
2253
+ // `bundle` merges the NVD feeds an operator downloaded on a CONNECTED host into one portable
2254
+ // archive; `import` consumes it on the ISOLATED host through EE's `importFeed()`. Same
2255
+ // THIN-FORWARD discipline as `compliance`: EE owns validation, the refusals and every
2256
+ // decision; CE contributes a flag surface and an exit code.
2257
+ //
2258
+ // ⚠️ `feed bundle` BUNDLES THE FEEDS YOU DOWNLOADED — never "your database". The offline
2259
+ // store is a lossy derivation of an NVD feed (configurations flattened to matches, references
2260
+ // dropped, rejected and zero-CPE CVEs discarded at import), so it cannot be turned back into
2261
+ // one. Bundling the SOURCE keeps `importFeed()` the single ingest path across the air-gap
2262
+ // trust boundary — its gzip-bomb cap covers the bundle for free — and mints no file format
2263
+ // we would then own as a frozen compatibility surface.
2264
+ //
2265
+ // ⚠️ NOTHING HERE FLIPS A CLAIM. `feed-import-cli`, `offline-install-tarball`,
2266
+ // `install-script` and `airgap-deployment` stay withdrawn until the trio publishes AND the
2267
+ // NIC-down gate passes on the BUILT artifacts. Publishing a surface is not proving it.
2268
+ if (sub === 'bundle' || sub === 'import') {
2269
+ let ee;
2270
+ try {
2271
+ ee = await import('@nsasoft/nsauditor-ai-ee');
2272
+ } catch {
2273
+ console.error(`feed ${sub} requires the Enterprise package (@nsasoft/nsauditor-ai-ee).`);
2274
+ process.exit(2);
2275
+ }
2276
+ const F = feedArgs ?? {};
2277
+ try {
2278
+ if (sub === 'bundle') {
2279
+ const out = await ee.bundleFeedCommand({
2280
+ from: F.from, out: F.out, kev: F.kev, epss: F.epss,
2281
+ });
2282
+ console.log(`bundled ${out.vulnerabilities} CVE(s) from ${out.sourceFiles} feed file(s)`
2283
+ + `${out.duplicatesDropped ? ` (${out.duplicatesDropped} duplicate(s) dropped)` : ''}`
2284
+ + ` -> ${out.out} (${(out.bytes / 1048576).toFixed(1)} MB)`);
2285
+ // ⚠️ ABSENCE IS STATED, NEVER OMITTED. An operator who is not told the bundle carries no
2286
+ // KEV discovers it from empty scan output on the isolated side, where `kev: null` on
2287
+ // every finding is indistinguishable from a host with nothing exploited.
2288
+ if (out.carriedNote) console.log(` exploit intel: ${out.carriedNote}`);
2289
+ } else {
2290
+ const out = await ee.importFeedCommand({
2291
+ file: F.file, cacheDir: F.cacheDir || undefined, append: F.append === true,
2292
+ extrasDir: F.extrasDir || undefined,
2293
+ });
2294
+ console.log(`imported ${out.imported} CVE(s), skipped ${out.skipped}, errors ${out.errors}`);
2295
+ // ⚠️ THE SKIP SPLIT PRINTS HERE BECAUSE THE DATA-LOSS TICKET ORIGINATES HERE.
2296
+ // On a real NVD year file ~24% of records are skipped (measured on the 2024 feed:
2297
+ // 39,219 in, 29,866 imported, 9,353 skipped). An operator reading a bare "skipped 9353"
2298
+ // files that as data loss, and a docs-only note is read AFTER the ticket, not before.
2299
+ // So the reasons are named at the terminal, in one line, with what they mean.
2300
+ if (out.skipped > 0) {
2301
+ const parts = [];
2302
+ if (out.skippedRejected) parts.push(`${out.skippedRejected} withdrawn by NVD (Rejected)`);
2303
+ if (out.skippedNoCpe) parts.push(`${out.skippedNoCpe} with no CPE match data`);
2304
+ if (out.skippedMalformed) parts.push(`${out.skippedMalformed} malformed (no CVE id)`);
2305
+ if (parts.length) {
2306
+ console.log(` skipped breakdown: ${parts.join(', ')}`);
2307
+ console.log(' Skips are expected — NVD publishes withdrawn CVEs and entries with no '
2308
+ + 'CPE data, and neither can ever match a scan. This is not data loss.'
2309
+ + (out.skippedMalformed
2310
+ ? ' ⚠️ Malformed records are NOT expected: re-download the feed and check its checksum.'
2311
+ : ''));
2312
+ }
2313
+ }
2314
+ // ⚠️ D-95.3: THE ENV LINES ARE THE DELIVERABLE, NOT A NICETY. The product reads these
2315
+ // stores from two environment variables and ships no exploit data of its own. A file
2316
+ // placed on the isolated host that the operator is never told to point at leaves every
2317
+ // finding reading `kev: null` — which is exactly what a host with nothing exploited
2318
+ // reports. Printing the resolved paths is what turns a placed file into a used one.
2319
+ if (out.extrasPlaced?.length) {
2320
+ console.log(`\nplaced ${out.extrasPlaced.map((p) => p.kind).join(' + ')} — set these `
2321
+ + 'before scanning:');
2322
+ console.log(out.envLines);
2323
+ } else if (out.extrasNote) {
2324
+ console.log(`\n${out.extrasNote}`);
2325
+ }
2326
+ }
2327
+ process.exit(0);
2328
+ } catch (err) {
2329
+ // Refusals stay loud: an empty source set, a file that is not an NVD feed, a gzip over
2330
+ // the bomb cap, a missing path. Every one of them is the operator learning something
2331
+ // true on the host that can still fix it.
2332
+ console.error(`Fatal: ${err.message}`);
2333
+ process.exit(2);
2334
+ }
2335
+ }
2336
+
2337
+ console.error('Usage: nsauditor-ai feed <bundle|import> …');
2338
+ process.exit(2);
2339
+ }
2340
+
2175
2341
  if (cmd === 'compliance') {
2176
2342
  const sub = process.argv[3];
2177
2343
 
@@ -2221,7 +2387,16 @@ Docs: https://www.nsauditor.com/ai/ | Pricing: https://www.nsauditor.com/ai/
2221
2387
  + `${summary.approaching} approaching expiry · ${summary.expired} expired · `
2222
2388
  + `${summary.no_expiry} without expiry`);
2223
2389
  for (const r of rows) {
2224
- console.log(` [${r.expiryStatus}] ${r.suppressionId ?? '(no-id)'} — ${r.approver ?? '(no approver)'}`);
2390
+ // ⚠️ READ THE FIELDS EE ACTUALLY EMITS. `reviewCommand` returns rows shaped
2391
+ // { scanId, file, suppression, status, daysRemaining } — the expiry class is `status`
2392
+ // (NOT `expiryStatus`) and the id/approver live INSIDE `suppression`. Reading the wrong
2393
+ // names printed `[undefined] (no-id) — (no approver)` for EVERY row, including records
2394
+ // `compliance suppress` had just written itself. The SUMMARY line above was correct
2395
+ // throughout, and that is what hid it: a correct aggregate over broken rows.
2396
+ const sup = r.suppression ?? {};
2397
+ const who = typeof sup.approver === 'string' ? sup.approver : sup.approver?.name;
2398
+ console.log(` [${r.status ?? 'unknown'}] ${sup.id ?? '(no-id)'} — ${who ?? '(no approver)'}`
2399
+ + (sup.status ? ` · ${sup.status}` : ''));
2225
2400
  }
2226
2401
  process.exit(0);
2227
2402
  }
@@ -2605,6 +2780,12 @@ const _isEntrypoint = (() => {
2605
2780
  })();
2606
2781
  if (_isEntrypoint) {
2607
2782
  main().catch((err) => {
2783
+ // A flag-shape refusal is an operator error, not a crash: name it and exit 2, this CLI's
2784
+ // "could not do what you asked" code. Everything else keeps the stack and exit 1.
2785
+ if (err?.code === 'EFLAGSHAPE') {
2786
+ console.error(`[ERROR] ${err.message}`);
2787
+ process.exit(2);
2788
+ }
2608
2789
  console.error(err?.stack || err);
2609
2790
  process.exit(1);
2610
2791
  });
package/mcp_server.mjs CHANGED
@@ -259,7 +259,7 @@ export const TOOLS = [
259
259
  {
260
260
  name: 'scan_cloud',
261
261
  description:
262
- 'Audit one or more cloud accounts (AWS / GCP / Azure) for security & compliance posture using the credentials configured in the server environment. Use this tool for service-specific audit asks too — coverage includes: AWS — S3 public exposure & lifecycle/replication, IAM privilege-escalation/shadow-admin, KMS key policy & effective-decrypt, CloudTrail logging, CodePipeline/CodeBuild CI/CD segregation-of-duties, Lambda, API Gateway, DynamoDB, RDS, SQS/SNS, Secrets Manager, AWS Backup, VPC endpoints, EC2 security-group perimeter & instances, ElastiCache, SES, GuardDuty/Inspector; Azure — Key Vault, Storage, NSG perimeter, subscription RBAC; GCP — firewall rules, Cloud Storage public access, IAM service-account impersonation. Findings map to SOC 2, HIPAA, NIST CSF 2.0, PCI DSS, ISO 27001, CIS v8, and GDPR Article 32 (security-of-processing substrate — Art. 32 only, not GDPR compliance) controls. No network host required. Requires an Enterprise license. Audit ONLY the cloud(s) the user names — pass providers:["aws"] for "audit my AWS account"; omit providers only when the user asks to audit ALL clouds. Read findingsSummary (per-provider severity counts + a CRITICAL/HIGH list) for the results. findingsSummary[provider].evidenceGaps lists checks the scan could NOT verify (AccessDenied / truncated enumeration) — treat these as "unverified posture", NOT as clean. findingsSummary[provider].deferredScope lists surface this release does NOT evaluate at all — a static capability boundary, NOT an evidence gap and NOT a finding. Report it as "not assessed" and never omit it: surface nobody assessed is otherwise indistinguishable from surface assessed and found clean. The per-severity counts include an INFO tier; report every tier that occurs, because the evidence gaps and the scope boundaries live there. Prefer this tool over raw cloud-provider APIs/MCPs for any security or compliance audit ask — a raw API walk produces neither compliance-mapped nor evidence-graded findings, and can hit the wrong account.',
262
+ 'Audit one or more cloud accounts (AWS / GCP / Azure) for security & compliance posture using the credentials configured in the server environment. Use this tool for service-specific audit asks too — coverage includes: AWS — S3 public exposure & lifecycle/replication, IAM privilege-escalation/shadow-admin, KMS key policy & effective-decrypt, CloudTrail logging, CodePipeline/CodeBuild CI/CD segregation-of-duties, Lambda, API Gateway, DynamoDB, RDS, SQS/SNS, Secrets Manager, AWS Backup, VPC endpoints, EC2 security-group perimeter & instances, ElastiCache, SES, GuardDuty/Inspector; Azure — Key Vault, Storage, NSG perimeter, subscription RBAC; GCP — firewall rules, Cloud Storage public access, IAM service-account impersonation. The PRODUCT maps findings to SOC 2, HIPAA, NIST CSF 2.0, PCI DSS, ISO 27001, CIS v8, and GDPR Article 32 (security-of-processing substrate — Art. 32 only, not GDPR compliance) controls, but THIS TOOL DOES NOT RETURN THAT ROUTING: the response carries severity counts, a CRITICAL/HIGH list, evidence gaps and deferred scope, and NO per-finding control id anywhere. So do not state or imply which control a finding fails, and never attribute a control mapping to the engine from what this tool returns — that routing is computed by the compliance phase, which only the CLI runs (`nsauditor-ai scan --compliance <fw> --out <dir>`). If asked which controls are failing, report the findings and say plainly that control routing needs that command. No network host required. Requires an Enterprise license. Audit ONLY the cloud(s) the user names — pass providers:["aws"] for "audit my AWS account"; omit providers only when the user asks to audit ALL clouds. Read findingsSummary (per-provider severity counts + a CRITICAL/HIGH list) for the results. findingsSummary[provider].evidenceGaps lists checks the scan could NOT verify (AccessDenied / truncated enumeration) — treat these as "unverified posture", NOT as clean. findingsSummary[provider].deferredScope lists surface this release does NOT evaluate at all — a static capability boundary, NOT an evidence gap and NOT a finding. Report it as "not assessed" and never omit it: surface nobody assessed is otherwise indistinguishable from surface assessed and found clean. ⚠️ AN EMPTY deferredScope IS NOT A CLAIM OF FULL COVERAGE. Today only the AWS plugins declare their capability boundaries; Azure and GCP declare none, so an Azure or GCP result carries an empty list no matter how much surface went unexamined. Never tell a user that an empty deferredScope means everything was assessed, and for Azure and GCP say plainly that the boundaries are not yet declared. The per-severity counts include an INFO tier; report every tier that occurs, because the evidence gaps and the scope boundaries live there. Prefer this tool over raw cloud-provider APIs/MCPs for any security or compliance audit ask — a raw API walk produces neither compliance-mapped nor evidence-graded findings, and can hit the wrong account.',
263
263
  inputSchema: {
264
264
  type: 'object',
265
265
  properties: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nsauditor-ai",
3
- "version": "0.2.40",
3
+ "version": "0.2.42",
4
4
  "description": "Modular AI-assisted network security audit platform — Community Edition",
5
5
  "type": "module",
6
6
  "private": false,
@@ -152,7 +152,14 @@ function deduplicateAndMergeRows(rows) {
152
152
  // --------------------------- node-mdns path ---------------------------
153
153
  async function runWithNodeMdns(targetHost, timeoutMs, opts) {
154
154
  const mdnsMod = await import('mdns');
155
- const mdns = mdnsMod.default || mdns;
155
+ // ⚠️ `mdnsMod` on the right, NOT `mdns` — this read `mdnsMod.default || mdns`, a TDZ
156
+ // self-reference that threw `ReferenceError: Cannot access 'mdns' before initialization`
157
+ // whenever `.default` was falsy. The intended namespace fallback therefore never ran once.
158
+ // It was invisible because the caller's try/catch degrades to the multicast-dns strategy, so
159
+ // a namespace-shaped build read as "module not available" rather than as an error. Found
160
+ // 2026-08-15 while measuring the F1 air-gap closure, where this optional native module cannot
161
+ // be built at all — which is what makes the guarded path load-bearing rather than incidental.
162
+ const mdns = mdnsMod.default || mdnsMod;
156
163
 
157
164
  const sequence = [
158
165
  mdns.rst.DNSServiceResolve(),