nsauditor-ai 0.2.1 β†’ 0.2.3

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,15 +17,7 @@ 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.2.1 + Enterprise 0.18.0** (June 2026)
21
-
22
- - πŸ›‘οΈ **GCP false-negative hardening** (Enterprise 0.18.0 β€” paired CE 0.2.1, docs-only) β€” five substrate-depth false-negative closures in the GCP auditors, all on already-covered controls (no new controls); the impersonation findings are now live-validated against a real GCP project (see below):
23
- - **GCP evidence-gaps now fail their controls** β€” a denied GCP firewall / IAM / bucket enumeration (`AccessDenied`) now routes into the scan findings (single-owner anchors) and **FAILS** its controls instead of reading clean (was a compliance-layer false-CLEAN).
24
- - **GCP project-IAM public-exposure detection now actually fires** β€” the project-IAM-public check was calling `getIamPolicy` on the wrong GCP client (the Compute `ProjectsClient`, which has no IAM methods), so it failed on every live scan and never detected a publicly-bound project IAM policy; it now reads the project IAM policy via the correct Resource Manager `ProjectsClient`, so `allUsers` / `allAuthenticatedUsers` project-level bindings are detected instead of perpetually evidence-gapping.
25
- - **GCP IAM auditor now authenticates under pure ADC** β€” the GCP IAM auditor's `googleapis` IAM-admin client (which powers the custom-role audit, service-account-key custody, and the service-account impersonation graph) only set credentials for the impersonation / key-file paths; under plain Application Default Credentials it had **no** auth, and β€” unlike the `@google-cloud` clients, which auto-detect ADC β€” the `googleapis` REST library does not, so those deeper IAM dimensions returned `AccessDenied` and never ran even for a project owner. They now run under pure ADC via an explicit scoped credential. (Caught by the live test-infra smoke; same class as the project-IAM client fix above.)
26
- - **Legacy-ACL public-exposure detection in GCP Cloud Storage** β€” a bucket made public via a **legacy ACL** (`allUsers` / `allAuthenticatedUsers`) while Uniform Bucket-Level Access is disabled was reading CLEAN; the auditor now scans the bucket ACL + a sampled object-ACL surface β†’ CRITICAL / HIGH (routed to SOC 2 CC6.6 / HIPAA 164.312(a)(1) / CIS v8 3.3).
27
- - **GCP IAM impersonation-BFS completeness** β€” project-scope `roles/iam.serviceAccountKeyAdmin` (mint a long-lived key for any service account = offline impersonation) now fires the project-scope impersonation CRITICAL, and a service account privileged via an **admin-equivalent custom role** (`iam.serviceAccounts.actAs`…) is now marked admin in the impersonation graph so paths terminating there are detected instead of reading clean. Both now fire **live** against a real owner-ADC GCP project (project-scope `serviceAccountKeyAdmin` CRITICAL + a custom-role-`actAs` SA reached over a `tokenCreator` edge).
28
- - 🌐 **`--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.
20
+ **Latest: CE 0.2.3 + Enterprise 0.18.2** (June 2026) β€” the MCP `scan_cloud` summary now **surfaces evidence-gaps**: a dedicated "Evidence gaps (unverified)" section so a Claude Desktop auditor sees the no-false-clean "we couldn't verify this" disclosures instead of a silent LOW count. Paired with **EE 0.18.2**, which retrofits the AWS S3 / Azure / IAM plugins to mark their scan-coverage gaps via a new CI producer-contract (so they reach the collector across all three clouds). Plugin count UNCHANGED at 28; all six coverage matrices UNCHANGED.
29
21
 
30
22
  β†’ Full release history: **[CHANGELOG.md](./CHANGELOG.md)**
31
23
  β†’ 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)
package/mcp_server.mjs CHANGED
@@ -216,7 +216,7 @@ const TOOLS = [
216
216
  {
217
217
  name: 'scan_cloud',
218
218
  description:
219
- 'Audit one or more cloud accounts (AWS / GCP / Azure) for security & compliance posture using the credentials configured in the server environment. No network host required. Requires an Enterprise license. Audit ONLY the cloud(s) the user names β€” pass providers:["aws"] for "audit my AWS account"; omit providers only when the user asks to audit ALL clouds. Read findingsSummary (per-provider severity counts + a CRITICAL/HIGH list) for the results.',
219
+ 'Audit one or more cloud accounts (AWS / GCP / Azure) for security & compliance posture using the credentials configured in the server environment. No network host required. Requires an Enterprise license. Audit ONLY the cloud(s) the user names β€” pass providers:["aws"] for "audit my AWS account"; omit providers only when the user asks to audit ALL clouds. Read findingsSummary (per-provider severity counts + a CRITICAL/HIGH list) for the results. findingsSummary[provider].evidenceGaps lists checks the scan could NOT verify (AccessDenied / truncated enumeration) β€” treat these as "unverified posture", NOT as clean.',
220
220
  inputSchema: {
221
221
  type: 'object',
222
222
  properties: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nsauditor-ai",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Modular AI-assisted network security audit platform β€” Community Edition",
5
5
  "type": "module",
6
6
  "private": false,
@@ -35,8 +35,11 @@ function findingsOf(r) {
35
35
  /**
36
36
  * @param {Array} results out.results (each {id, result:{findings|data}})
37
37
  * @param {(id:any)=>string|null} providerOf plugin id -> cloudProvider
38
- * @param {number} [cap] max CRITICAL/HIGH entries listed per provider
39
- * @returns {Object} { [provider]: { counts:{SEV:n}, findings:[{severity,plugin,title}], truncated:boolean } }
38
+ * @param {number} [cap] max CRITICAL/HIGH entries listed per provider (also caps evidenceGaps independently)
39
+ * @returns {Object} { [provider]: { counts:{SEV:n}, findings:[{severity,plugin,title}], truncated:boolean,
40
+ * evidenceGaps:[{severity,plugin,title}], evidenceGapsTruncated?:boolean } }
41
+ * evidenceGaps surfaces findings carrying `details.evidenceGap === true` (the no-false-clean
42
+ * "could not verify" disclosures) REGARDLESS of severity, so a LOW/INFO gap is never invisible.
40
43
  */
41
44
  export function summarizeCloudFindings(results, providerOf, cap = Number(process.env.CLOUD_FINDINGS_CAP) || 60) {
42
45
  const out = {};
@@ -59,13 +62,16 @@ export function summarizeCloudFindings(results, providerOf, cap = Number(process
59
62
  // bucketed under 'unknown' rather than silently dropped (defense-in-depth against a
60
63
  // future id collision / cloudProvider drift β€” never let a real finding vanish).
61
64
  const prov = providerOf(r?.id ?? r?.result?.id) || UNKNOWN_PROVIDER;
62
- const bucket = (out[prov] ||= { counts: {}, findings: [], truncated: false });
65
+ const bucket = (out[prov] ||= { counts: {}, findings: [], evidenceGaps: [], truncated: false });
63
66
  for (const x of found) {
64
67
  const sev = String(x?.severity || x?.level || 'INFO').toUpperCase();
65
68
  bucket.counts[sev] = (bucket.counts[sev] || 0) + 1;
66
69
  if (sev === 'CRITICAL' || sev === 'HIGH') {
67
70
  bucket.findings.push({ severity: sev, plugin: String(r?.id ?? ''), title: describeFinding(x) });
68
71
  }
72
+ if (x && typeof x === 'object' && x.details && x.details.evidenceGap === true) {
73
+ bucket.evidenceGaps.push({ severity: sev, plugin: String(r?.id ?? ''), title: describeFinding(x) });
74
+ }
69
75
  }
70
76
  }
71
77
  // Sort by severity (CRITICAL first) THEN truncate β€” so a CRITICAL is never evicted
@@ -75,6 +81,7 @@ export function summarizeCloudFindings(results, providerOf, cap = Number(process
75
81
  const b = out[prov];
76
82
  b.findings.sort((a, c) => (RANK[c.severity] || 0) - (RANK[a.severity] || 0));
77
83
  if (b.findings.length > cap) { b.truncated = true; b.findings = b.findings.slice(0, cap); }
84
+ if (b.evidenceGaps.length > cap) { b.evidenceGapsTruncated = true; b.evidenceGaps = b.evidenceGaps.slice(0, cap); }
78
85
  }
79
86
  return out;
80
87
  }
@@ -119,6 +126,8 @@ export function renderCloudFindingsMarkdown(summary, providers) {
119
126
  lines.push(`## ${String(prov).toUpperCase()} β€” ${c.CRITICAL || 0} CRITICAL Β· ${c.HIGH || 0} HIGH Β· ${c.MEDIUM || 0} MEDIUM Β· ${c.LOW || 0} LOW Β· ${c.PASS || 0} PASS`);
120
127
  for (const f of (b.findings || [])) lines.push(`- **[${f.severity}]** ${f.plugin}: ${f.title}`);
121
128
  if (b.truncated) lines.push(`- _…CRITICAL/HIGH list truncated; see counts above for totals._`);
129
+ for (const g of (b.evidenceGaps || [])) lines.push(`- **[⚠ EVIDENCE GAP β€” unverified]** ${g.plugin}: ${g.title}`);
130
+ if (b.evidenceGapsTruncated) lines.push(`- _…evidence-gap list truncated; see LOW count for totals._`);
122
131
  lines.push('');
123
132
  }
124
133
  // Append the incomplete-coverage advisory (when present) as an informational note.