nsauditor-ai 0.2.41 → 0.2.43

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,13 +17,17 @@ 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 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 stays **not yet proven**, its verification gate not having run against published bytes. 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 stays `not yet proven`**: this ships the code that makes the verification gate satisfiable, which is not a gate that has passed.
20
+ **The pack-signing commands land in the CLI — reachable, not yet proven (CE 0.2.43 / Enterprise 0.38.0).** `nsauditor-ai compliance sign-pack --manifest <envelope>` signs a chain-of-custody envelope with an operator-held Ed25519 key; `compliance verify-pack` establishes authorship from it — via `--registry` (your identity registry, with revocation and validity checked **as at signing time**) or `--public-key` (which discloses in its own output that those checks did not run). Both are thin forwards: Enterprise owns every decision and refusal, CE contributes the flags and the exit code. **Verify does more than the signature** — it recomputes every `artifacts[].sha256` against disk, so a signed pack whose report was edited returns a failure with the signature still reading VERIFIED. Exit **0** verified · **1** a violation · **2** the run could not measure, and the third is never a failure. Requires the Enterprise package; an auditor can also verify offline with `openssl` and nothing of ours, because the exact signed bytes ship beside the signature. **⚠️ Also a correction affecting 0.2.42/0.37.0:** a **gzipped** KEV/EPSS store — the default shape, since FIRST distributes EPSS only as `.csv.gz` — loaded with ZERO entries, so every finding reported *no store* rather than an exploit band. Misdiagnosis and silent degradation, **not** a false security verdict: findings carried a visible no-store marker throughout. Compression is now decided by file content rather than by filename.
21
21
 
22
- **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.
22
+ **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.
23
+
24
+ **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.
25
+
26
+ **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.
23
27
 
24
28
  **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.
25
29
 
26
- **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**.
30
+ **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**.
27
31
 
28
32
  See **[CHANGELOG.md](./CHANGELOG.md)** for the full per-release history.
29
33
 
@@ -64,7 +68,7 @@ If you're heading into a **SOC 2, HIPAA, NIST CSF 2.0, PCI DSS, ISO 27001, CIS C
64
68
  - **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
65
69
  - **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)
66
70
  - **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
67
- - 🔐 **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.
71
+ - 🔐 **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.
68
72
  - 🏛️ **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.
69
73
  - 🔗 **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.
70
74
  - 🗄️ **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
@@ -112,7 +116,7 @@ How Marketplace fulfillment works (ZDE and air-gap preserved — no runtime AWS
112
116
  | Multi-framework `--compliance soc2,hipaa,nist-csf,pci-dss,iso-27001,cis-v8,gdpr` from one scan | — | — | ✅ |
113
117
  | **Enterprise — auditor-grade evidence** | | | |
114
118
  | 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`) | — | — | ✅ |
115
- | Suppression workflow with approver identity verification (Ed25519 SIGNING reachable 0.35.0, not yet proven) | — | — | ✅ |
119
+ | Suppression workflow with approver identity verification (Ed25519 SIGNING reachable 0.35.0, proven 0.36.0, verified per approver holding **key material**) | — | — | ✅ |
116
120
  | Chain-of-custody manifests | — | — | ✅ |
117
121
  | SLA / MTTR tracking + compensating controls | — | — | ✅ |
118
122
  | Recurring-scan attestation (Type II operating-effectiveness) | — | — | ✅ |
@@ -120,7 +124,8 @@ How Marketplace fulfillment works (ZDE and air-gap preserved — no runtime AWS
120
124
  | **Enterprise — integration + deployment** | | | |
121
125
  | GRC connectors — Vanta + Drata + Secureframe push (scan-time, opt-in) | — | — | ✅ |
122
126
  | Tabletop simulation + SIEM correlation — built, **not reachable** (no caller) | — | — | 🚧 |
123
- | 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.) | — | — | ✅ |
127
+ | Air-gapped **operation** once installed — offline licence validation (local ES256, no callback, every tier) and offline CVE matching under `NSAUDITOR_OFFLINE_ONLY=1`, which is the **Pro** intelligence engine. The `feed bundle` / `feed import` hand-carry pipeline (EE 0.37.0) moves the NVD feeds **you downloaded**; it needs the Enterprise **package** installed but is not licence-gated, so a Pro licensee can run it. | — | ✅ | ✅ |
128
+ | Air-gapped **DELIVERY** — the dependency-complete offline installation tarball, install script and checksums (EE 0.37.0), distributed **restricted**, amd64 only. No container image bundle ships and arm64 images remain WITHDRAWN. | — | — | ✅ |
124
129
 
125
130
  **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.
126
131
 
@@ -262,7 +267,7 @@ Results land in `./out/<host>_<timestamp>/`:
262
267
 
263
268
  **28 enterprise plugins: 27 cloud auditors across AWS, GCP and Azure substrate, plus **`1023 Zero Trust Assessment`**, which scores zero-trust posture (segmentation, encryption-in-transit, identity, lateral-movement risk) from a **network-host** scan** — all mapped to AICPA Trust Services Criteria 2017 (10 covered + 4 partial controls). EE plugins live in the disjoint 1000+ ID range; CE reserves 001-099. Once licensed, the EE package installs alongside the CE binary and discovers automatically. A cloud `--host` pass auto-scopes on `cloudProvider`, which only the 27 declare, so `1023` never runs on a cloud pass. It runs on a network-host scan with the full plugin set — measured on the shipped CLI, selecting it by id on its own does not work, because it requires the host to be confirmed up by a discovery plugin.
264
269
 
265
- → **[Watch a sample scan run end-to-end](https://www.nsauditor.com/ai/docs/sample-scan/)** — synthetic Acme Corp AWS account + home-office router. Real real scan output, no signup required. See the transitive SG chain reachability finding, the multi-region GuardDuty audit, the dnsmasq CVE detection, and what the signed evidence pack actually looks like.
270
+ → **[Watch a sample scan run end-to-end](https://www.nsauditor.com/ai/docs/sample-scan/)** — synthetic Acme Corp AWS account + home-office router. Real real scan output, no signup required. See the transitive SG chain reachability finding, the multi-region GuardDuty audit, the dnsmasq CVE detection, and what the evidence pack actually looks like.
266
271
 
267
272
  → **[Buy NSAuditor AI Enterprise Edition](https://www.nsauditor.com/ai/enterprise/)** · $2k / $5k / $10k+ per year · 5 / 25 / unlimited seats · onboarding call included.
268
273
 
@@ -304,8 +309,8 @@ Results land in `./out/<host>_<timestamp>/`:
304
309
  | 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** |
305
310
  | 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** |
306
311
  | 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** |
307
- | — | 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.) |
308
- | — | **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. |
312
+ | — | 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**.) |
313
+ | — | **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. |
309
314
  | — | **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. |
310
315
  | — | **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. |
311
316
  | — | **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. |
@@ -354,7 +359,7 @@ nsauditor-ai scan --host aws --plugins all --compliance soc2 --aws-region all
354
359
  - **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).
355
360
  - **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.
356
361
 
357
- 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).
362
+ 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).
358
363
 
359
364
  ---
360
365
 
@@ -675,6 +680,10 @@ nsauditor-ai license install <KEY>
675
680
  nsauditor-ai license <--status | --capabilities | --plugins>
676
681
  nsauditor-ai security <set|delete|list|get> <KEY>
677
682
  nsauditor-ai validate
683
+ nsauditor-ai feed bundle --from <dir-of-NVD-feeds> --out <bundle.json.gz> [--kev <f>] [--epss <f>]
684
+ nsauditor-ai feed import --file <feed-or-bundle> [--cache-dir <d>] [--extras-dir <d>] # the feeds you downloaded
685
+ nsauditor-ai compliance <attest|suppress|review|renew|keygen> (Enterprise)
686
+ nsauditor-ai mcp
678
687
  nsauditor-ai --help (or -h, or `help`)
679
688
  nsauditor-ai --version (or -v, or `version`)
680
689
  ```
@@ -846,7 +855,7 @@ explanatory message if it is absent. CLI-only; the MCP surface does not reach th
846
855
  # (pass --email/--role/--team too, or the member prints REPLACE ME placeholders)
847
856
  nsauditor-ai compliance keygen --key ~/.nsauditor/approver.pem --approver "Ann Approver"
848
857
 
849
- # Record an approval; SIGNS it when NSAUDITOR_SIGNING_KEY names a local Ed25519 key (not yet proven)
858
+ # Record an approval; SIGNS it when NSAUDITOR_SIGNING_KEY names a local Ed25519 key (verified per approver holding key material)
850
859
  NSAUDITOR_SIGNING_KEY=~/.nsauditor/approver.pem \
851
860
  nsauditor-ai compliance suppress --suppressions ./out/suppressions.json \
852
861
  --source auth_agent --title-pattern "SSH password authentication enabled" \
@@ -869,8 +878,12 @@ auditor checks an archived approval); a malformed signing key fails the command
869
878
  it warns loudly and tells you to re-approve.
870
879
 
871
880
  ⚠️ **`--flag=value` is not supported**, here or on any flag this CLI has: use `--flag value`.
872
- Ed25519 suppression signing is reachable from Enterprise 0.35.0 and **not yet proven** — its
873
- verification gate has not run against published bytes.
881
+ Ed25519 suppression signing is reachable from Enterprise 0.35.0, and **proven at 0.36.0 for approvers whose registry entry carries key material**.
882
+ Its verification gate ran against the published bytes and passed, tamper negative control included.
883
+ A fingerprint-only registry entry reads `not checked by this report`, which records that no check
884
+ ran and never that one failed.
885
+ Verification runs for approvers whose registry entry carries key material; a fingerprint-only entry
886
+ reads `not checked by this report`, which records that no check ran and never that one failed.
874
887
 
875
888
  **Compliance evidence (`NSAUDITOR_*` — Enterprise, all opt-in):**
876
889
 
@@ -899,15 +912,15 @@ NSAUDITOR_SIGNING_KEY= # A REFERENCE, never key material: keychain:LA
899
912
  # (mode 0600) | awskms:alias/… — see the honesty note below.
900
913
  ```
901
914
 
902
- One of these is deliberately **not** a capability yet and the other is proven on one path only —
903
- saying which is which is the point:
915
+ Both of these are capabilities now, and each was proven later than it was wired — saying exactly
916
+ when, on which delivery vehicle, and for whom is the point:
904
917
 
905
- - **`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
906
- not run against published bytes, so a produced signature is not yet evidence anything vouches
907
- for. The record's `algorithm` and `backend` fields were frozen *before* the first
918
+ - **`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.
919
+ Its verification gate ran against the published bytes and passed, so a produced signature IS
920
+ 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
908
921
  signature could reach a customer archive, because retrofitting that later breaks every
909
922
  auditor holding one.
910
- - **`NSAUDITOR_TSA_URL` is wired, and since 2026-08-07 it is proven — on the npm path only.**
923
+ - **`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.**
911
924
  The live check this note used to say was outstanding has been run: through the installed
912
925
  binary, against a real Time-Stamp Authority, the auditor procedure `openssl ts -verify`
913
926
  returns OK on the compliance report, the scope attestation and the chain of custody; the
@@ -1126,7 +1139,7 @@ NSAuditor AI is built on a **Zero Data Exfiltration (ZDE)** architecture:
1126
1139
  - **No data processing.** Nsasoft US LLC never sees, stores, or processes your scan results.
1127
1140
  - **AI is opt-in.** External AI calls use your own API keys. Redaction runs locally first.
1128
1141
  - **License validation is offline.** JWT signature verified locally with an embedded public key.
1129
- - **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).
1142
+ - **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).
1130
1143
 
1131
1144
  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.
1132
1145
 
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'),
@@ -698,6 +762,12 @@ export async function parseArgs(argv) {
698
762
  suppressionId: str('id'),
699
763
  expiresInDays: str('expires-in-days'),
700
764
  attestationLevel: str('attestation-level'),
765
+ // Pack signing (EE 0.38.0). `--manifest` names ONE chain-of-custody envelope, because a
766
+ // signature is scoped to one framework's envelope and the framework is read from the file
767
+ // being signed — not guessed from a directory that holds seven of them.
768
+ manifest: str('manifest'),
769
+ publicKey: str('public-key') ?? str('public_key'),
770
+ registry: str('registry'),
701
771
  compensatingControl: str('compensating-control'),
702
772
  force: !!get('force'),
703
773
  };
@@ -1041,7 +1111,7 @@ export async function preflightGrcIfRequested(env, opts = {}) {
1041
1111
 
1042
1112
  export async function main() {
1043
1113
  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;
1114
+ 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
1115
 
1046
1116
  // Version: handled before license verification so it works without a key.
1047
1117
  // CE-0.1.30.1 — closes the discovery-flag UX gap where pre-fix
@@ -1108,6 +1178,16 @@ Scan options:
1108
1178
 
1109
1179
  Compliance subcommands:
1110
1180
  nsauditor-ai compliance attest --history <dir> [--framework <fw>] [--window 6m|12m|90d]
1181
+ nsauditor-ai feed bundle --from <dir-of-downloaded-NVD-feeds> --out <bundle.json.gz>
1182
+ [--kev <known_exploited_vulnerabilities.json>]
1183
+ [--epss <epss_scores.csv.gz>]
1184
+ --kev / --epss carry YOUR OWN downloads from CISA and FIRST inside the archive. No
1185
+ exploit data ships with this product; in an air-gapped enclave the archive is the only
1186
+ way across. Both are validated here, on the connected host, where a bad file can still
1187
+ be re-downloaded.
1188
+ nsauditor-ai feed import --file <feed-or-bundle.json.gz> [--cache-dir <dir>] [--append]
1189
+ [--extras-dir <dir>]
1190
+ --extras-dir places any carried KEV/EPSS and prints the env lines to set.
1111
1191
  nsauditor-ai compliance keygen --key <path> [--approver <name>] [--email <e>] [--role <r>]
1112
1192
  [--team <t>] [--force]
1113
1193
  Generate an Ed25519 approval keypair (private 0600) and print
@@ -2172,6 +2252,98 @@ Docs: https://www.nsauditor.com/ai/ | Pricing: https://www.nsauditor.com/ai/
2172
2252
  // veto, framework-alias resolution and every default. CE contributes the flag
2173
2253
  // surface and the exit code — nothing here can drift out of sync with the engine
2174
2254
  // because nothing here decides anything.
2255
+ if (cmd === 'feed') {
2256
+ const sub = process.argv[3];
2257
+
2258
+ // ── THE AIR-GAP HAND-CARRY PIPELINE (EE F1) ────────────────────────────
2259
+ // `bundle` merges the NVD feeds an operator downloaded on a CONNECTED host into one portable
2260
+ // archive; `import` consumes it on the ISOLATED host through EE's `importFeed()`. Same
2261
+ // THIN-FORWARD discipline as `compliance`: EE owns validation, the refusals and every
2262
+ // decision; CE contributes a flag surface and an exit code.
2263
+ //
2264
+ // ⚠️ `feed bundle` BUNDLES THE FEEDS YOU DOWNLOADED — never "your database". The offline
2265
+ // store is a lossy derivation of an NVD feed (configurations flattened to matches, references
2266
+ // dropped, rejected and zero-CPE CVEs discarded at import), so it cannot be turned back into
2267
+ // one. Bundling the SOURCE keeps `importFeed()` the single ingest path across the air-gap
2268
+ // trust boundary — its gzip-bomb cap covers the bundle for free — and mints no file format
2269
+ // we would then own as a frozen compatibility surface.
2270
+ //
2271
+ // ⚠️ NOTHING HERE FLIPS A CLAIM. `feed-import-cli`, `offline-install-tarball`,
2272
+ // `install-script` and `airgap-deployment` stay withdrawn until the trio publishes AND the
2273
+ // NIC-down gate passes on the BUILT artifacts. Publishing a surface is not proving it.
2274
+ if (sub === 'bundle' || sub === 'import') {
2275
+ let ee;
2276
+ try {
2277
+ ee = await import('@nsasoft/nsauditor-ai-ee');
2278
+ } catch {
2279
+ console.error(`feed ${sub} requires the Enterprise package (@nsasoft/nsauditor-ai-ee).`);
2280
+ process.exit(2);
2281
+ }
2282
+ const F = feedArgs ?? {};
2283
+ try {
2284
+ if (sub === 'bundle') {
2285
+ const out = await ee.bundleFeedCommand({
2286
+ from: F.from, out: F.out, kev: F.kev, epss: F.epss,
2287
+ });
2288
+ console.log(`bundled ${out.vulnerabilities} CVE(s) from ${out.sourceFiles} feed file(s)`
2289
+ + `${out.duplicatesDropped ? ` (${out.duplicatesDropped} duplicate(s) dropped)` : ''}`
2290
+ + ` -> ${out.out} (${(out.bytes / 1048576).toFixed(1)} MB)`);
2291
+ // ⚠️ ABSENCE IS STATED, NEVER OMITTED. An operator who is not told the bundle carries no
2292
+ // KEV discovers it from empty scan output on the isolated side, where `kev: null` on
2293
+ // every finding is indistinguishable from a host with nothing exploited.
2294
+ if (out.carriedNote) console.log(` exploit intel: ${out.carriedNote}`);
2295
+ } else {
2296
+ const out = await ee.importFeedCommand({
2297
+ file: F.file, cacheDir: F.cacheDir || undefined, append: F.append === true,
2298
+ extrasDir: F.extrasDir || undefined,
2299
+ });
2300
+ console.log(`imported ${out.imported} CVE(s), skipped ${out.skipped}, errors ${out.errors}`);
2301
+ // ⚠️ THE SKIP SPLIT PRINTS HERE BECAUSE THE DATA-LOSS TICKET ORIGINATES HERE.
2302
+ // On a real NVD year file ~24% of records are skipped (measured on the 2024 feed:
2303
+ // 39,219 in, 29,866 imported, 9,353 skipped). An operator reading a bare "skipped 9353"
2304
+ // files that as data loss, and a docs-only note is read AFTER the ticket, not before.
2305
+ // So the reasons are named at the terminal, in one line, with what they mean.
2306
+ if (out.skipped > 0) {
2307
+ const parts = [];
2308
+ if (out.skippedRejected) parts.push(`${out.skippedRejected} withdrawn by NVD (Rejected)`);
2309
+ if (out.skippedNoCpe) parts.push(`${out.skippedNoCpe} with no CPE match data`);
2310
+ if (out.skippedMalformed) parts.push(`${out.skippedMalformed} malformed (no CVE id)`);
2311
+ if (parts.length) {
2312
+ console.log(` skipped breakdown: ${parts.join(', ')}`);
2313
+ console.log(' Skips are expected — NVD publishes withdrawn CVEs and entries with no '
2314
+ + 'CPE data, and neither can ever match a scan. This is not data loss.'
2315
+ + (out.skippedMalformed
2316
+ ? ' ⚠️ Malformed records are NOT expected: re-download the feed and check its checksum.'
2317
+ : ''));
2318
+ }
2319
+ }
2320
+ // ⚠️ D-95.3: THE ENV LINES ARE THE DELIVERABLE, NOT A NICETY. The product reads these
2321
+ // stores from two environment variables and ships no exploit data of its own. A file
2322
+ // placed on the isolated host that the operator is never told to point at leaves every
2323
+ // finding reading `kev: null` — which is exactly what a host with nothing exploited
2324
+ // reports. Printing the resolved paths is what turns a placed file into a used one.
2325
+ if (out.extrasPlaced?.length) {
2326
+ console.log(`\nplaced ${out.extrasPlaced.map((p) => p.kind).join(' + ')} — set these `
2327
+ + 'before scanning:');
2328
+ console.log(out.envLines);
2329
+ } else if (out.extrasNote) {
2330
+ console.log(`\n${out.extrasNote}`);
2331
+ }
2332
+ }
2333
+ process.exit(0);
2334
+ } catch (err) {
2335
+ // Refusals stay loud: an empty source set, a file that is not an NVD feed, a gzip over
2336
+ // the bomb cap, a missing path. Every one of them is the operator learning something
2337
+ // true on the host that can still fix it.
2338
+ console.error(`Fatal: ${err.message}`);
2339
+ process.exit(2);
2340
+ }
2341
+ }
2342
+
2343
+ console.error('Usage: nsauditor-ai feed <bundle|import> …');
2344
+ process.exit(2);
2345
+ }
2346
+
2175
2347
  if (cmd === 'compliance') {
2176
2348
  const sub = process.argv[3];
2177
2349
 
@@ -2188,7 +2360,8 @@ Docs: https://www.nsauditor.com/ai/ | Pricing: https://www.nsauditor.com/ai/
2188
2360
  // the three-part gate runs against the published registry bytes and the hedges
2189
2361
  // flip at N+1. If you are reading this while editing marketing copy, the answer
2190
2362
  // is still "not yet".
2191
- if (sub === 'suppress' || sub === 'review' || sub === 'renew' || sub === 'keygen') {
2363
+ if (sub === 'suppress' || sub === 'review' || sub === 'renew' || sub === 'keygen'
2364
+ || sub === 'sign-pack' || sub === 'verify-pack') {
2192
2365
  let ee;
2193
2366
  try {
2194
2367
  ee = await import('@nsasoft/nsauditor-ai-ee');
@@ -2215,13 +2388,65 @@ Docs: https://www.nsauditor.com/ai/ | Pricing: https://www.nsauditor.com/ai/
2215
2388
  + 'signing happens here, at approval time.');
2216
2389
  process.exit(0);
2217
2390
  }
2391
+ if (sub === 'sign-pack' || sub === 'verify-pack') {
2392
+ // ⚠️ THIN FORWARD, LIKE EVERY VERB ABOVE IT. EE decides what a manifest is, whether the
2393
+ // key may be used, what gets written and what every verdict means. This block builds an
2394
+ // options object, prints what EE returns, and exits.
2395
+ if (!A.manifest) {
2396
+ console.error(`compliance ${sub} requires --manifest <scan_chain_of_custody_<framework>.json>`);
2397
+ process.exit(2);
2398
+ }
2399
+ if (sub === 'sign-pack') {
2400
+ const out = await ee.signPackCommand({ manifestPath: A.manifest }, {
2401
+ // Same provenance discipline as `suppress`: only THIS line knows the reference came
2402
+ // from an environment variable, so only this line can make a refusal name it.
2403
+ signingKeyRaw: process.env.NSAUDITOR_SIGNING_KEY || undefined,
2404
+ signingKeyFrom: 'environment',
2405
+ attestationLevel: A.attestationLevel || undefined,
2406
+ });
2407
+ console.log(`signed ${path.basename(A.manifest)} — ${out.record.subject.framework} / ${out.record.subject.scanId}`);
2408
+ console.log(` wrote ${out.recordPath}`);
2409
+ console.log(` wrote ${out.payloadPath} (the exact bytes signed — an auditor verifies THIS file)`);
2410
+ console.log(' this signature covers THIS framework\'s envelope and the four artifacts it '
2411
+ + 'enumerates — not the directory, and not the pack.');
2412
+ process.exit(0);
2413
+ }
2414
+ let publicKeyPem;
2415
+ if (A.publicKey) {
2416
+ try {
2417
+ publicKeyPem = await fsp.readFile(A.publicKey, 'utf8');
2418
+ } catch (e) {
2419
+ console.error(`cannot read --public-key ${A.publicKey}: ${e.code ?? e.message}`);
2420
+ process.exit(2);
2421
+ }
2422
+ }
2423
+ // Two arms, and EE refuses both-at-once rather than applying a silent precedence — an
2424
+ // operator must be able to tell which trust anchor was actually consulted.
2425
+ const out = await ee.verifyPackCommand({
2426
+ manifestPath: A.manifest, publicKeyPem, registryPath: A.registry || undefined,
2427
+ });
2428
+ for (const line of out.lines) console.log(line);
2429
+ // ⚠️ EE OWNS THE EXIT CODE. 0 verified · 1 a violation · 2 could not measure — and the
2430
+ // third is NOT a failure. Collapsing it here would tell an auditor their evidence failed
2431
+ // verification when the truth is that nothing was checked.
2432
+ process.exit(out.exitCode);
2433
+ }
2218
2434
  if (sub === 'review') {
2219
2435
  const { rows, summary } = await ee.reviewCommand({ rootDir: complianceHistory });
2220
2436
  console.log(`Suppressions: ${summary.total} total · ${summary.active} active · `
2221
2437
  + `${summary.approaching} approaching expiry · ${summary.expired} expired · `
2222
2438
  + `${summary.no_expiry} without expiry`);
2223
2439
  for (const r of rows) {
2224
- console.log(` [${r.expiryStatus}] ${r.suppressionId ?? '(no-id)'} — ${r.approver ?? '(no approver)'}`);
2440
+ // ⚠️ READ THE FIELDS EE ACTUALLY EMITS. `reviewCommand` returns rows shaped
2441
+ // { scanId, file, suppression, status, daysRemaining } — the expiry class is `status`
2442
+ // (NOT `expiryStatus`) and the id/approver live INSIDE `suppression`. Reading the wrong
2443
+ // names printed `[undefined] (no-id) — (no approver)` for EVERY row, including records
2444
+ // `compliance suppress` had just written itself. The SUMMARY line above was correct
2445
+ // throughout, and that is what hid it: a correct aggregate over broken rows.
2446
+ const sup = r.suppression ?? {};
2447
+ const who = typeof sup.approver === 'string' ? sup.approver : sup.approver?.name;
2448
+ console.log(` [${r.status ?? 'unknown'}] ${sup.id ?? '(no-id)'} — ${who ?? '(no approver)'}`
2449
+ + (sup.status ? ` · ${sup.status}` : ''));
2225
2450
  }
2226
2451
  process.exit(0);
2227
2452
  }
@@ -2605,6 +2830,12 @@ const _isEntrypoint = (() => {
2605
2830
  })();
2606
2831
  if (_isEntrypoint) {
2607
2832
  main().catch((err) => {
2833
+ // A flag-shape refusal is an operator error, not a crash: name it and exit 2, this CLI's
2834
+ // "could not do what you asked" code. Everything else keeps the stack and exit 1.
2835
+ if (err?.code === 'EFLAGSHAPE') {
2836
+ console.error(`[ERROR] ${err.message}`);
2837
+ process.exit(2);
2838
+ }
2608
2839
  console.error(err?.stack || err);
2609
2840
  process.exit(1);
2610
2841
  });
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.41",
3
+ "version": "0.2.43",
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(),