Coverage Honesty Comes to Azure and GCP: Inside NSAuditor AI Enterprise 0.39.0
Every GCP and Azure plugin now declares what it does not evaluate — including over an empty estate. Inside the 0.39.0 release and why an underclaim costs more.
An empty findings list is the most ambiguous output a security scanner can produce. It means one of two things, and they are not remotely equivalent: we examined this surface and it was clean, or we never examined this surface at all. A report that cannot distinguish them is not evidence. It is a shape that looks like evidence.
NSAuditor AI Enterprise 0.39.0, released 19 August 2026 alongside Community Edition 0.2.44 and agent-skill 0.2.42, closes that ambiguity across every cloud provider the product audits. Every GCP and Azure plugin now declares, in the report itself, which surfaces it does not evaluate.
The gap was asymmetric, and that made it worse
NSAuditor has shipped machine-readable coverage declarations — deferredScope — for some time. Until this release, all nine of them sat on AWS plugins. The seven GCP and Azure plugins declared nothing at all.
That asymmetry is more dangerous than a uniform silence would have been. A reader who opens an AWS section and finds an explicit list of what was excluded by design learns, correctly, that this vendor discloses its boundaries. The same reader then opens the Azure section, sees no such list, and draws the obvious inference: there was nothing to exclude. AWS disclosing is precisely what made the others’ silence read as completeness.
The condition was field-confirmed on a live operator scan before the work started: deferredScope came back empty on Azure beside a populated AWS list.
What ships
Seven new declarations across plugins 1021, 1022, 1024 and 1025 — the GCP and Azure scanners and deep auditors — and 1220, 1221 and 1222, the dedicated Azure auditors. Each carries 8 to 12 static boundaries.
Re-measured on this release against real cloud accounts, through the built packages: AWS reports 9 declarations, Azure 4, GCP 3. The two that read empty no longer do.
Emitted at run scope — including over an empty estate
Each declaration is emitted at run scope on the audited path, and that includes the case of an account with no resources in it. This is the case that matters most and the one most easily overlooked: an empty estate produces zero findings whether or not anything was ever examined, so it is exactly where a coverage statement earns its keep.
None is emitted on a precondition-failure path, where no audit ran at all. A scan that could not start is a different fact and gets a different signal — conflating the two would replace one ambiguity with another.
Ordered by materiality, not by code layout
The Community Edition’s MCP summary abridges each declaration to 420 characters, so ordering is not cosmetic. Boundaries that would invalidate a cross-cutting conclusion lead; per-resource depth trails. Two boundaries were promoted out of places no report reader could ever reach — one from a code comment, one from a data field the summariser never reads.
The precision mattered more than the count
Every boundary was verified against the implementing code rather than against a keyword search, and that changed four of them before release.
The token serviceTag appears nowhere in the product’s source. A keyword-driven process would have concluded that Azure service tags were unhandled and published that as deferred. In fact plugin 1221 handles the Azure Internet and AzureCloud service tags with their own severity calibration. Publishing that boundary would have denied a capability that ships.
Three more were narrowed the same way: key-vault rotation policy presence is evaluated (only the interval is not); diagnostic export is evaluated (only its destination and retention are not); and Workload Identity Federation admin roles are flagged (only pool trust configuration is unread).
An overstated boundary is an underclaim, and an underclaim is the expensive direction, because nothing ever complains about it. A customer who is told a capability is missing does not file a bug — they simply price the product lower, or buy something else.
Two AWS declarations became estate-independent
Plugins 1080 and 1130 emitted their declarations inside a per-region helper, so the count scaled with the number of enabled regions and each copy was region-stamped. That is why an operator scan reported ten declarations against nine producers. Both now emit once per run on any estate, matching plugin 1090.
Two sentences were corrected rather than carried across: both plugins had stated that multi-region enumeration was deferred, while both in fact fan out across every in-scope region. Another underclaim, found by reading the code.
GCP impersonation now refuses instead of degrading
With GOOGLE_IMPERSONATE_SERVICE_ACCOUNT configured, IAM calls were returning Google’s phrasing for a request carrying no credentials, while the same project on ordinary application-default credentials enumerated normally. An operator who configured impersonation lost service-account, custom-role, service-account-key and impersonation-path detection, and was told “could not enumerate” — not a false clean, but a false reason, which sends someone to check permissions they already hold.
Two changes ship. The authentication headers shim now satisfies both of its consumer contracts: it had been returning a Headers instance, which has no enumerable own properties, so a consumer spreading it received an empty object and the Authorization header was silently dropped. And a preflight now refuses an impersonated client that yields no Authorization header — by name, carrying the target principal and the remedy — surfacing as a scanner-down condition rather than as an audit that found nothing. That is fail-closed by design; a loud refusal is the correct outcome.
Stated plainly, because the distinction is the point: driving the real impersonation client from the vendor’s own tree, the REST path does authenticate. The field mechanism is not reproduced, and this is not a claim to have diagnosed it. What ships instead makes the entire class unrepresentable — whatever produces a credential-less impersonated client, the scan now refuses it.
A command you could run and could not find
The previous release, 0.38.0, was named after two commands: compliance sign-pack and compliance verify-pack. They worked. They were documented in both packages’ READMEs, both changelogs, the design record and the integration contract. They were missing from nsauditor-ai help for the whole of that release — the one place a user looks to find out what a tool can do.
The reason nothing caught it is worth stating rather than burying: every reachability check the product runs asks whether a customer can reach a capability, and all of them correctly answered yes. None asked whether a customer could find it. Those are different properties and only one of them had a test. Both commands now carry usage text, and a new check derives the list of verbs the CLI actually dispatches and fails the build if the help text and that list disagree in either direction.
Community Edition, and why it needed its own release
The Community package’s MCP scan_cloud description told an AI assistant that only the AWS plugins declared their capability boundaries. Enterprise 0.39.0 falsifies that — but the deeper problem is that the sentence was unsound in principle. Community Edition floats over a range of Enterprise versions and cannot derive its plugin roster at all, so any sentence counting providers was a claim it had no way to check.
It now states the invariant that holds in both states: not every plugin declares its boundaries, so the list bounds only what the declaring plugins state, and an empty or short list is never a claim of full coverage. npm freezes a package’s text at publish, so correcting a string an assistant reads on every scan requires a release of its own.
What has not changed, said explicitly
The plugin catalog is unchanged at 28 Enterprise plugins, 55 overall — 27 cloud auditors across AWS, Azure and GCP, plus one network-scan zero-trust posture assessment.
All seven compliance coverage matrices are unchanged (covered / partial / out-of-scope):
| Framework | Matrix | Granularity |
|---|---|---|
| SOC 2 (AICPA TSC 2017) | 10 / 4 / 37 | Trust Services Criteria |
| HIPAA Security Rule §164.312 | 7 / 3 / 45 | Technical Safeguards |
| NIST CSF 2.0 | 13 / 10 / 83 | Subcategory level |
| PCI DSS v4.0.1 | 19 / 9 / 39 | Sub-requirement level, 67 of ~250 |
| ISO/IEC 27001:2022 | 17 / 14 / 62 | Per Annex A control |
| CIS Controls v8 | 17 / 23 / 113 | Per Safeguard, IG1/IG2/IG3 |
| GDPR Article 32 | 4 / 5 / 2 | Security of Processing infrastructure substrate only, never GDPR compliance |
That the matrices did not move is a claim, not an oversight. A deferredScope declaration routes to zero controls by design, re-measured across all 49 declaration-by-framework combinations at zero control violations, beside a live negative control that correctly routed to three SOC 2 controls. A disclosure that a surface was not examined must never be filed as evidence that it passed.
Availability
Community Edition is MIT-licensed on npm as nsauditor-ai. Pro and Enterprise ship as @nsasoft/nsauditor-ai-ee and install alongside it. Enterprise 0.39.0 requires Community 0.2.43 or newer — unchanged from the previous release, asserted from the package manifest rather than assumed: nothing in this release imports Community surface that did not already exist. Upgrading Enterprise alone is sufficient; Community 0.2.44 carries two corrections to text that npm freezes at publish, which is why it ships in the same trio rather than waiting.
NSAuditor AI runs on the customer’s own infrastructure with agentless, read-only credentials. Scan data is not collected, transmitted or stored by the vendor; the outbound paths the product can take are enumerated in the egress register published in its architecture documentation.
Full details: NSAuditor AI Enterprise · Pricing



