nsauditor-ai 0.2.36 → 0.2.37

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
@@ -13,10 +13,12 @@ A modular, AI-assisted network security audit platform that scans, understands,
13
13
 
14
14
  NSAuditor AI is the open-source core of a privacy-first security intelligence platform built by [Nsasoft US LLC](https://www.nsauditor.com/ai/). It orchestrates 27 specialized scanning plugins against target hosts, fuses their results through an intelligent concluder, and optionally produces AI-powered vulnerability reports — all running entirely on your machine.
15
15
 
16
- **Zero Data Exfiltration by design.** NSAuditor AI works fully offline. AI analysis, CVE correlation, and continuous monitoring all happen locally. External calls (to AI APIs, NVD, etc.) are opt-in and use your own API keys. We never see your scan data.
16
+ **Zero Data Exfiltration by design — and stated precisely.** We never see your scan data: no customer data is collected, transmitted, or stored by Nsasoft US LLC, and the product has no telemetry or phone-home endpoint. Scanning, analysis, license verification and report generation all run on your machine. Two clarifications that matter operationally: AI enrichment is **opt-in** and uses your own API keys (point it at a local Ollama and nothing leaves the host), while **CVE correlation queries NIST's public NVD API by default** — set `NSAUDITOR_OFFLINE_ONLY=1` with a local NVD store to make it fully local, which reports an explicit coverage gap instead of a silent clean.
17
17
 
18
18
  ## What's New
19
19
 
20
+ **Three flags, one new command, and a startup veto that stops swallowing itself (CE 0.2.37 / Enterprise 0.2.37 + 0.33.0).** Paired with Enterprise 0.33.0, and **this CE is required by it** — `compliance attest`, `--sla-policy <file>` and `--compliance-history <dir>` live here, so the Enterprise peer floor is raised to 0.2.37. `compliance attest` produces a Type II recurring-scan attestation over a directory of prior scans; the two flags reach the SLA/MTTR engine, which had been wired inside the compliance phase behind options nothing set. A startup posture preflight now refuses a configuration contradiction — `NSAUDITOR_OFFLINE_ONLY=1` together with a configured outbound path — with exit 2, at every tier and every command. And the bare `catch {}` around the Enterprise stage is split: "Enterprise is not installed" stays silent, but "Enterprise ran and threw" now names the lost stage on stderr instead of exiting 0 with no compliance report and no output. Also corrected on this frozen page: the MCP tool tables listed seven tools that are not registered and omitted two that are; WORM evidence storage was sold with a ✅ though its writer has no caller; and `--help` told operators to select plugin 1023 with `--plugins`, which cannot work — it needs a discovery plugin to confirm the host is up first.
21
+
20
22
  **The scan summary stops hiding its own scope limits, and a new tool ends matrix guesswork (CE 0.2.36 / Enterprise 0.32.11).** `scan_cloud`'s markdown rendered `CRITICAL · HIGH · MEDIUM · LOW · PASS` — **INFO was not a column** — so on a real AWS scan 55 of 62 INFO records appeared in no summary anywhere, several of them declarations of what was **not** assessed. INFO is now a column and rolls up by category, and scope boundaries get their own `[🔎 SCOPE NOT ASSESSED]` channel: surface nobody assessed must not read like surface assessed and found clean. **New MCP tool `compliance_matrix`** returns the shipped coverage matrix for any of the seven frameworks, derived from the framework maps at call time and failing closed rather than returning an empty one. `pdfExport` is withdrawn (registered and minted, implemented nowhere), and **every capability now ships a written description** — a bare flag name is a claim with no text behind it. `--watch` is described honestly, including the part the obvious correction got wrong: each tick writes that tick's artifacts; what is missing is any relation between them.
21
23
 
22
24
  **The dependency gate starts measuring what you install (CE 0.2.35 / Enterprise 0.32.10).** Paired with Enterprise 0.32.10 — **no CE behaviour change**. On the Enterprise side the advisory gate had been auditing the maintainer's working tree while calling it the production closure; it now packs the tarball, installs it the way you would, and audits **that**, and it will not report a clean result until it has proved an advisory database actually answered. The two trees shared 5 of 26 advisory packages and **zero** high-severity ones. Also: the SOC 2 matrix is enumerated in full (**10 / 4 / 37 = 51** — completeness, no control moved), and the Type II docs now say which mechanisms are reachable versus roadmap.
@@ -61,7 +63,7 @@ NSAuditor AI is available in three editions: Community (free, MIT-licensed, no r
61
63
 
62
64
  If you're heading into a **SOC 2, HIPAA, NIST CSF 2.0, PCI DSS, ISO 27001, CIS Controls v8, or GDPR Article 32 audit** — or need to satisfy customer security questionnaires citing those frameworks, or an IG1 attestation for cyber-insurance renewal — Enterprise turns scan output into **auditor-ready evidence packs** that pass institutional scrutiny:
63
65
 
64
- - ☁️ **28 cloud plugins** across AWS / Azure / GCP — find the configuration risks an auditor will flag, before they do (CloudTrail integrity, KMS custody, S3 Object Lock, IAM shadow-admin paths, GCP IAM impersonation chains, Azure RBAC sprawl, and more)
66
+ - ☁️ **28 enterprise plugins — 27 cloud auditors across AWS / Azure / GCP, plus **`1023 Zero Trust Assessment`**, which scores zero-trust posture (segmentation, encryption-in-transit, identity, lateral-movement risk) from a **network-host** scan** — find the configuration risks an auditor will flag, before they do (CloudTrail integrity, KMS custody, S3 Object Lock, IAM shadow-admin paths, GCP IAM impersonation chains, Azure RBAC sprawl, and more)
65
67
  - 📋 **7 compliance frameworks shipped** — generate any combination from a single scan:
66
68
  - **SOC 2** (AICPA TSC 2017) — 10 fully-covered + 4 partial controls
67
69
  - **HIPAA Security Rule §164.312** — 7 covered + 3 partial Technical Safeguards; **Zero BAA required** (ePHI never leaves your infrastructure)
@@ -70,10 +72,10 @@ If you're heading into a **SOC 2, HIPAA, NIST CSF 2.0, PCI DSS, ISO 27001, CIS C
70
72
  - **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
71
73
  - **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)
72
74
  - **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
73
- - 🔐 **Cryptographically signed evidence** — SHA-256 chain-of-custody + RFC 3161 trusted timestamps + Ed25519 suppression signing. Non-repudiation, not just integrity. Auditors can verify offline.
74
- - 🏛️ **Zero Data Exfiltration architecture** — your scan data never leaves your infrastructure. Air-gapped deployment supported. AI analysis happens locally (Ollama) or via your own API keys. Important for PCI DSS CDE-isolation threat models.
75
+ - 🔐 **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, and have never been exercised against a real one. **Ed25519 suppression signing is implemented in the package but not reachable from any shipped entry point** — no first-party run signs a suppression, so do not plan around it.
76
+ - 🏛️ **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.
75
77
  - 🔗 **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.
76
- - 🗄️ **WORM evidence storage** — S3 Object Lock COMPLIANCE-mode for SEC Rule 17a-4(f) / FINRA 4511 retention compliance
78
+ - 🗄️ **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
77
79
  - 📊 **SLA / MTTR tracking + recurring-scan attestation** — the **Type II operating-effectiveness evidence** auditors actually demand (not just point-in-time snapshots)
78
80
  - 🎯 **11 adversarial-audit Claude Code skills** authored per the Per-Framework Adversarial-Audit Skill Pairing institutional pattern — Phase-4 Compliance/GRC chain 8-of-8 COMPLETE for all shipped frameworks (SOC 2 + HIPAA + NIST CSF + PCI DSS + ISO 27001 + CIS Controls v8 + GDPR Article 32 + GRC connector)
79
81
 
@@ -104,7 +106,7 @@ How Marketplace fulfillment works (ZDE and air-gap preserved — no runtime AWS
104
106
  | Risk scoring + prioritization | — | ✅ | ✅ |
105
107
  | Parallel analysis agents | — | ✅ | ✅ |
106
108
  | **Enterprise — cloud scanning** | | | |
107
- | 28 cloud plugins (AWS / Azure / GCP) | — | — | ✅ |
109
+ | 28 enterprise plugins — 27 cloud (AWS / Azure / GCP) + 1 network-scan zero-trust check | — | — | ✅ |
108
110
  | Zero Trust assessment | — | — | ✅ |
109
111
  | **Enterprise — compliance (7 frameworks)** | | | |
110
112
  | SOC 2 (AICPA TSC 2017) — 10 covered + 4 partial controls | — | — | ✅ |
@@ -116,16 +118,16 @@ How Marketplace fulfillment works (ZDE and air-gap preserved — no runtime AWS
116
118
  | **GDPR Article 32 (Security of Processing)** — Art. 32 infrastructure substrate (4 covered + 5 partial + 2 OOS / 11 sub-measure units); **not GDPR compliance** · Art. 83(4) lower fine tier | — | — | ✅ |
117
119
  | Multi-framework `--compliance soc2,hipaa,nist-csf,pci-dss,iso-27001,cis-v8,gdpr` from one scan | — | — | ✅ |
118
120
  | **Enterprise — auditor-grade evidence** | | | |
119
- | Signed evidence packs (SHA-256 + RFC 3161 timestamps) | — | — | ✅ |
120
- | Ed25519 suppression signing | — | — | ✅ |
121
+ | Evidence packs with SHA-256 chain-of-custody (RFC 3161 timestamps opt-in, unproven against a real TSA) | — | — | ✅ |
122
+ | Suppression workflow with approver identity verification (Ed25519 SIGNING is built, not reachable) | — | — | ✅ |
121
123
  | Chain-of-custody manifests | — | — | ✅ |
122
124
  | SLA / MTTR tracking + compensating controls | — | — | ✅ |
123
125
  | Recurring-scan attestation (Type II operating-effectiveness) | — | — | ✅ |
124
- | WORM evidence storage (S3 Object Lock — SEC 17a-4 / FINRA 4511) | — | — | ✅ |
126
+ | WORM POSTURE VALIDATION on your own buckets (S3 Object Lock — SEC 17a-4 / FINRA 4511 substrate). The engine WRITING into the immutable store is built, **not reachable** | — | — | ✅ |
125
127
  | **Enterprise — integration + deployment** | | | |
126
128
  | GRC connectors — Vanta + Drata + Secureframe push (scan-time, opt-in) | — | — | ✅ |
127
- | Tabletop simulation + SIEM correlation | — | — | ✅ |
128
- | Air-gapped deployment | — | — | ✅ |
129
+ | Tabletop simulation + SIEM correlation — built, **not reachable** (no caller) | — | — | 🚧 |
130
+ | 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.) | — | — | ✅ |
129
131
 
130
132
  **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.
131
133
 
@@ -265,7 +267,7 @@ Results land in `./out/<host>_<timestamp>/`:
265
267
 
266
268
  ### Pro/Enterprise Plugins (via @nsasoft/nsauditor-ai-ee)
267
269
 
268
- **28 enterprise plugins** across AWS, GCP, and Azure substrate audits — 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.
270
+ **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.
269
271
 
270
272
  → **[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.
271
273
 
@@ -309,8 +311,8 @@ Results land in `./out/<host>_<timestamp>/`:
309
311
  | 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** |
310
312
  | 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** |
311
313
  | 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** |
312
- | — | SOC 2 Compliance Engine | Enterprise | AICPA TSC 2017 mapping (10 covered + 4 partial controls), chain-of-custody, RFC 3161 timestamps, suppression workflow with Ed25519 signing. |
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 institutional-grade evidence infrastructure as SOC 2 (chain-of-custody, RFC 3161 timestamps, Ed25519 suppression signing). 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. |
314
+ | — | SOC 2 Compliance Engine | Enterprise | AICPA TSC 2017 mapping (10 covered + 4 partial controls), SHA-256 chain-of-custody, opt-in RFC 3161 timestamps, and the suppression workflow. (Ed25519 SIGNING of suppressions is built but unreachable — the workflow ships, the signature does not.) |
315
+ | — | **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 built but not reachable). 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. |
314
316
  | — | **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. |
315
317
  | — | **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. |
316
318
  | — | **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. |
@@ -319,7 +321,7 @@ Results land in `./out/<host>_<timestamp>/`:
319
321
  | — | SLA & MTTR Tracking | Enterprise | Per-severity SLA targets, compensating-control flow, finding lifecycle, Type II rolling-quarter cadence. |
320
322
  | — | Recurring-Scan Attestation | Enterprise | Multi-scan chronological matrix, cadence gap detection, scope-drift surface (CC8.1). |
321
323
  | — | GRC Platform Connector | Enterprise | Vanta + Drata + Secureframe connectors — scan-time push (opt-in), retry/backoff, deterministic idempotency, rate-limit handling, circuit breaker, foreign-token detection, ZDE egress redaction. See [GRC Connectors](#grc-connectors-vanta-drata-secureframe). |
322
- | — | WORM Evidence Storage | Enterprise | S3 Object Lock COMPLIANCE-mode + resource redaction + SHA-256 manifest. SEC 17a-4 / FINRA 4511 retention-compatible. |
324
+ | — | WORM Posture Validation | Enterprise | Reads S3 Object Lock configuration on your trail buckets + resource redaction + SHA-256 manifest — substrate for a SEC 17a-4 / FINRA 4511 claim. The archive WRITER (`prepareArchive`) is built and **not reachable**: no caller, so no first-party run writes into an immutable store. |
323
325
  | — | Tabletop Simulation | Enterprise | Probe-event manifest + SIEM detection correlation, configurable coverage bands (Type II / High-Assurance presets). |
324
326
 
325
327
  **Running EE plugins** (after `nsauditor-ai license install <key>`):
@@ -359,7 +361,7 @@ nsauditor-ai scan --host aws --plugins all --compliance soc2 --aws-region all
359
361
  - **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).
360
362
  - **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.
361
363
 
362
- The auditor evidence pack is emitted under `out/` — cover-page Scope Attestation, SHA-256 chain-of-custody sidecars, RFC 3161 trusted-timestamps, suppression workflow, identity verification. EE is available at [`www.nsauditor.com/ai/pricing`](https://www.nsauditor.com/ai/pricing).
364
+ 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`; never run against a real authority). Ed25519 suppression SIGNING is built and not reachable. EE is available at [`www.nsauditor.com/ai/pricing`](https://www.nsauditor.com/ai/pricing).
363
365
 
364
366
  ---
365
367
 
@@ -438,32 +440,32 @@ nsauditor-ai-mcp
438
440
  npx nsauditor-ai-mcp
439
441
  ```
440
442
 
441
- **CE Tools:**
443
+ The server registers **seven** tools and lists all seven to every client. The licence is
444
+ checked when a tool is CALLED, not when the list is served — so an unlicensed call returns
445
+ an explicit 🔒 refusal naming the tier it needs, never an empty result that reads as "nothing
446
+ found". A higher tier does not add tools; it unlocks the ones already in the list.
447
+
448
+ **Available on every tier:**
442
449
 
443
450
  | Tool | Purpose |
444
451
  |---|---|
445
- | `scan_host` | Run full scan against a host with plugin selection |
446
- | `list_plugins` | List available scanner plugins with metadata |
452
+ | `scan_host` | Run a full plugin scan against a host — service detection, OS fingerprint, structured findings |
453
+ | `list_plugins` | List available scanner plugins with their IDs, priorities and requirements |
454
+ | `compliance_matrix` | Return the shipped coverage matrix for a framework — Covered / Partial / Out of scope, with the per-group out-of-scope reasons. **Needs the Enterprise pack installed**: the matrices are its data, so on a Community-only install this fails closed with an install instruction rather than returning an empty matrix (an empty matrix is what gets filled in with a guess) |
447
455
 
448
- **Pro Tools** (requires license key + `@nsasoft/nsauditor-ai-ee`):
456
+ **Unlocked by Pro** (licence key + `@nsasoft/nsauditor-ai-ee`):
449
457
 
450
458
  | Tool | Purpose |
451
459
  |---|---|
452
- | `probe_service` | Deep scan a specific port/service |
453
- | `get_vulnerabilities` | Query CVEs by CPE string |
454
- | `risk_summary` | Prioritized risk overview from last scan |
455
- | `scan_compare` | Diff two scan results with risk weighting |
456
- | `save_finding` | Save a validated finding to the finding queue (schema-checked) |
460
+ | `probe_service` | Run one specific plugin against a single `host:port` |
461
+ | `get_vulnerabilities` | Look up known CVEs for a CPE (Common Platform Enumeration) string via the NVD API |
457
462
 
458
- **Enterprise Tools** (requires Enterprise license):
463
+ **Unlocked by Enterprise:**
459
464
 
460
465
  | Tool | Purpose |
461
466
  |---|---|
462
467
  | `scan_cloud` | Audit one or more cloud accounts (AWS / GCP / Azure) using server-configured credentials; no network host. "Audit my AWS account" / "Audit my AWS and Azure accounts". |
463
- | `start_assessment` | Multi-host orchestrated assessment workflow |
464
- | `prioritize_risks` | Cross-host risk prioritization |
465
- | `compliance_check` | Compliance mapping with gap analysis |
466
- | `export_report` | Generate formatted compliance report |
468
+ | `get_findings` | Drill into the findings of the most recent `scan_cloud` run (per-provider session cache — not live state) |
467
469
 
468
470
  > `scan_cloud` runs the requested clouds' plugins **concurrently** (default up to 20 at once, 25s per-plugin
469
471
  > timeout) so a full multi-service cloud audit completes within Claude Desktop's ~60s tool-call limit. Tune with
@@ -819,6 +821,66 @@ NSAUDITOR_LICENSE_KEY=pro_eyJhbGci... # Pro or Enterprise license key
819
821
  NSAUDITOR_PLUGIN_PATH= # Additional plugin directories (colon-separated)
820
822
  ```
821
823
 
824
+ **Longitudinal compliance evidence (Enterprise CLI, new alongside the variables below):**
825
+
826
+ A single scan shows configuration at an instant. A SOC 2 Type II auditor — and the ISO
827
+ surveillance cadence, PCI Appendix E sampling, HIPAA §164.312(b) and GDPR Art. 32(1)(d)
828
+ equivalents — asks whether controls operated over a *period*. Every scan has always written
829
+ a per-framework `scan_attestation_<framework>.json`, so a history you already have is
830
+ aggregatable today:
831
+
832
+ ```bash
833
+ # Track remediation SLA / MTTR against a directory of prior scans
834
+ nsauditor-ai scan --host aws --compliance soc2 \
835
+ --compliance-history ./out --sla-policy ./my-sla.json
836
+
837
+ # Roll a directory of prior scans up into a multi-period attestation
838
+ nsauditor-ai compliance attest --history ./out --framework soc2 --window 12m
839
+ ```
840
+
841
+ Three things the roll-up states about itself rather than leaving you to discover:
842
+ discovery reads **one** directory level (`<history-root>/<scan-id>/`), scans whose own
843
+ attestation is marked `REPORT INVALID FOR AUDIT` are **counted and named** rather than
844
+ averaged into a clean verdict, and an empty history exits **3** with status `no_evidence` —
845
+ absence of evidence is a finding, not a pass.
846
+
847
+ **Compliance evidence (`NSAUDITOR_*` — Enterprise, all opt-in):**
848
+
849
+ Every variable added from Enterprise 0.33.0 onward carries the `NSAUDITOR_` prefix, so a
850
+ deploy can tell at a glance which environment variables belong to this product. The
851
+ unprefixed families above (`AI_*`, `OPENAI_*`, `NVD_API_KEY`, `COMPLIANCE_GRC_*`) keep
852
+ working exactly as they do today — prefixed aliases arrive in a later minor, and a silent
853
+ rename will never happen.
854
+
855
+ ```ini
856
+ NSAUDITOR_OFFLINE_ONLY=1 # Exact match on '1'. Forbids outbound: CVE matching reads a local
857
+ # NVD store and reports an explicit coverage gap rather than a silent
858
+ # clean. Also VETOES the two settings below — configuring an offline
859
+ # posture and an outbound destination together is a startup error,
860
+ # never a quiet downgrade to weaker evidence.
861
+ NSAUDITOR_TSA_URL= # RFC 3161 Time-Stamp Authority endpoint. NO DEFAULT, EVER — unset
862
+ # means the feature is absent, not "use a vendor default".
863
+ NSAUDITOR_TSA_CERT_CHAIN= # Path to the TSA certificate chain (PEM), for offline verification
864
+ NSAUDITOR_TSA_POLICY_OID= # Optional policy OID to request from the TSA
865
+ NSAUDITOR_IDENTITY_REGISTRY= # Path to the approver identity registry JSON. Binds the humans named
866
+ # in your suppression file to identities an assessor can check.
867
+ # Template ships at data/compliance/identity_registry.json (Enterprise).
868
+ NSAUDITOR_SIGNING_KEY= # A REFERENCE, never key material: keychain:LABEL | /path/to/key.pem
869
+ # (mode 0600) | awskms:alias/… — see the honesty note below.
870
+ ```
871
+
872
+ Two of these are deliberately **not** capabilities yet, and saying so here is the point:
873
+
874
+ - **`NSAUDITOR_SIGNING_KEY` is groundwork, and less wired than this note used to say.** EE 0.33.0 stopped parsing it at option-resolution time — a malformed value used to abort the EE stage for a setting nothing reads — so it is now carried through unparsed; `parseSigningRef` / `resolveSigner` hold the parse and the offline veto at the point a key would be used, and no
875
+ shipped entry point signs a suppression, so setting it changes nothing today. It exists
876
+ now because the signature record's `algorithm` and `backend` fields had to be frozen
877
+ *before* the first signature reaches a customer archive — retrofitting that later breaks
878
+ every auditor holding one.
879
+ - **`NSAUDITOR_TSA_URL` is wired but unproven.** The request is built and sent; it has
880
+ **never been exercised against a real Time-Stamp Authority** — every test to date injects
881
+ a fake, which proves the plumbing and not the capability. Treat trusted timestamping as
882
+ roadmap until that live check is published.
883
+
822
884
  **Security overrides:**
823
885
 
824
886
  ```ini
@@ -1016,7 +1078,7 @@ NSAuditor AI is built on a **Zero Data Exfiltration (ZDE)** architecture:
1016
1078
  - **No data processing.** Nsasoft US LLC never sees, stores, or processes your scan results.
1017
1079
  - **AI is opt-in.** External AI calls use your own API keys. Redaction runs locally first.
1018
1080
  - **License validation is offline.** JWT signature verified locally with an embedded public key.
1019
- - **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, and the only other outbound paths — AI enrichment and the GRC push — are opt-in and off by default. Populating the local NVD store is the operator's; no feed data is delivered with the product.
1081
+ - **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.
1020
1082
 
1021
1083
  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.
1022
1084
 
package/cli.mjs CHANGED
@@ -660,6 +660,27 @@ export async function parseArgs(argv) {
660
660
  // (override-on) for this scan; `--aws-profile <name>` selects a named profile
661
661
  // from ~/.aws/credentials. Both are wired into main() via resolveScanEnv().
662
662
  // get() returns undefined (absent), true (value-less flag), or the string value.
663
+ // EE 0.33.0 (N2): longitudinal compliance evidence. Both are forwarded verbatim to
664
+ // EE, which owns every default and every validation — CE never parses or defaults
665
+ // them. Tri-state like `--env` and NOT the `--compliance-scope` collapse-to-null
666
+ // form: a value-less `--sla-policy` must be an error in main(), because silently
667
+ // nulling it is precisely the quiet skip this feature exists to remove.
668
+ // `--history` is the spelling the `compliance attest` subcommand documents and the one
669
+ // an operator types there; `--compliance-history` is the scan-path spelling. Both
670
+ // resolve to the same value because they name the same directory. Accepting only one
671
+ // is how a DOCUMENTED flag becomes a flag that does nothing — and that is exactly what
672
+ // shipped in the first draft here, caught by RUNNING the command rather than reading
673
+ // it. The EE guard that catches this class is scoped to /--compliance[A-Za-z-]*/, and
674
+ // `--history` does not match it.
675
+ const complianceHistoryVal = get('compliance-history') || get('compliance_history') || get('history');
676
+ args.complianceHistory = complianceHistoryVal === undefined ? undefined : complianceHistoryVal;
677
+ const slaPolicyVal = get('sla-policy') || get('sla_policy');
678
+ args.slaPolicy = slaPolicyVal === undefined ? undefined : slaPolicyVal;
679
+ const attestWindowVal = get('window');
680
+ args.attestWindow = (attestWindowVal && attestWindowVal !== true) ? attestWindowVal : null;
681
+ const frameworkVal = get('framework');
682
+ args.framework = (frameworkVal && frameworkVal !== true) ? frameworkVal : null;
683
+
663
684
  const envVal = get('env');
664
685
  args.env = envVal === undefined ? undefined : envVal; // string path, or true if value-less
665
686
  const awsProfileVal = get('aws-profile');
@@ -740,21 +761,47 @@ async function scanSingleHost(pm, host, plugins, opts, promptMode) {
740
761
  // AWS accounts. EE 0.3.2 + CE 0.1.30 ship as a paired release; mixing
741
762
  // versions is supported but the EE-side cloud-harvest behavior requires
742
763
  // CE-0.1.30+ to take effect.
764
+ // ⚠️ THE IMPORT AND THE CALL GET SEPARATE CATCHES, AND THE SPLIT IS THE POINT.
765
+ // A single bare `catch {}` around both made "EE is not installed" — a legitimate,
766
+ // expected, silent skip for every Community user — indistinguishable from "EE ran and
767
+ // THREW", which silently deletes the deliverable: the scan exits 0, writes no compliance
768
+ // report, and prints nothing. That is the false-clean shape, and it is the channel a
769
+ // whole class of defects has reached production through (a malformed env var did exactly
770
+ // this until 0.33.0 moved the parse). Absence stays silent; failure is named and the run
771
+ // is marked, because an operator who asked for `--compliance` and got no report must not
772
+ // have to diff directories to discover it.
773
+ let ee = null;
774
+ try {
775
+ ee = await import('@nsasoft/nsauditor-ai-ee');
776
+ } catch { /* EE not installed — CE proceeds unchanged. The ONLY silent case. */ }
743
777
  try {
744
- const { enrichScan } = await import('@nsasoft/nsauditor-ai-ee');
745
- const eeEnrichment = await enrichScan(conclusion, {
778
+ const eeEnrichment = ee ? await ee.enrichScan(conclusion, {
746
779
  host,
747
780
  outDir,
748
781
  compliance: opts.compliance ?? process.env.COMPLIANCE_FRAMEWORKS ?? null,
749
782
  complianceScope: opts.complianceScope ?? null,
783
+ // EE 0.33.0 (N2). Forwarded verbatim; EE owns the defaults, the validation and
784
+ // the path-or-object handling for the policy.
785
+ complianceTrackSla: opts.complianceTrackSla,
786
+ complianceHistoryRoot: opts.complianceHistoryRoot,
787
+ slaPolicy: opts.slaPolicy,
750
788
  results,
751
789
  onWarn: (msg) => console.warn(`[EE] ${msg}`),
752
- });
790
+ }) : null;
753
791
  if (eeEnrichment?.enrichedPrompt) {
754
792
  conclusion.result = conclusion.result || {};
755
793
  conclusion.result.eeEnrichment = eeEnrichment;
756
794
  }
757
- } catch { /* EE not installed — CE proceeds unchanged */ }
795
+ } catch (err) {
796
+ // EE IS PRESENT AND FAILED. Never silent: name the stage that was lost, and mark the
797
+ // conclusion so a downstream reader can tell "no findings" from "the stage that finds
798
+ // them did not run". Still non-fatal — a scan that completed its CE work is worth
799
+ // keeping — but it is now a visible degradation rather than an invisible one.
800
+ console.error(`[EE] enrichment FAILED — this scan has no compliance report, no `
801
+ + `intelligence enrichment and no analysis-agent findings: ${err?.message ?? err}`);
802
+ conclusion.result = conclusion.result || {};
803
+ conclusion.result.eeEnrichmentError = String(err?.message ?? err);
804
+ }
758
805
 
759
806
  const { file_paths: ai_file_paths, ai_conclusion, ai_status, ai_error } = await maybeSendToOpenAI({ host, results, conclusion, promptMode, outDir });
760
807
  // BUG1 (fail-VISIBLE): a one-line end-of-scan AI status so a failed/aborted AI
@@ -902,6 +949,45 @@ function maxSeverityInConclusion(conclusion) {
902
949
  * @param {object} [opts] - { cmd, frameworks, importEE(test seam) }
903
950
  * @returns {Promise<{ran:boolean, reason?:string}>}
904
951
  */
952
+ /**
953
+ * STARTUP POSTURE VETO — `NSAUDITOR_OFFLINE_ONLY=1` against a configured egress path.
954
+ *
955
+ * EE raises this as an `NsauditorConfigError` from `resolveComplianceEnvOpts`, and exports
956
+ * that function specifically so CE can fail fast at startup with the SAME definition of
957
+ * "offline". Until now nothing called it, and the veto's only route into CE was through
958
+ * `enrichScan` — straight into the bare `catch {}` on the scan path, which does not abort
959
+ * the scan: it silently drops the ENTIRE EE stage (intelligence, agents, the compliance
960
+ * report). A quiet skip wearing a fail-fast's name, one repo over from the test that
961
+ * forbids exactly that.
962
+ *
963
+ * Deliberately NOT gated on the command and NOT gated on a licence tier — a contradiction
964
+ * between two operator settings is a configuration error under every command, and
965
+ * `enrichScan` never even evaluated it below Pro tier. `tests/cli_posture_preflight.test.mjs`
966
+ * pins both of those as decisions rather than accidents, and explains why this exits 2 while
967
+ * the GRC preflight below exits 1.
968
+ *
969
+ * EE stays optional: an unresolvable EE means no EE egress paths to veto, and that is a
970
+ * reason, not a failure.
971
+ *
972
+ * @param {Record<string,string|undefined>} env
973
+ * @param {{importEE?: Function}} [opts] `importEE` is the test seam — these cases must not
974
+ * require EE to be installed, the same convention `preflightGrcIfRequested` uses.
975
+ * @returns {Promise<{ran: boolean, reason?: string}>}
976
+ * @throws the EE `NsauditorConfigError` verbatim, so main() can print it and exit 2
977
+ */
978
+ export async function preflightNsauditorPosture(env, opts = {}) {
979
+ const { importEE } = opts;
980
+ let ee;
981
+ try {
982
+ ee = importEE ? await importEE() : await import('@nsasoft/nsauditor-ai-ee');
983
+ } catch {
984
+ return { ran: false, reason: 'ee-unavailable' };
985
+ }
986
+ if (typeof ee?.resolveComplianceEnvOpts !== 'function') return { ran: false, reason: 'ee-too-old' };
987
+ ee.resolveComplianceEnvOpts(env);
988
+ return { ran: true };
989
+ }
990
+
905
991
  export async function preflightGrcIfRequested(env, opts = {}) {
906
992
  const { cmd, frameworks, importEE } = opts;
907
993
  if (cmd !== 'scan') return { ran: false, reason: 'not-scan' };
@@ -922,7 +1008,7 @@ export async function preflightGrcIfRequested(env, opts = {}) {
922
1008
 
923
1009
  export async function main() {
924
1010
  const args = await parseArgs(process.argv);
925
- const { cmd, host, plugins, insecureHttps, hostFile, parallel, failOn, outputFormat, watch, intervalMinutes, webhookUrl, alertSeverity, ports, compliance, complianceScope, awsRegion } = args;
1011
+ const { cmd, host, plugins, insecureHttps, hostFile, parallel, failOn, outputFormat, watch, intervalMinutes, webhookUrl, alertSeverity, ports, compliance, complianceScope, complianceHistory, slaPolicy, attestWindow, framework, awsRegion } = args;
926
1012
 
927
1013
  // Version: handled before license verification so it works without a key.
928
1014
  // CE-0.1.30.1 — closes the discovery-flag UX gap where pre-fix
@@ -980,6 +1066,22 @@ Scan options:
980
1066
  of soc2,hipaa,nist-csf,pci-dss,iso-27001,cis-v8,gdpr (aliases
981
1067
  nist/pci/iso/cis). Unknown tokens fail fast. Enterprise only.
982
1068
  --compliance-scope <path> JSON file describing the assessment scope
1069
+ --compliance-history <dir> Directory of prior scans (one subdirectory per scan). Turns on
1070
+ SLA/MTTR longitudinal tracking against that history. Enterprise.
1071
+ --sla-policy <path> JSON file of SLA thresholds per severity; defaults to the
1072
+ shipped data/compliance/sla.json. Turns SLA tracking on by
1073
+ itself. Relative paths resolve against the current
1074
+ directory. Enterprise.
1075
+
1076
+ Compliance subcommands:
1077
+ nsauditor-ai compliance attest --history <dir> [--framework <fw>] [--window 6m|12m|90d]
1078
+ Aggregate the per-scan attestation records in
1079
+ <dir> into a multi-period (Type II) recurring-scan
1080
+ attestation. Reads scan_attestation_<fw>.json from
1081
+ each subdirectory, so a history you already have is
1082
+ aggregatable. Discovery is ONE level deep and the
1083
+ report says so. Exits 3 when no evidence is found —
1084
+ an empty history is a finding, not a pass.
983
1085
 
984
1086
  License subcommands:
985
1087
  nsauditor-ai license install <KEY> Verify and persist a license key (Keychain
@@ -1024,7 +1126,9 @@ Environment:
1024
1126
  NSA_ALLOW_ALL_HOSTS=1 Permit RFC1918 / loopback (local-network auditing)
1025
1127
  CLOUD_PROVIDER=aws|gcp|azure Required for cloud scanner plugins (020/021/022/023/030)
1026
1128
  AI_PROVIDER=openai|claude|ollama AI provider for report generation
1027
- COMPLIANCE_TSA_URL RFC 3161 timestamp authority for SOC 2 attestation
1129
+ NSAUDITOR_TSA_URL RFC 3161 timestamp authority for compliance attestation.
1130
+ No default, ever — unset means the feature is absent.
1131
+ Refused at startup together with NSAUDITOR_OFFLINE_ONLY=1.
1028
1132
 
1029
1133
  Cloud-scan hosts:
1030
1134
  --host aws[,gcp,azure] One or more cloud sentinel literals, comma-separated
@@ -1046,8 +1150,10 @@ Cloud-scan hosts:
1046
1150
  is safe here.
1047
1151
  Note: the composite zero-trust checker (1023) has no
1048
1152
  single cloud and is therefore skipped under this
1049
- auto-scope; to run it, select it explicitly
1050
- (--plugins 023,...) or scan a network host/CIDR.
1153
+ auto-scope. It runs on a NETWORK host/CIDR scan with
1154
+ the full plugin set; selecting it by id on its own
1155
+ does not work, because it needs a discovery plugin to
1156
+ confirm the host is up first.
1051
1157
  INVERSE (cloud scope integrity): a cloud auditor runs
1052
1158
  ONLY on its own sentinel host. On a NETWORK host (IP /
1053
1159
  CIDR / hostname) the cloud-scanner plugins NEVER run —
@@ -1116,6 +1222,17 @@ Docs: https://www.nsauditor.com/ai/ | Pricing: https://www.nsauditor.com/ai/
1116
1222
  // --env load) so a bad token / control-map / provider fails fast instead of after a
1117
1223
  // full scan. The helper gates on the exact push condition (so a framework-less recon
1118
1224
  // scan / non-scan command is never hard-failed) + silently skips if EE isn't installed.
1225
+ // Posture veto FIRST — before the GRC preflight and before any scan work. An operator who
1226
+ // has forbidden outbound connections and also configured one must hear about it here, not
1227
+ // from an EE stage that a bare catch would drop on the floor. Exit 2 matches
1228
+ // `compliance attest`, the other door into the same veto.
1229
+ try {
1230
+ await preflightNsauditorPosture(process.env);
1231
+ } catch (err) {
1232
+ console.error(`Fatal: ${err.message}`);
1233
+ process.exit(2);
1234
+ }
1235
+
1119
1236
  try {
1120
1237
  await preflightGrcIfRequested(process.env, { cmd, frameworks: args.compliance ?? process.env.COMPLIANCE_FRAMEWORKS });
1121
1238
  } catch (err) {
@@ -1996,6 +2113,55 @@ Docs: https://www.nsauditor.com/ai/ | Pricing: https://www.nsauditor.com/ai/
1996
2113
  process.exit(exitCode);
1997
2114
  }
1998
2115
 
2116
+ // ── `compliance attest` (EE 0.33.0, N2) ───────────────────────────────────
2117
+ // Placed BEFORE the unknown-command guard, or it would be dead code that exits 2.
2118
+ // Deliberately a THIN forward: EE owns framework validation, the offline posture
2119
+ // veto, framework-alias resolution and every default. CE contributes the flag
2120
+ // surface and the exit code — nothing here can drift out of sync with the engine
2121
+ // because nothing here decides anything.
2122
+ if (cmd === 'compliance') {
2123
+ const sub = process.argv[3];
2124
+ if (sub !== 'attest') {
2125
+ console.error('Usage: nsauditor-ai compliance attest --history <dir> [--framework <fw>] [--window 6m|12m|90d]');
2126
+ process.exit(2);
2127
+ }
2128
+ if (typeof complianceHistory !== 'string' || complianceHistory.length === 0) {
2129
+ console.error('Fatal: compliance attest requires --history <dir>');
2130
+ process.exit(2);
2131
+ }
2132
+ let runAttestCommand;
2133
+ try {
2134
+ ({ runAttestCommand } = await import('@nsasoft/nsauditor-ai-ee'));
2135
+ } catch {
2136
+ console.error('compliance attest requires the Enterprise package (@nsasoft/nsauditor-ai-ee).');
2137
+ process.exit(2);
2138
+ }
2139
+ try {
2140
+ const { report, files, exitCode } = await runAttestCommand({
2141
+ // EE gates on this, exactly as it does for a compliance scan. CE resolves the
2142
+ // tier it already resolved for `--compliance`; presenting it here rather than
2143
+ // letting EE default keeps ONE definition of "Enterprise" across both paths.
2144
+ capabilities: resolveCapabilities(getTierFromEnv()),
2145
+ rootDir: complianceHistory,
2146
+ outDir: resolveBaseOutDir(),
2147
+ framework: framework || compliance || 'soc2',
2148
+ window: attestWindow || '6m',
2149
+ onWarn: (msg) => console.warn(`[EE] ${msg}`),
2150
+ });
2151
+ console.log(`Recurring attestation (${report.framework}): ${report.summary.scanCount} scans in window, status ${report.summary.complianceStatus}`);
2152
+ if (report.summary.invalidForAudit > 0) {
2153
+ console.warn(` ${report.summary.invalidForAudit} of them are marked REPORT INVALID FOR AUDIT by their own attestation — see scansWithWarnings`);
2154
+ }
2155
+ for (const f of files) console.log(` wrote ${f.path ?? f}`);
2156
+ process.exit(exitCode);
2157
+ } catch (err) {
2158
+ // A posture contradiction or an unknown framework arrives here. It must NOT be
2159
+ // swallowed: the whole point of the veto is that it stops the run loudly.
2160
+ console.error(`Fatal: ${err.message}`);
2161
+ process.exit(2);
2162
+ }
2163
+ }
2164
+
1999
2165
  if (cmd !== 'scan') {
2000
2166
  console.error(`Unknown command: ${cmd}`);
2001
2167
  process.exit(2);
@@ -2024,6 +2190,30 @@ Docs: https://www.nsauditor.com/ai/ | Pricing: https://www.nsauditor.com/ai/
2024
2190
  if (ports) opts.ports = ports;
2025
2191
  if (compliance) opts.compliance = compliance;
2026
2192
  if (complianceScope) opts.complianceScope = complianceScope;
2193
+ // A value-less flag is a mistake worth stopping for: `--sla-policy` with no path
2194
+ // would otherwise scan on and produce a report indistinguishable from one where the
2195
+ // operator never asked for SLA tracking at all.
2196
+ for (const [flag, val] of [['--compliance-history', complianceHistory], ['--sla-policy', slaPolicy]]) {
2197
+ if (val === true) {
2198
+ console.error(`Fatal: ${flag} requires a value`);
2199
+ process.exit(2);
2200
+ }
2201
+ }
2202
+ if (typeof complianceHistory === 'string' && complianceHistory.length > 0) {
2203
+ opts.complianceHistoryRoot = complianceHistory;
2204
+ // The history root IS the request: asking for a history and not tracking against
2205
+ // it would be a flag that reads as doing something and does nothing.
2206
+ opts.complianceTrackSla = true;
2207
+ }
2208
+ if (typeof slaPolicy === 'string' && slaPolicy.length > 0) {
2209
+ opts.slaPolicy = slaPolicy;
2210
+ // …and it TURNS TRACKING ON. Setting the policy alone was a byte-identical no-op:
2211
+ // the whole SLA block is gated on complianceTrackSla, so an operator who passed
2212
+ // --sla-policy and nothing else got a report indistinguishable from passing nothing,
2213
+ // with no warning in either channel. A flag that reads as configuring a feature must
2214
+ // not silently require a second flag to have any effect.
2215
+ opts.complianceTrackSla = true;
2216
+ }
2027
2217
  if (awsRegionIntent) opts.awsRegionIntent = awsRegionIntent;
2028
2218
  const pm = await PluginManager.create(`${__dirname}/plugins`);
2029
2219
  const promptMode = String(process.env.OPENAI_PROMPT_MODE || 'basic').toLowerCase().trim();
package/mcp_server.mjs CHANGED
@@ -211,6 +211,29 @@ function appendCallSentinel(text, callId) {
211
211
  // Max findings per page — also referenced in the TOOLS inputSchema description below.
212
212
  const GET_FINDINGS_MAX_LIMIT = 20; // ~500 chars/finding -> ~10KB page (Desktop 60s budget)
213
213
 
214
+ // ---------------------------------------------------------------------------
215
+ // FRAMEWORK_STEMS — the ONE CE-side name for the shipped compliance framework set.
216
+ //
217
+ // ⚠️ WHY A CE-SIDE CONSTANT AND NOT AN EE IMPORT. EE owns this set
218
+ // (`@nsasoft/nsauditor-ai-ee/utils/framework_ids.mjs` → FRAMEWORK_STEMS) and that specifier IS
219
+ // exported and resolvable — but EE is not a CE dependency at all: CE must load and serve its
220
+ // tool registry with EE absent. `TOOLS` below is built at MODULE LOAD, so importing EE here
221
+ // would make the tool REGISTRY itself conditional on an optional package, which is a strictly
222
+ // worse failure than a stale list (no tools at all, on the customers who never buy EE).
223
+ // The equality is therefore enforced where a hard EE dependency is allowed — at test time, by
224
+ // tests/framework_stem_parity.test.mjs, which holds this array in TWO-WAY equality with both
225
+ // EE's declaration and the on-disk `data/compliance/*.json` census, and which FAILS (never
226
+ // skips) when EE is unresolvable.
227
+ //
228
+ // It is declared HERE, above TOOLS, because the set had drifted into FOUR transcriptions: the
229
+ // compliance_matrix enum, the handler's validation list, this suite's own copy — and the tool
230
+ // DESCRIPTION, which is the load-bearing one. A schema enum that goes stale REJECTS a token; a
231
+ // description that goes stale silently steers an assistant's tool selection and errors nowhere.
232
+ // Everything below DERIVES from this array. Do not re-type it.
233
+ export const FRAMEWORK_STEMS = Object.freeze([
234
+ 'soc2', 'hipaa', 'nist-csf', 'pci-dss', 'iso-27001', 'cis-v8', 'gdpr',
235
+ ]);
236
+
214
237
  // Exported for direct testing (the scan_cloud description is a routing surface —
215
238
  // tests pin its service-coverage enumeration + contract clauses).
216
239
  export const TOOLS = [
@@ -270,15 +293,26 @@ export const TOOLS = [
270
293
  },
271
294
  {
272
295
  name: 'compliance_matrix',
296
+ // ⚠️ THE FRAMEWORK LIST IN THIS DESCRIPTION IS DERIVED, NOT TYPED. It is the surface a model
297
+ // reads to CHOOSE a `framework` token, so a stale list here is worse than a stale enum: the
298
+ // enum rejects a bad token loudly, the description mis-steers tool selection silently. The
299
+ // GDPR scoping doctrine is kept as its OWN sentence rather than a parenthetical inside the
300
+ // list, so it survives any reordering of the stems (and so the list stays machine-readable
301
+ // for tests/framework_stem_parity.test.mjs, which compares it to the shipped JSON census).
273
302
  description:
274
- 'Return the SHIPPED compliance coverage matrix for a framework — how many controls are Covered, Partial and Out of Scope, with the control ids and the per-group out-of-scope reasons. Frameworks: soc2, hipaa, nist-csf, pci-dss, iso-27001, cis-v8, gdpr (GDPR is Article 32 security-of-processing substrate ONLY, never "GDPR compliance"), or "all" for the counts across every framework. ALWAYS call this tool before stating or tabulating any coverage matrix, and quote the numbers it returns verbatim. Do NOT derive a matrix from the plugin inventory, from a scan result, or from documentation — coverage is a property of the shipped framework maps, not of the plugin list, and a derived matrix will disagree with the customer\'s own report. Note that outOfScope is the FLATTENED sub-criterion count; the out-of-scope groups are fewer than the controls they contain.',
303
+ 'Return the SHIPPED compliance coverage matrix for a framework — how many controls are Covered, Partial and Out of Scope, with the control ids and the per-group out-of-scope reasons. ' +
304
+ `Frameworks: ${FRAMEWORK_STEMS.join(', ')}, or "all" for the counts across every framework. ` +
305
+ 'The gdpr map is GDPR Article 32 security-of-processing substrate ONLY, never "GDPR compliance". ' +
306
+ 'ALWAYS call this tool before stating or tabulating any coverage matrix, and quote the numbers it returns verbatim. Do NOT derive a matrix from the plugin inventory, from a scan result, or from documentation — coverage is a property of the shipped framework maps, not of the plugin list, and a derived matrix will disagree with the customer\'s own report. Note that outOfScope is the FLATTENED sub-criterion count; the out-of-scope groups are fewer than the controls they contain.',
275
307
  inputSchema: {
276
308
  type: 'object',
277
309
  properties: {
278
310
  framework: {
279
311
  type: 'string',
280
- enum: ['soc2', 'hipaa', 'nist-csf', 'pci-dss', 'iso-27001', 'cis-v8', 'gdpr', 'all'],
281
- description: 'Which framework matrix to return. Omit or pass "all" for the counts across all seven.',
312
+ enum: [...FRAMEWORK_STEMS, 'all'],
313
+ // The count is DERIVED too. "across all seven" was a transcribed count standing beside
314
+ // a transcribed list — two things to forget on the next framework cycle instead of none.
315
+ description: `Which framework matrix to return. Omit or pass "all" for the counts across all ${FRAMEWORK_STEMS.length} shipped frameworks.`,
282
316
  },
283
317
  },
284
318
  required: [],
@@ -672,7 +706,11 @@ export async function handleListPlugins() {
672
706
  // 2. FAIL CLOSED. `plugin_discovery.mjs` swallows an unresolvable EE into `catch {}` so CE
673
707
  // runs standalone, and copying that here would answer "no matrix" — which is precisely
674
708
  // the void an assistant fills with a synthesised one.
675
- const FRAMEWORK_STEMS = ['soc2', 'hipaa', 'nist-csf', 'pci-dss', 'iso-27001', 'cis-v8', 'gdpr'];
709
+ //
710
+ // The framework set this handler validates against is the module-level FRAMEWORK_STEMS declared
711
+ // above TOOLS — the SAME array the enum and the tool description are built from. It used to be
712
+ // a second copy right here, which is how rule 1 was being obeyed for the NUMBERS and quietly
713
+ // broken for the framework LIST inside the very tool built to stop transcription rot.
676
714
 
677
715
  function _defaultResolveEE() {
678
716
  // `@nsasoft/nsauditor-ai-ee/data/compliance/*.json` is NOT importable — EE's `exports` map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nsauditor-ai",
3
- "version": "0.2.36",
3
+ "version": "0.2.37",
4
4
  "description": "Modular AI-assisted network security audit platform — Community Edition",
5
5
  "type": "module",
6
6
  "private": false,