nsauditor-ai 0.1.97 β†’ 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -17,38 +17,9 @@ NSAuditor AI is the open-source core of a privacy-first security intelligence pl
17
17
 
18
18
  ## What's New
19
19
 
20
- **Latest: CE 0.1.94 + Enterprise 0.16.3** (May 2026)
21
-
22
- - πŸ›‘οΈ **`scan_cloud` now surfaces cloud findings reliably** (CE 0.1.95 β€” paired EE 0.16.4) β€” *(false-clean fix)* the tool now builds a `findingsSummary` (per-provider severity counts + the CRITICAL/HIGH list) **directly from the scan results**, instead of the network-host concluder β€” which is built for port/service scans, doesn't understand cloud compliance findings, and silently **dropped** them. A scan could previously report `audited:true` with **0 findings over real CRITICALs**; now every finding is counted and every CRITICAL/HIGH is listed. Distinct from the 0.1.93 not-audited fold (caught by the `audit-cloud-plugin-false-negatives` review). Plugin count UNCHANGED at 28; all six matrices UNCHANGED.
23
- - ⚑ **`scan_cloud` runs cloud plugins in parallel** (CE 0.1.94 β€” paired EE 0.16.3) β€” the cloud-audit engine now runs the requested clouds' plugins **concurrently** (default up to 20 at once, 25s per-plugin timeout) so a full ~20-plugin AWS/GCP/Azure audit finishes within Claude Desktop's ~60s tool-call limit β€” previously the heaviest, most security-critical plugins (IAM, S3, EC2, Inspector/GuardDuty, decrypt-path) timed out at the 5s the budget forced under sequential execution. Tunable via `CLOUD_SCAN_CONCURRENCY` / `CLOUD_PLUGIN_TIMEOUT_MS`; the network `scan_host` path is unchanged; the anti-false-clean reporting is preserved. Plugin count UNCHANGED at 28; all six matrices UNCHANGED.
24
- - ☁️ **MCP `scan_cloud` tool: audit cloud accounts directly** (CE 0.1.93 β€” paired EE 0.16.2) β€” *(Enterprise)* a dedicated MCP tool so you can say *"Audit my AWS account"* or *"Audit my AWS and Azure accounts"* in Claude Desktop / Claude Code, with **no network host** β€” the server scopes the run to only the requested clouds' EE plugins. The MCP analog of the CLI `--host aws --plugins all` scoping. Enterprise-gated (`enterpriseMCP`) with plugin-layer defense-in-depth, and **anti-false-clean reporting** β€” a cloud is reported "audited" only if a plugin actually ran; a missing-credential / skipped cloud is surfaced as an explicit note + `audited:false` (R-HIGH caught + folded by the `audit-cloud-plugin-false-negatives` review). Plugin count UNCHANGED at 28; all six matrices UNCHANGED.
25
- - πŸ”Œ **MCP `NSA_ENV_FILE`: per-environment `.env` for the MCP server** (CE 0.1.92 β€” paired EE 0.16.1) β€” the MCP analog of 0.16.0's CLI `--env`. Point the MCP server at a specific account/cloud by setting **`NSA_ENV_FILE`** to a dotenv path in your Claude Desktop / Claude Code config instead of inlining every credential; the server loads that environment's cloud credentials + `CLOUD_PROVIDER` at startup. Loaded **after** the MCP auth gate + license (scan-target vars only; the auth key + license are ignored if present in the file), **fail-fast** on a missing / INI / set-but-empty path, and treated as the **authoritative scan target** β€” leftover ambient provider credentials the file doesn't set are cleared, closing a false-clean (a partial file could otherwise silently scan a leftover account) caught + folded by the `audit-cloud-plugin-false-negatives` review. Plugin count UNCHANGED at 28; all six matrices UNCHANGED.
26
- - 🎯 **Per-account scanning: `--env` / `--aws-profile` + sentinel-host plugin scoping** (CE 0.1.91 β€” paired EE 0.16.0) β€” audit many cloud accounts one at a time without shell-export juggling. **`--env <path>`** loads a per-scan dotenv credentials file (override-on; fail-fast on a missing file; an INI/`~/.aws/credentials` file is detected and redirected to `--aws-profile`). **`--aws-profile <name>`** uses a named profile from the OS-default `~/.aws/credentials` (clears stale explicit keys, sets `AWS_SDK_LOAD_CONFIG=1`, implies `CLOUD_PROVIDER=aws`). On a cloud-sentinel host, **`--host aws|gcp|azure` + `--plugins all` auto-scopes** to only that cloud's plugins (other clouds + non-cloud plugins are skipped and logged); explicit `--plugins` lists are unaffected. A host↔`CLOUD_PROVIDER` conflict fails fast (no silent empty "clean" report), and a zero-match sentinel scope warns loudly. EE 0.16.0 adds a declarative `cloudProvider` field to all 27 cloud plugins. Plugin count UNCHANGED at 28; all six matrices UNCHANGED.
27
- - πŸ›‘οΈ **Cross-cloud scope hotfix** (CE 0.1.90 β€” paired EE 0.15.9) β€” EE 0.15.9 moves the AWS-plugin `CLOUD_PROVIDER` gate from `preflight()` to `run()`: the 0.15.8 gate was placed where the scan orchestrator never calls it, so the cross-cloud bleed persisted; it's now on the load-bearing `run()` path (a `CLOUD_PROVIDER=gcp|azure` scan with AWS creds present no longer bleeds AWS resources into the GCP/Azure packs), validated via a run()-path test + a real-creds local proof. CE is a paired no-op bump. Plugin count UNCHANGED at 28; all six matrices UNCHANGED.
28
- - πŸ›‘οΈ **Cloud-plugin scoping fixes** (CE 0.1.89 β€” paired EE 0.15.8) β€” EE 0.15.8 closes two issues surfaced by the 0.15.7 full multi-cloud smoke: the AWS plugins now gate on `CLOUD_PROVIDER` (a `CLOUD_PROVIDER=gcp|azure` scan with AWS creds present no longer bleeds AWS resources into the GCP/Azure attestation packs), and the GCP IAM/storage auditor now surfaces an evidence-gap instead of a false-clean PASS when a policy can't be read. CE is a paired no-op bump (no CE code change). Plugin count UNCHANGED at 28; all six coverage matrices UNCHANGED.
29
- - ☁️ **GCP SDK refresh** (CE 0.1.88 β€” paired EE 0.15.7) β€” EE 0.15.7 re-applies the GCP SDK major bump (`@google-cloud/compute` ^6 / `@google-cloud/iam` ^2 / `googleapis` ^173) on the pure-ADC credential path, validated live against a test-infra GCP project (first live GCP audit: 3 CRITICAL firewall findings on compute@6). Compute-client SA-impersonation is explicitly unsupported on compute@6 (documented in-code + gated to plan-later); pure-ADC and key-file paths are fully supported. Also folds the plugin-1021 project-resolution fix. CE is a paired no-op bump (no CE code change). Plugin count UNCHANGED at 28; all six coverage matrices UNCHANGED.
30
- - 🧭 **Compliance-mapping correctness** (CE 0.1.87 β€” paired EE 0.15.6) β€” EE 0.15.6 closes two cross-framework defects in how S3 public-exposure findings route to the compliance frameworks: a publicly-accessible bucket now correctly maps to **NIST CSF PR.AA-05 + PR.DS-01** and **PCI DSS 7.2.1** (it previously showed CLEAN on those two), and a missing-Public-Access-Block guardrail gap (not a confirmed exposure) no longer false-FAILs the confidentiality-exposure controls. CE is a paired no-op bump (no CE code change). Plugin count UNCHANGED at 28; all six coverage matrices UNCHANGED.
31
- - 🧹 **Dependency-hygiene / institutional-trust patch** (CE 0.1.86 β€” paired EE 0.15.5) β€” removes deprecation warnings + advisories from `npm install`. The abandoned `simple-wappalyzer` (β†’ deprecated, now-commercial `wappalyzer-core`) is replaced by an in-house **zero-dependency** web tech fingerprinter (`utils/tech_fingerprint.mjs` β€” header/HTML/script-src/cookie/meta signatures; same `{name,categories,confidence,version}` output). `@anthropic-ai/sdk` bumped `^0.82`β†’`^0.100` (exits the GHSA-p7fg-763f-g4gf range; the Filesystem Memory Tool is never used). The direct `uuid` dependency is dropped in favor of native `crypto.randomUUID()`. NEW `SECURITY.md` documents remaining transitive notices. No feature/behavior change.
32
-
33
-
34
-
35
- - πŸͺ£ **Non-current-version ACL sampling + public WRITE-vs-READ differentiation** (EE 0.15.4 β€” paired) β€” closes the two residuals the 0.15.3 spec carried as deferred. Plugin 1020 gains NEW step 2c-v: on versioning-Enabled/Suspended buckets it samples **non-current** object versions (`ListObjectVersions` first-page, cap-bounded β†’ per-version `GetObjectAcl({VersionId})`, delete-markers skipped) β€” catching a public ACL that survives on an **overwritten** version still downloadable via `?versionId=` after the current version is made private (CRITICAL via the existing `"publicly accessible"` anchor; PAB `IgnorePublicAcls` β†’ LOW; skipped on `BucketOwnerEnforced`). NEW sibling helper `extractPublicWriteGroups` flags public WRITE/WRITE_ACP/FULL_CONTROL grants (anyone-can-overwrite) distinctly from READ-only, as an enrichment line on the already-CRITICAL finding at bucket/object/version ACL sites. `ListObjectVersions AccessDenied` (distinct `s3:ListBucketVersions` action) + a `GetBucketVersioning AccessDenied` review-fold both degrade to routed LOW evidence-gaps via the existing `"S3 object-ACL evidence-gap"` anchor β€” never a silent PASS. **Enterprise plugin count UNCHANGED (28); all six coverage matrices UNCHANGED; ZERO framework-JSON edits.** TDD-first, +27 tests; EE regression 6628/6628 GREEN. _(Staged on `main`; awaiting live smoke + trio publish.)_
36
- - πŸͺ£ **Object-level ACL enumeration + BucketOwnerEnforced short-circuit** (EE 0.15.3 β€” paired) β€” closes the 4th and final S3 public-exposure vector (object-level ACLs) documented as a residual in the 0.15.2 closure. Plugin 1020 gains NEW step 2c sampled `GetObjectAcl` enumeration over first-page objects (default cap 10; per-object throttle default 50ms) + NEW step 2a `GetBucketOwnershipControls` upstream short-circuit that skips both 2b (bucket-ACL) and 2c (object-ACL) on `BucketOwnerEnforced` buckets (the default since April 2023; saves 11+ API calls per BOE bucket on modern estates AND closes a false-positive class where BOE buckets with legacy stored public ACL grants previously emitted CRITICAL β€” they now emit informational because S3 structurally ignores ACL grants under BOE). **INTENTIONAL MATRIX DELTA from 0.15.2** on BOE buckets with pre-BOE legacy grants: CRITICAL β†’ informational. NEW shared `extractPublicGroups` helper used by BOTH step 2b (refactored byte-identical) AND step 2c. 4 LOW evidence-gap emissions via NEW `"S3 object-ACL evidence-gap"` substring anchor on SOC 2 CC7.1 + HIPAA Β§164.312(b) (substrate-depth on already-covered controls; matrices unchanged). Live AWS smoke all 4 spot-checks PASS (BOE detection; E1 CRITICAL en-dash bytes preserved; cap clamping; objectRateMs throttling). Enterprise plugin count unchanged (28); all six coverage matrices unchanged.
37
- - 🎯 **Audit-accuracy calibration & CloudTrail hardening** (EE 0.15.2 β€” paired) β€” four real-production-account-driven folds: **(1)** plugin 1020 (S3) effective-public-exposure calibration β€” missing/partial Public Access Block downgraded CRITICALβ†’MEDIUM (guardrail gap, not current exposure) + NEW `GetBucketAcl` check completing the ACLΓ—policyΓ—PAB join (public AllUsers/AuthenticatedUsers grant β†’ CRITICAL unless neutralized by PAB `IgnorePublicAcls`); **(2)** plugin 1040 (CloudTrail) KMS-CMK calibration β€” trail-level "KmsKeyId not set" downgraded MEDIUMβ†’LOW when the destination bucket has default SSE-KMS; **(3)** plugin 1040 (CloudTrail) multi-region timeout hardening β€” an `AbortController` tied to the soft-budget deadline lets a hung disabled-region abort so the plugin finalizes PARTIAL evidence; **(4)** plugin 1221 (Azure NSG) +10 restricted UDP ports + plugin 1222 (Azure Key Vault) F-2 custom-role resolution + F-7.2 HSM dim. Enterprise plugin count unchanged (28); all six coverage matrices unchanged.
38
- - πŸ”§ **Azure Key Vault Deep auditor hotfix** (EE 0.15.1 β€” plugin 1222) β€” two defects surfaced by the 0.15.0 published-build smoke. **H-1**: the diagnostic-logging dim treated `@azure/arm-monitor`'s `diagnosticSettings.list()` as a paged async-iterator when it actually returns a `{value:[]}` collection object β†’ the dim always degraded to a non-functional evidence-gap; now correctly `await`-ed and read via `.value` (confirmed against live Azure; the unit-test mock was the mock-vs-real-SDK mismatch that masked it). **H-2**: the privileged-access dim flagged inherited subscription/management-group-scope Owner/Contributor as HIGH on every RBAC vault β€” re-tuned so inherited Owner/User-Access-Admin β†’ MEDIUM, inherited Contributor β†’ LOW, with HIGH reserved for vault-scoped control-plane god roles + Key Vault Administrator. Additive bug-fix only; enterprise plugin count unchanged (28); all six coverage matrices unchanged.
39
- - ☁️ **NEW Azure Key Vault Deep auditor** (EE 0.15.0 β€” plugin 1222; enterprise plugin count 27 β†’ 28) β€” the third dedicated Azure auditor (after 1220 storage + 1221 NSG), the Key Vault analog of how 1221 deepens the multi-purpose Azure scanner'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, (3) diagnostic logging β†’ Log Analytics, (4) privileged-access depth (RBAC role assignments + legacy access-policy breadth incl. export/wide-crypto). Orthogonal to the Azure scanner's vault-property dims (purge/soft-delete/network-ACL/RBAC-mode) β€” no double-emission; secret/cert expiry is a deliberate data-plane scope boundary. Findings route across all six frameworks (SOC 2 CC6.3/C1.1/CC6.1/CC7.2 / HIPAA / NIST CSF / PCI DSS / ISO 27001 / CIS v8) β€” all coverage matrices unchanged.
40
- - ☁️ **Azure NSG Perimeter auditor β€” UDP lane** (EE 0.14.1 β€” plugin 1221) β€” the perimeter auditor now tiers **UDP** management/amplification services (SNMP 161, CLDAP 389, NTP 123, rpcbind 111, IPMI 623, IKE 500, Memcached 11211, etc.), not just TCP β€” closing a false negative where a public-internet UDP service was silently treated as a benign "web tier" port. NEW Dim 2u/3u (UDP public-source + `::/0`), attachment-aware (CRITICAL effective / MEDIUM latent), per-transport priority/deny-override resolution; Dim-4 made protocol-aware. Plugin count unchanged (27); all six coverage matrices unchanged.
41
- - ☁️ **NEW Azure NSG Perimeter auditor** (EE 0.14.0 β€” plugin 1221; enterprise plugin count 26 β†’ 27) β€” the Azure analog of AWS plugin 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 restricted management/data-tier ports (SSH/RDP/databases/etc.), `::/0` IPv6-wildcard (the dimension the multi-purpose Azure scanner misses), with **attachment-aware severity** (attached to a subnet/NIC β†’ CRITICAL effective exposure; orphaned β†’ MEDIUM latent), effective priority/deny-override resolution, and `0.0.0.0/1` split-range coverage. Findings route across all six frameworks (SOC 2 CC6.6 / HIPAA / NIST CSF / PCI DSS / ISO 27001 / CIS v8) β€” all coverage matrices unchanged.
42
- - ☁️ **Azure Storage auditor deepened** (EE 0.13.3 β€” plugin 1220, +2 dims) β€” added blob recoverability (soft-delete + versioning) and per-container anonymous public-access detection (account-toggle-aware) via the secondary blob-service / container API paths. Plugin count unchanged (26); all six coverage matrices unchanged.
43
- - ☁️ **NEW Azure Storage Account auditor** (EE 0.13.2 β€” plugin 1220; enterprise plugin count 25 β†’ 26) β€” the first dedicated Azure auditor beyond the multi-purpose Azure scanner. Audits the encryption-at-rest / in-transit / authorization-mode surface: HTTPS-only transit, minimum TLS version, Shared Key authorization (bypasses Azure AD), infrastructure (double) encryption, and customer-managed-key reachability + rotation. Findings route across all six frameworks (SOC 2 / HIPAA / NIST CSF / PCI DSS / ISO 27001 / CIS v8) β€” all coverage matrices unchanged.
44
- - πŸ›‘οΈ **CIS-Hardened-Image detection LIVE + AWS EC2 Instance auditor** (EE 0.13.1 β€” plugin 1210) β€” multi-cloud CIS-Hardened-Image detection (AWS / Azure / GCP) on CIS Safeguards 4.1/4.2/4.6; EC2 instance-level IMDSv1 / EBS + account-default encryption / public-IP exposure audit. CIS v8 matrix 17/22/114.
45
- - πŸ†• **CIS Critical Security Controls v8** (Center for Internet Security, May 2021; v8.1 errata June 2024) is now the **sixth** supported compliance framework in Enterprise β€” alongside SOC 2, HIPAA, NIST CSF 2.0, PCI DSS v4.0.1, and ISO/IEC 27001:2022. Per-Safeguard mapping (the atomic, attestable unit): 17 covered + 21 partial + 115 OOS across **153 Safeguards / 18 Controls / 3 cumulative Implementation Groups**. **Implementation Group cumulative discipline** β€” IG1=56 (the cyber-insurance baseline; ~50-70% of mid-market policies require IG1 attestation), IG2 cumulative=130, IG3 cumulative=153. **No-certification-body attestation discipline** β€” engine output is INPUT to your CSAT / CIS-CAT Pro self-attestation (or a SOC 2 auditor cross-validating CIS scope), never "CIS certified." Cloud Companion Guide v8 shared-responsibility-model boundary + CIS-Hardened-Image substrate-evidence credit (Safeguards 4.1/4.2/4.6) + 5 Security Functions (NOT 6 β€” no Govern) + MS-ISAC/EI-ISAC/H-ISAC sector baselines. Generate evidence for any combination from a single scan: `--compliance soc2,hipaa,nist-csf,pci-dss,iso-27001,cis-v8` (Enterprise only).
46
- - πŸ” **ISO/IEC 27001:2022** (added in EE 0.12.0) β€” per-Annex-A-code mapping (auditor-canonical for ISO/IEC 17021-1 certification body assessors): 17 covered + 14 partial + 62 OOS across 93 Annex A controls. Statement of Applicability per Clause 6.1.3.d discipline + 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.
47
- - πŸ’³ **PCI DSS v4.0.1** (added in EE 0.11.0) β€” Defined-vs-Customized Approach discipline per Appendix E. CDE scope operator-attested. Card-brand AOC enforcement priority view (Visa CISP / Mastercard SDP / Amex DSOP / Discover DISC).
48
- - ⚑ **NIST Cybersecurity Framework 2.0** (added in EE 0.10.0) β€” 13 covered + 10 partial + 83 OOS Subcategories across 106 of CSF 2.0's 107 Subcategories.
49
- - πŸ₯ **HIPAA Security Rule Β§164.312** (added in EE 0.9.0) β€” Zero BAA required; your ePHI never leaves your infrastructure.
50
- - ☁️ **25 cloud plugins** across AWS, Azure, GCP β€” fully integrated SOC 2 / HIPAA / NIST CSF 2.0 / PCI DSS v4.0.1 / ISO 27001 / CIS Controls v8 evidence pipeline (Enterprise).
51
- - πŸ”¬ **Per-Framework Adversarial-Audit Skill Pairing** (institutional pattern) β€” Enterprise now ships with **10 authored Claude Code skills** (Phase-4 Compliance/GRC chain **7-of-7 COMPLETE** for all shipped frameworks). Each framework cycle is paired with a dedicated adversarial-audit skill authored in the same cycle: CIS Controls v8 pairs with NEW `audit-cis-controls-v8-implementation-group-perspective` (Skill #19) β€” surfacing 16 ship-blocker classes pre-author for a clean ship.
20
+ **Latest: CE 0.2.0 + Enterprise 0.17.0** (June 2026)
21
+
22
+ - 🌐 **`--aws-region` scoping + genuine multi-region fan-out** (CE 0.2.0 β€” paired EE 0.17.0) β€” a new `--aws-region <one|csv|all>` CLI flag (and a `regions` argument on the MCP `scan_cloud` tool) scopes an AWS audit to a single region, a CSV of regions, or every account-enabled region (`all`). The EE regional plugins now audit every in-scope region instead of only the configured one, and the S3 auditors resolve each bucket's own region β€” closing latent cross-region false-cleans. Precedence is `--aws-region` β€Ί `AWS_REGION` β€Ί single-region default; the no-flag default stays single-region (behaviour-preserving) and discloses any unscanned regions. Plugin count UNCHANGED at 28; all six coverage matrices UNCHANGED.
52
23
 
53
24
  β†’ Full release history: **[CHANGELOG.md](./CHANGELOG.md)**
54
25
  β†’ See a sample EE scan output: **[walk-through with synthetic Acme Corp AWS account](https://www.nsauditor.com/ai/docs/sample-scan/)** (no signup required)
@@ -79,7 +50,7 @@ NSAuditor AI is available in three editions: Community (free, MIT-licensed, no r
79
50
 
80
51
  If you're heading into a **SOC 2, HIPAA, NIST CSF 2.0, PCI DSS, ISO 27001, or CIS Controls v8 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:
81
52
 
82
- - ☁️ **25 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)
53
+ - ☁️ **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)
83
54
  - πŸ“‹ **6 compliance frameworks shipped** β€” generate any combination from a single scan:
84
55
  - **SOC 2** (AICPA TSC 2017) β€” 10 fully-covered + 4 partial controls
85
56
  - **HIPAA Security Rule Β§164.312** β€” 7 covered + 3 partial Technical Safeguards; **Zero BAA required** (ePHI never leaves your infrastructure)
@@ -112,7 +83,7 @@ If you're heading into a **SOC 2, HIPAA, NIST CSF 2.0, PCI DSS, ISO 27001, or CI
112
83
  | Risk scoring + prioritization | β€” | βœ… | βœ… |
113
84
  | Parallel analysis agents | β€” | βœ… | βœ… |
114
85
  | **Enterprise β€” cloud scanning** | | | |
115
- | 25 cloud plugins (AWS / Azure / GCP) | β€” | β€” | βœ… |
86
+ | 28 cloud plugins (AWS / Azure / GCP) | β€” | β€” | βœ… |
116
87
  | Zero Trust assessment | β€” | β€” | βœ… |
117
88
  | **Enterprise β€” compliance (6 frameworks)** | | | |
118
89
  | SOC 2 (AICPA TSC 2017) β€” 10 covered + 4 partial controls | β€” | β€” | βœ… |
@@ -316,6 +287,27 @@ nsauditor-ai scan --host aws --plugins all --compliance soc2
316
287
  nsauditor-ai scan --host aws --plugins 1130 --plugin-opts '{"1130":{"vpcEndpointsPageCap":50}}'
317
288
  ```
318
289
 
290
+ ### Scoping the AWS audit to regions β€” `--aws-region`
291
+
292
+ By default an AWS audit runs against a single region (`AWS_REGION`, else `us-east-1`). The `--aws-region <one|csv|all>` flag controls which regions the **regional** plugins (security groups, EC2, RDS, KMS, Lambda, Secrets Manager, DynamoDB, CodePipeline/CodeBuild, Backup, SQS/SNS, VPC endpoints, ElastiCache, SES, Inspector/GuardDuty, CloudTrail) audit β€” each now audits *every in-scope region*, not just the configured one:
293
+
294
+ ```bash
295
+ # A single region
296
+ nsauditor-ai scan --host aws --plugins all --compliance soc2 --aws-region us-east-1
297
+
298
+ # A comma-separated list of regions
299
+ nsauditor-ai scan --host aws --plugins all --compliance soc2 --aws-region us-east-1,eu-west-1,ap-southeast-2
300
+
301
+ # Every region enabled on the account (via DescribeRegions; static-list fallback on AccessDenied)
302
+ nsauditor-ai scan --host aws --plugins all --compliance soc2 --aws-region all
303
+ ```
304
+
305
+ - **Precedence:** `--aws-region` flag β€Ί `AWS_REGION` (shell / `--env` file) β€Ί single-region default.
306
+ - **Default (no flag, no `AWS_REGION`):** scans one region and adds an informational *"incomplete region coverage"* note listing the enabled regions that were **not** scanned. It maps to no compliance control (a disclosure, not a finding β€” your posture is unchanged); pass `--aws-region all` for full coverage.
307
+ - **Unknown region:** the explicit flag **fails fast** on an unrecognized region code (set `NSA_AWS_REGION_ALLOW_UNKNOWN=1` to permit a brand-new region); an `AWS_REGION`-derived value warns and proceeds.
308
+ - **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).
309
+ - **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.
310
+
319
311
  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).
320
312
 
321
313
  ---
@@ -429,6 +421,14 @@ npx nsauditor-ai-mcp
429
421
  > (per-provider severity counts + a CRITICAL/HIGH list) for the findings; `audited:false` / `notes` / `pluginsRan:0`
430
422
  > still mean a cloud was NOT audited (never a clean pass). Pass `providers:["aws"]` to audit only the cloud named.
431
423
 
424
+ > **Full all-region AWS coverage fits Desktop's limit automatically.** When you ask for "all regions" / "full
425
+ > coverage", the agent scans the enabled regions in small **region-group batches** (each within the ~60s window)
426
+ > rather than one long `regions:["all"]` call β€” so it completes without timing out, and you do **not** raise any
427
+ > timeout for it. Keep `CLOUD_PLUGIN_TIMEOUT_MS` **under** Desktop's ~60s tool-call cap (default `25000`; raise to
428
+ > ~`45000` only for very large accounts β€” a higher per-plugin cap can let one plugin run past Desktop's wall and
429
+ > cause a hard timeout). For unbounded multi-region scans use the **CLI** (`nsauditor-ai scan … --aws-region all`),
430
+ > which has no MCP tool-call cap β€” there you can raise `PLUGIN_TIMEOUT_MS` (e.g. `90000`) freely.
431
+
432
432
  Security: SSRF protection on all host inputs (blocks RFC 1918, loopback, fc00::/7, cloud metadata), port validation (1–65535), CPE format enforcement, dependency injection for test isolation. **Server-startup authentication is required** β€” see next section.
433
433
 
434
434
  ### Authentication (required)
@@ -513,7 +513,7 @@ The exact `NSA_MCP_AUTH_KEY` value to paste is printed by `nsauditor-ai mcp inst
513
513
  - `NSA_ALLOW_ALL_HOSTS=1` β€” required to scan private/RFC 1918 addresses (e.g., `192.168.x.x`)
514
514
  - `PLUGIN_TIMEOUT_MS=5000` β€” reduces per-plugin timeout to 5s so the full scan completes within Claude Desktop's 60s MCP limit
515
515
  - `CLOUD_SCAN_CONCURRENCY` β€” max cloud plugins run at once by `scan_cloud` (default 20).
516
- - `CLOUD_PLUGIN_TIMEOUT_MS` β€” per-plugin timeout for `scan_cloud` (default 25000; independent of the network `PLUGIN_TIMEOUT_MS`).
516
+ - `CLOUD_PLUGIN_TIMEOUT_MS` β€” per-plugin timeout for `scan_cloud` (default 25000; independent of the network `PLUGIN_TIMEOUT_MS`). Keep it **under** Desktop's ~60s tool-call cap (raise to ~`45000` only for very large accounts); full all-region coverage is delivered by automatic region-batching, so it needs **no** timeout increase.
517
517
  - `AI_PROVIDER` and API key β€” optional, enables AI-powered analysis of scan results
518
518
 
519
519
  #### `NSA_ENV_FILE` β€” point the MCP server at an environment file
package/cli.mjs CHANGED
@@ -613,6 +613,11 @@ export async function parseArgs(argv) {
613
613
  args.env = envVal === undefined ? undefined : envVal; // string path, or true if value-less
614
614
  const awsProfileVal = get('aws-profile');
615
615
  args.awsProfile = awsProfileVal === undefined ? undefined : awsProfileVal;
616
+ // Per-scan AWS region scoping (EE region cycle). `--aws-region <one|csv|all>`.
617
+ // get() returns undefined (absent), true (value-less flag β†’ error in main()),
618
+ // or the raw string ('us-east-1' | 'us-east-1,eu-west-1' | 'all').
619
+ const awsRegionVal = get('aws-region');
620
+ args.awsRegion = awsRegionVal === undefined ? undefined : awsRegionVal;
616
621
 
617
622
  return args;
618
623
  }
@@ -816,7 +821,7 @@ function maxSeverityInConclusion(conclusion) {
816
821
 
817
822
  export async function main() {
818
823
  const args = await parseArgs(process.argv);
819
- const { cmd, host, plugins, insecureHttps, hostFile, parallel, failOn, outputFormat, watch, intervalMinutes, webhookUrl, alertSeverity, ports, compliance, complianceScope } = args;
824
+ const { cmd, host, plugins, insecureHttps, hostFile, parallel, failOn, outputFormat, watch, intervalMinutes, webhookUrl, alertSeverity, ports, compliance, complianceScope, awsRegion } = args;
820
825
 
821
826
  // Version: handled before license verification so it works without a key.
822
827
  // CE-0.1.30.1 β€” closes the discovery-flag UX gap where pre-fix
@@ -848,6 +853,9 @@ Scan options:
848
853
  credentials). Override-on; missing file = hard error.
849
854
  --aws-profile <name> Use a named profile from the OS-default ~/.aws/credentials.
850
855
  Implies CLOUD_PROVIDER=aws; overrides explicit AWS_* keys.
856
+ --aws-region <r> AWS region scope: one (us-east-1), CSV (us-east-1,eu-west-1),
857
+ or 'all' (every account-enabled region). Default: AWS_REGION
858
+ if set, else a single region with an incomplete-coverage notice.
851
859
  --plugins <list|all> Plugins to run (e.g. 001,003,020 or "all"; default: all)
852
860
  --ports <range> Override port list (e.g. 22,80,443 or 1-1000)
853
861
  --out <dir> Output directory for scan artifacts
@@ -958,6 +966,17 @@ Docs: https://www.nsauditor.com/ai/ | Pricing: https://www.nsauditor.com/ai/
958
966
  process.exit(1);
959
967
  }
960
968
 
969
+ // Build the AWS region intent AFTER env load so AWS_REGION (.env/shell) is visible.
970
+ // Explicit --aws-region fail-fasts on an unknown region.
971
+ let awsRegionIntent = null;
972
+ try {
973
+ const { buildRegionIntent } = await import('./utils/region_intent.mjs');
974
+ awsRegionIntent = buildRegionIntent(awsRegion);
975
+ } catch (err) {
976
+ console.error(`Error: ${err.message}`);
977
+ process.exit(2);
978
+ }
979
+
961
980
  // Verify license JWT at startup (~5ms for ES256). Populates _verifiedTier
962
981
  // so all subsequent getTierFromEnv() calls return the cryptographically
963
982
  // validated tier instead of relying on prefix detection alone.
@@ -1830,6 +1849,7 @@ Docs: https://www.nsauditor.com/ai/ | Pricing: https://www.nsauditor.com/ai/
1830
1849
  if (ports) opts.ports = ports;
1831
1850
  if (compliance) opts.compliance = compliance;
1832
1851
  if (complianceScope) opts.complianceScope = complianceScope;
1852
+ if (awsRegionIntent) opts.awsRegionIntent = awsRegionIntent;
1833
1853
  const pm = await PluginManager.create(`${__dirname}/plugins`);
1834
1854
  const promptMode = String(process.env.OPENAI_PROMPT_MODE || 'basic').toLowerCase().trim();
1835
1855
 
package/mcp_server.mjs CHANGED
@@ -22,6 +22,7 @@ import {
22
22
  CallToolRequestSchema,
23
23
  ListToolsRequestSchema,
24
24
  } from '@modelcontextprotocol/sdk/types.js';
25
+ import { buildRegionIntent } from './utils/region_intent.mjs';
25
26
  import { getTierFromEnv, loadLicense } from './utils/license.mjs';
26
27
  import { resolveCapabilities } from './utils/capabilities.mjs';
27
28
  import { buildMarkdownReport } from './utils/report_md.mjs';
@@ -120,6 +121,24 @@ export function _setValidateHost(fn) {
120
121
  _validateHostFn = fn ?? validateHost;
121
122
  }
122
123
 
124
+ /**
125
+ * Build a RegionIntent for the MCP scan_cloud `regions` argument.
126
+ *
127
+ * DIVERGENT DEFAULT vs CLI: omitting `regions` (undefined/null) returns null
128
+ * so the plugins fall back to the server-configured AWS_REGION (single-region
129
+ * or env-driven). The caller must pass ["all"] EXPLICITLY to fan out to every
130
+ * enabled region β€” an implicit fan-out could blow Claude Desktop's tool timeout.
131
+ *
132
+ * @param {string[]|undefined|null} regions Tool arg value
133
+ * @returns {{ kind: 'all'|'list', explicit: true }|null}
134
+ */
135
+ export function buildScanCloudRegionIntent(regions) {
136
+ if (regions === undefined || regions === null) return null; // divergent default: do NOT fan out
137
+ if (!Array.isArray(regions)) throw new Error('regions must be an array of region codes or ["all"]');
138
+ if (regions.length === 1 && String(regions[0]).trim().toLowerCase() === 'all') return buildRegionIntent('all');
139
+ return buildRegionIntent(regions.join(','));
140
+ }
141
+
123
142
  // ---------------------------------------------------------------------------
124
143
  // Per-call cryptographic sentinel (CE 0.1.36 β€” Thread L Phase 2)
125
144
  // ---------------------------------------------------------------------------
@@ -206,6 +225,11 @@ const TOOLS = [
206
225
  items: { type: 'string', enum: ['aws', 'gcp', 'azure'] },
207
226
  description: 'Which cloud(s) to audit. Omit to audit all clouds the server is configured for.',
208
227
  },
228
+ regions: {
229
+ type: 'array',
230
+ items: { type: 'string' },
231
+ description: 'AWS region codes (e.g. ["us-east-1","eu-west-1"]) or ["all"] to scan every enabled region. OMIT to scan the server-configured AWS_REGION (or a single default) β€” omitting does NOT fan out to all regions; pass ["all"] explicitly for that.',
232
+ },
209
233
  },
210
234
  required: [],
211
235
  },
@@ -365,6 +389,11 @@ export async function handleScanCloud(args) {
365
389
  if (requested.length > 0) providers = requested;
366
390
  }
367
391
 
392
+ // Validate regions BEFORE the scan runs so a bad region rejects cleanly.
393
+ // buildScanCloudRegionIntent throws on unknown regions; returns null when
394
+ // omitted (divergent default: does NOT fan out β€” explicit ["all"] required).
395
+ const awsRegionIntent = buildScanCloudRegionIntent(args && args.regions);
396
+
368
397
  const pm = await getPluginManager();
369
398
 
370
399
  // Save/set/restore CLOUD_PROVIDER so the per-plugin .includes() gates pass and
@@ -374,7 +403,7 @@ export async function handleScanCloud(args) {
374
403
  let output;
375
404
  try {
376
405
  process.env.CLOUD_PROVIDER = providers.join(',');
377
- output = await pm.runCloud(providers);
406
+ output = await pm.runCloud(providers, awsRegionIntent ? { awsRegionIntent } : {});
378
407
  } finally {
379
408
  if (savedProvider === undefined) delete process.env.CLOUD_PROVIDER;
380
409
  else process.env.CLOUD_PROVIDER = savedProvider;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nsauditor-ai",
3
- "version": "0.1.97",
3
+ "version": "0.2.0",
4
4
  "description": "Modular AI-assisted network security audit platform β€” Community Edition",
5
5
  "type": "module",
6
6
  "private": false,
@@ -0,0 +1,50 @@
1
+ // utils/aws_regions.mjs
2
+ // Canonical AWS region list (pure data β€” NO SDK). Single source of truth for
3
+ // CLI validation + the static fallback when DescribeRegions is denied. Relocated
4
+ // from plugins/1040 (EE) so the CE base layer can validate --aws-region without
5
+ // the AWS SDK. EE imports this from the CE peer dep.
6
+ export const CANONICAL_REGIONS_BY_PARTITION = Object.freeze({
7
+ aws: Object.freeze([
8
+ 'us-east-1', 'us-east-2', 'us-west-1', 'us-west-2',
9
+ 'ca-central-1', 'ca-west-1',
10
+ 'eu-west-1', 'eu-west-2', 'eu-west-3',
11
+ 'eu-central-1', 'eu-central-2',
12
+ 'eu-north-1', 'eu-south-1', 'eu-south-2',
13
+ 'ap-northeast-1', 'ap-northeast-2', 'ap-northeast-3',
14
+ 'ap-southeast-1', 'ap-southeast-2', 'ap-southeast-3', 'ap-southeast-4',
15
+ 'ap-southeast-5', 'ap-southeast-7',
16
+ 'ap-south-1', 'ap-south-2',
17
+ 'ap-east-1',
18
+ 'sa-east-1',
19
+ 'me-south-1', 'me-central-1',
20
+ 'af-south-1',
21
+ 'il-central-1',
22
+ 'mx-central-1',
23
+ ]),
24
+ 'aws-cn': Object.freeze(['cn-north-1', 'cn-northwest-1']),
25
+ 'aws-us-gov': Object.freeze(['us-gov-east-1', 'us-gov-west-1']),
26
+ });
27
+
28
+ export const CANONICAL_REGION_LIST_VERSION = '2026-05';
29
+
30
+ export function detectPartition(region) {
31
+ if (typeof region !== 'string' || region.length === 0) return 'aws';
32
+ if (region.startsWith('cn-')) return 'aws-cn';
33
+ if (region.startsWith('us-gov-')) return 'aws-us-gov';
34
+ return 'aws';
35
+ }
36
+
37
+ const _ALL = Object.freeze(
38
+ Object.values(CANONICAL_REGIONS_BY_PARTITION).flat()
39
+ );
40
+
41
+ export function isKnownRegion(region) {
42
+ if (typeof region !== 'string') return false;
43
+ return _ALL.includes(region.trim().toLowerCase());
44
+ }
45
+
46
+ const _EMPTY = Object.freeze([]);
47
+
48
+ export function regionsForPartition(partition) {
49
+ return CANONICAL_REGIONS_BY_PARTITION[partition] ?? _EMPTY;
50
+ }
@@ -40,6 +40,18 @@ function findingsOf(r) {
40
40
  */
41
41
  export function summarizeCloudFindings(results, providerOf, cap = Number(process.env.CLOUD_FINDINGS_CAP) || 60) {
42
42
  const out = {};
43
+ // Scan for the FIRST plugin that carries a scanScope (AWS plugins emit it on
44
+ // result.summary.scanScope). Used to derive the incomplete-coverage advisory.
45
+ // This runs before the per-finding loop so even results with no findings still
46
+ // contribute their scanScope (e.g. a region-scoped scan with PASS on all checks).
47
+ let advisoryScope = null;
48
+ for (const r of (Array.isArray(results) ? results : [])) {
49
+ const sc = r?.result?.summary?.scanScope;
50
+ if (sc && typeof sc === 'object') { advisoryScope = sc; break; }
51
+ }
52
+ const advisory = incompleteCoverageAdvisory(advisoryScope);
53
+ if (advisory) out._incompleteCoverage = advisory;
54
+
43
55
  for (const r of (Array.isArray(results) ? results : [])) {
44
56
  const found = findingsOf(r);
45
57
  if (!found.length) continue;
@@ -59,6 +71,7 @@ export function summarizeCloudFindings(results, providerOf, cap = Number(process
59
71
  // Sort by severity (CRITICAL first) THEN truncate β€” so a CRITICAL is never evicted
60
72
  // from the displayed list by a HIGH. Counts above are always complete (pre-cap).
61
73
  for (const prov of Object.keys(out)) {
74
+ if (prov === '_incompleteCoverage') continue; // meta-key, not a provider bucket
62
75
  const b = out[prov];
63
76
  b.findings.sort((a, c) => (RANK[c.severity] || 0) - (RANK[a.severity] || 0));
64
77
  if (b.findings.length > cap) { b.truncated = true; b.findings = b.findings.slice(0, cap); }
@@ -66,10 +79,39 @@ export function summarizeCloudFindings(results, providerOf, cap = Number(process
66
79
  return out;
67
80
  }
68
81
 
82
+ /**
83
+ * Derive the scan-level incomplete-coverage advisory from a plugin's scanScope.
84
+ * Returns null unless scope was IMPLICIT (operator expressed no region intent).
85
+ * Informational + NOT harvested into compliance β†’ maps to zero controls.
86
+ *
87
+ * @param {object|null} scanScope A plugin's result.summary.scanScope object.
88
+ * @returns {{ severity: 'info', kind: string, text: string }|null}
89
+ */
90
+ export function incompleteCoverageAdvisory(scanScope) {
91
+ if (!scanScope || scanScope.source !== 'implicit-default') return null;
92
+ const unscanned = Array.isArray(scanScope.regionsKnownButNotScanned) ? scanScope.regionsKnownButNotScanned : [];
93
+ if (unscanned.length > 0) {
94
+ return {
95
+ severity: 'info',
96
+ kind: 'incomplete-region-coverage',
97
+ text: `Incomplete region coverage β€” ${unscanned.length} enabled region(s) not scanned (${unscanned.join(', ')}). Re-run with --aws-region all (or set AWS_REGION) for full coverage.`,
98
+ };
99
+ }
100
+ if (scanScope.resolveError) {
101
+ return {
102
+ severity: 'info',
103
+ kind: 'incomplete-region-coverage',
104
+ text: `Region scope was implicit and region enumeration could not be performed (${scanScope.resolveError}) β€” coverage NOT verified; pass --aws-region (or all) explicitly.`,
105
+ };
106
+ }
107
+ return null;
108
+ }
109
+
69
110
  /** Compact markdown from a summary. Named providers first, then any extras (e.g. 'unknown') so nothing is hidden. */
70
111
  export function renderCloudFindingsMarkdown(summary, providers) {
71
112
  const named = providers && providers.length ? providers.slice() : [];
72
- const order = [...named, ...Object.keys(summary).filter((p) => !named.includes(p))];
113
+ // Exclude the meta-key _incompleteCoverage from the provider rendering loop.
114
+ const order = [...named, ...Object.keys(summary).filter((p) => !named.includes(p) && p !== '_incompleteCoverage')];
73
115
  const lines = [];
74
116
  for (const prov of order) {
75
117
  const b = summary[prov]; if (!b) continue;
@@ -79,5 +121,11 @@ export function renderCloudFindingsMarkdown(summary, providers) {
79
121
  if (b.truncated) lines.push(`- _…CRITICAL/HIGH list truncated; see counts above for totals._`);
80
122
  lines.push('');
81
123
  }
124
+ // Append the incomplete-coverage advisory (when present) as an informational note.
125
+ // This advisory is summary-only and maps to zero compliance controls.
126
+ if (summary._incompleteCoverage) {
127
+ lines.push(`> **ℹ️ Advisory:** ${summary._incompleteCoverage.text}`);
128
+ lines.push('');
129
+ }
82
130
  return lines.join('\n').trim();
83
131
  }
@@ -0,0 +1,30 @@
1
+ // utils/region_intent.mjs
2
+ // Pure CE helper: turn the raw --aws-region arg into a RegionIntent, fail-fast
3
+ // validating EXPLICIT regions against the canonical list. No SDK. The EE resolver
4
+ // expands the intent to concrete regions (DescribeRegions for 'all').
5
+ import { isKnownRegion } from './aws_regions.mjs';
6
+
7
+ export function buildRegionIntent(rawArg) {
8
+ if (rawArg === undefined) return null; // no flag β†’ env/implicit downstream
9
+ if (rawArg === true) { // value-less flag
10
+ throw new Error("--aws-region requires a value (e.g. --aws-region us-east-1 | us-east-1,eu-west-1 | all)");
11
+ }
12
+ const raw = String(rawArg).trim();
13
+ if (raw.toLowerCase() === 'all') return { kind: 'all', explicit: true };
14
+ const regions = raw.split(',').map((s) => s.trim().toLowerCase()).filter(Boolean);
15
+ if (regions.length === 0) {
16
+ throw new Error("--aws-region requires at least one region or 'all'");
17
+ }
18
+ const allowUnknown = process.env.NSA_AWS_REGION_ALLOW_UNKNOWN === '1';
19
+ if (!allowUnknown) {
20
+ for (const r of regions) {
21
+ if (!isKnownRegion(r)) {
22
+ throw new Error(
23
+ `unknown AWS region '${r}'. Valid regions: see the canonical list ` +
24
+ `(set NSA_AWS_REGION_ALLOW_UNKNOWN=1 to bypass for a brand-new region).`,
25
+ );
26
+ }
27
+ }
28
+ }
29
+ return { kind: 'list', regions, explicit: true };
30
+ }