nsauditor-ai-agent-skill 0.2.51 → 0.2.53

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.
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "nsauditor-ai-agent-skill",
3
+ "owner": {
4
+ "name": "Nsasoft US LLC",
5
+ "url": "https://www.nsauditor.com"
6
+ },
7
+ "plugins": [
8
+ {
9
+ "name": "nsauditor-ai",
10
+ "source": "./",
11
+ "description": "Network security auditing and eight-framework compliance evidence with NSAuditor AI, for Claude Code."
12
+ }
13
+ ]
14
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "nsauditor-ai",
3
+ "description": "Network security scanning, vulnerability assessment and cloud compliance-evidence auditing with NSAuditor AI, via its MCP server.",
4
+ "author": {
5
+ "name": "Nsasoft US LLC",
6
+ "url": "https://www.nsauditor.com"
7
+ },
8
+ "homepage": "https://github.com/nsasoft/nsauditor-ai-agent-skill",
9
+ "repository": "https://github.com/nsasoft/nsauditor-ai-agent-skill.git",
10
+ "license": "MIT",
11
+ "keywords": ["nsauditor", "network-security", "vulnerability-assessment", "compliance", "mcp"]
12
+ }
package/README.md CHANGED
@@ -9,9 +9,15 @@ An installable knowledge package that teaches AI coding agents how to use NSAudi
9
9
 
10
10
  Works with **Claude Code**, **Claude Desktop**, **Cursor**, **Windsurf**, **VS Code Copilot**, and any MCP-aware agent.
11
11
 
12
+ > **About the `<!-- nsa:derived id="…" -->…<!-- /nsa:derived -->` markers in `SKILL.md`:** the text between them is a fact rendered by the Enterprise build from the trio's `package.json` files and shipped framework data (versions, the Community floor, plugin counts, matrix movement). Quote the text; the markers are only how it stays current.
13
+
12
14
  ## Current release
13
15
 
14
- **0.2.51** — paired with **Enterprise 0.46.0 / Community 0.2.53**. ⚠️ **NOT a floor bump: Community stays >= 0.2.49.** `references/plugins.md` carried the retired `MVP-67` PCI density label; the enumeration is **72 — 19 covered, 9 partial, 44 out of scope with a stated reason**, derived from the shipped framework file. The line already told a reader to call `compliance_matrix` for the current triple and that did not save it: **a pointer beside a hard-coded number does not stop the number rotting; only removing the number does.**
16
+ **0.2.53** — paired with **Enterprise 1.1.0 / Community 0.2.55**. ⚠️ **A floor bump: requires Community >= 0.2.55** (it was >= 0.2.49 — Enterprise 1.1.0 calls code that first ships in Community 0.2.55). The skill now INSTALLS itself: `npx nsauditor-ai-agent-skill install` copies it into `~/.claude/skills/` for Claude Code, and `npx nsauditor-ai-agent-skill build-zip --out ~/Desktop` builds the Claude Desktop upload zip. Claude Code users can instead add it as a plugin marketplace — `/plugin marketplace add nsasoft/nsauditor-ai-agent-skill`. ⚠️ A marketplace reaches **Claude Code only**; Claude Desktop accepts skills only as an uploaded zip, and skills do not sync between surfaces. Knowledge updated for the Pro cross-run delta (`report --since`) that ships in Community 0.2.55.
17
+
18
+ **Prior: 0.2.52** — paired with **Enterprise 1.0.0 / Community 0.2.54** (Community is a README-only release). ⚠️ **NOT a floor bump: Community stays >= 0.2.49.** Plugin enumeration unchanged at 29 Enterprise plugins; all eight coverage matrices unchanged. New in this release's teaching: `Findings analyzed` counts findings rather than issues, so a 1.0.0 pack reads a SMALLER number than a 0.46.0 pack over the same estate and that is the correction — the new `report.summary.findingsReceived` / `findingsRouted` fields let an agent check `findingsRouted ≤ findingCount ≤ findingsReceived` from the pack alone; an Azure RBAC assignment at the root scope (`/`) is named as such; PCI DSS `11.5.2` is critical-file change detection (the payment-page controls are `11.6.1` and `6.4.3`). No tool surface changed. The evidence contract in Enterprise's `docs/contract-v1.md` is binding from EE 1.0.0. See [CHANGELOG.md](https://github.com/nsasoft/nsauditor-ai-agent-skill/blob/main/CHANGELOG.md).
19
+
20
+ **Prior: 0.2.51** — paired with **Enterprise 0.46.0 / Community 0.2.53**. ⚠️ **NOT a floor bump: Community stays >= 0.2.49.** `references/plugins.md` carried the retired `MVP-67` PCI density label; the enumeration is **72 — 19 covered, 9 partial, 44 out of scope with a stated reason**, derived from the shipped framework file. The line already told a reader to call `compliance_matrix` for the current triple and that did not save it: **a pointer beside a hard-coded number does not stop the number rotting; only removing the number does.**
15
21
 
16
22
  **Prior: 0.2.50** — paired with **Enterprise 0.45.0 / Community 0.2.52**. ⚠️ **NOT a floor bump: Community stays >= 0.2.49.** New in this release's teaching, and it is a REFUSAL rule before it is a feature note: **the NTP clock attestation is WITHDRAWN** — the probe is deleted, the three `complianceNtp*` options are gone, there is no `NSAUDITOR_NTP_*` environment variable and there never was one, and it is NOT on a roadmap, so a hedge like "planned" is now a promise rather than a disclosure. The probe validated any reply of at least 48 bytes with a non-null transmit timestamp and nothing else — no mode byte, no stratum, no originate echo — and it measured the SCANNER's clock while every framework control about time synchronisation asks about the customer's estate. Point users at **opt-in RFC 3161 timestamping** instead (`NSAUDITOR_TSA_URL`; an outbound call to a third party, off by default), whose `.tsr` time comes from the authority rather than from the host. ⚠️ **The attestation's shape did NOT change** — all six `ntp` keys remain, now constants, because the envelope is a frozen schema id; a null `probedAt` is not a failed probe.
17
23
 
@@ -90,7 +96,7 @@ and never that one failed, and must not be reported as tampering. The commands a
90
96
  **CLI-only**; no MCP tool reaches them, so an agent asked to record an approval must shell out
91
97
  rather than look for a tool.
92
98
 
93
- See [CHANGELOG.md](CHANGELOG.md) for the full history.
99
+ See [CHANGELOG.md](https://github.com/nsasoft/nsauditor-ai-agent-skill/blob/main/CHANGELOG.md) for the full history.
94
100
 
95
101
  ## What's Inside
96
102
 
@@ -125,7 +131,7 @@ Claude Code auto-discovers skills in `.claude/skills/`.
125
131
 
126
132
  ### Claude Desktop
127
133
 
128
- Upload `SKILL.md` as project knowledge in your Claude Desktop project settings.
134
+ Build the upload zip with `npx nsauditor-ai-agent-skill build-zip --out ~/Desktop`, then upload that `nsauditor-ai-skill-<version>.zip` in Claude Desktop's Settings (skills). Upload the zip, never `SKILL.md` alone: SKILL.md is under half of the skill, and its links to the reference files would point at nothing. Fully quit and relaunch Desktop after replacing a skill.
129
135
 
130
136
  ### Cursor
131
137
 
@@ -162,7 +168,7 @@ When an AI agent loads this skill, it gains:
162
168
  | **Security rules** | ZDE, SSRF protection, redaction, scan authorization requirements |
163
169
  | **Error handling** | License gates, SSRF blocks, timeout resolution, CPE format errors |
164
170
  | **Decision routing** | When to use scan_host vs probe_service vs CLI vs get_vulnerabilities |
165
- | **Cloud-region scoping** | The MCP `scan_cloud` `regions` argument — pass `["all"]` (or a region-code list like `["us-east-1","eu-west-1"]`) to audit every / specific AWS regions; **omit it to scan the server-configured `AWS_REGION`** (omitting does NOT fan out — pass `["all"]` explicitly for full coverage, mindful of the Desktop tool-call timeout). Mirrors the CE CLI `--aws-region <one\|csv\|all>` flag. |
171
+ | **Cloud-region scoping** | The MCP `scan_cloud` `regions` argument — pass `["all"]` (or a region-code list like `["us-east-1","eu-west-1"]`) to audit every / specific AWS regions; **omit it** and the auditors that take their region from the client scan the server-configured `AWS_REGION` only, while the ones that enumerate their own region list (CloudTrail trail discovery, GuardDuty/Inspector, EC2 instances) still attempt every enabled region — pass `["all"]` explicitly to extend every region-scoped check, mindful of the Desktop tool-call timeout. Mirrors the CE CLI `--aws-region <one\|csv\|all>` flag. |
166
172
 
167
173
  ## Prerequisites
168
174
 
package/SKILL.md CHANGED
@@ -16,7 +16,9 @@ description: >
16
16
 
17
17
  # NSAuditor AI — Agent Skill
18
18
 
19
- > **Version:** 0.2.51 (knowledge current as of **EE 0.46.0** · **requires CE ≥ 0.2.49**) — the PCI-citation-provenance release. **PCI DSS is 19 covered / 9 partial / 44 out of scope across 72 enumerated sub-requirements**; the other seven matrices are unchanged. ⚠️ **Do not cite `4.2.2.1` — it does not exist in PCI DSS v4.0.1** — and do not attribute Customized-Approach ineligibility to any appendix: the standard states it in each requirement's own Customized Approach Objective cell. Sampling is Section 6, which prescribes no sample size. For any PCI triple, call `compliance_matrix` rather than quoting a number from this file.
19
+ > **Version:** <!-- nsa:derived id="skill-version" -->0.2.53<!-- /nsa:derived --> (knowledge current as of **EE <!-- nsa:derived id="ee-version" -->1.1.0<!-- /nsa:derived -->** · **requires CE ≥ <!-- nsa:derived id="ce-floor" -->0.2.55<!-- /nsa:derived -->**) — the delta release: the product can now answer *what changed since the last scan*. Teach three things: (1) **`nsauditor-ai report --from <dir> --since <runId|prior>`** (Pro/Enterprise) compares two runs and reports new / resolved / severity-changed findings. (2) **NEVER present a `resolved` count as remediation on its own** — the report carries a NOT-COMPARABLE bucket with a reason per finding (host not scanned · the producing plugin did not run, ERRORED or TIMED OUT on that host, or cannot be identified · an evidence gap such as AccessDenied · **a PORT that was not measured on that host — a check that could not complete its connection, or a port that STOPPED ANSWERING between the two runs (a port that went quiet is not a fix)** · **the two runs COVERED DIFFERENT SCOPES — a narrower `--aws-region`, a different Azure subscription or GCP project — so that surface was not looked at, which is NOT the same as clean** · framework enumeration moved · **the PRODUCER CHANGED WHAT IT NAMES between the two releases**), and a finding that vanished for any of those reasons was not fixed. ⚠️ **THE LAST ONE IS NEW IN THIS RELEASE AND IT FIRES ON THE UPGRADE ITSELF.** FOURTEEN producers changed what identifies a finding at EE 1.1.0 — thirteen plugins that had left the object empty, named the REGION, recorded no region the report reads, carried a HIGH sentence that had to be corrected (1110, the effective-decrypt auditor), or (1023, the Zero Trust checker) now name the port a finding is about and keep the open-port count out of its title, and one analysis AGENT whose findings are keyed on their TITLE because they carry no object field at all. A comparison that straddles that upgrade reports those rows NOT-COMPARABLE instead of differencing them, so one surface under two keys is never read as a fix plus a fresh exposure. **So the first `report --since` a user runs across the 1.1.0 upgrade will legitimately show a LARGE not-comparable bucket — that is the declaration working, not a broken report.** Derive the reason list from `NOT_COMPARABLE_REASONS` in Community's `utils/scan_delta.mjs` rather than from this sentence; it is held in equality with what the engine emits, and this sentence is prose that has disagreed with it before. If a user asks what got fixed, read the NOT-COMPARABLE list before answering. **Two conditions refuse the whole comparison rather than bucketing findings**: the two runs straddle a product boundary where a reported number changed meaning, and the two runs ran at DIFFERENT LICENCE TIERS — the set of producers that ran differs, so a whole producer's findings would read as remediation. ⚠️ **AND ONE OF THOSE REASONS IS DECLARED RATHER THAN CHECKED: framework-enumeration movement is NOT EVALUATED in this edition**, because a run record carries no framework enumeration. The report says so as a limit on every comparison and each row's basis reads `framework enumeration: not evaluated`. Never tell a user that framework movement was ruled out. (3) **Integrity is a hash chain, not a signature — and say WHAT is sealed.** At finalize the run record AND each written host's findings files are digested over their exact persisted bytes, with the per-file digests sealed inside the record, which is itself digested and chained through `prevDigest`. So an altered **findings file** — which is what the comparison actually reads — is detected and NAMED, not merely an altered index; a file that did not exist at seal time is sealed as an explicit absence, so adding one is detected too. **BOTH runs are verified, not only the baseline.** An altered or unmeasurable run on either side REFUSES the comparison outright, and the refusal names which side. A file that cannot be READ is reported as unmeasurable rather than as tampering, and a record written before per-host sealing keeps its verdict while its reason says the findings files were not covered. The guarantee is tamper-EVIDENT against corruption and unsophisticated edits, **NOT tamper-proof against host-level access and NOT non-repudiation**. Do not describe it as file-integrity monitoring or a change-detection mechanism: PCI DSS `11.5.1` / `11.5.2` / `11.6.1` are enumerated OUT OF SCOPE in the shipped matrix. The code ships in Community; the capability is licence-gated to Pro/Enterprise. ⚠️ **`--since` REQUIRES COMMUNITY 0.2.55 OR LATER, AND THE PEER FLOOR NOW SAYS SO.** It moved to the version in the badge above because **Enterprise CALLS the new Community code** — the per-plugin run statuses the Zero Trust checker reads before it will assert a posture, and the run-record fields `report --since` compares. The earlier floor said otherwise on the grounds that Enterprise did not call it; that stopped being true in this release. If a user reports the flag is unrecognised, check their Community version first. (4) **A `kms:Decrypt on Resource:*` HIGH from 1110 is NEVER downgraded on a live run.** 1110 reads KMS key policies and grants in the configured region only, and one region cannot show that NO key trusts a principal — a key in another region may — so the HIGH→INFO downgrade it used to apply is withheld on every shipped path. The reason is in that plugin's run summary as `summary.kmsDowngrade` (`reason` `single-region-enumeration` for a complete read; `access-denied` · `per-key-error` · `keys-truncated` · `grants-truncated` for an incomplete one, which also emits an evidence-gap row). Never tell a user a surviving HIGH was checked against every key, and never read a missing downgrade as a scanner fault. Its grant finding (`kms-grant-decrypt-no-identity-grant`, MEDIUM) likewise covers keys in the configured region only. (5) **The NVD store and response cache MOVED at EE 1.1.0 and every run records where they are.** They used to be a `.nvd-cache` folder in whatever directory the scan started from; they now resolve, from any directory, to `--cache-dir`, else `NVD_CACHE_DIR`, else `NSAUDITOR_NVD_CACHE_DIR`, else `~/.nsauditor/nvd_cache`. An old `.nvd-cache` folder is NOT read, and the scan says so. If a user reports that offline CVE matching stopped after upgrading, that is the cause: move the folder once or set `NVD_CACHE_DIR` to it. Never read the resulting coverage gaps as a clean host. Plugin count UNCHANGED at <!-- nsa:derived id="plugins:ee" -->29<!-- /nsa:derived -->; <!-- nsa:derived id="matrix-movement" -->all eight coverage matrices unchanged since EE 1.0.0<!-- /nsa:derived -->; Community stays at <!-- nsa:derived id="ce-version" -->0.2.55<!-- /nsa:derived -->.
20
+ >
21
+ > **Prior: 0.2.51** (post-EE-0.46.0 · **requires CE ≥ 0.2.49**) — the PCI-citation-provenance release. **PCI DSS is 19 covered / 9 partial / 44 out of scope across 72 enumerated sub-requirements**; the other seven matrices are unchanged. ⚠️ **Do not cite `4.2.2.1` — it does not exist in PCI DSS v4.0.1** — and do not attribute Customized-Approach ineligibility to any appendix: the standard states it in each requirement's own Customized Approach Objective cell. Sampling is Section 6, which prescribes no sample size. For any PCI triple, call `compliance_matrix` rather than quoting a number from this file.
20
22
  >
21
23
  > **What EE 0.45.0 teaches, and it is a REFUSAL rule before it is a feature note.**
22
24
  > **⚠️ THE NTP CLOCK ATTESTATION IS WITHDRAWN. Do not describe it, offer it, or plan around it.**
@@ -163,13 +165,20 @@ order (discovery → service probes → OS detection → result fusion).
163
165
  | Parameter | Type | Required | Default | Description |
164
166
  |-----------|------|----------|---------|-------------|
165
167
  | `host` | string | ✅ | — | Target hostname or IP address |
166
- | `timeout` | number | ❌ | 30000 | Per-plugin timeout in ms |
167
168
 
168
- **Returns:** `{ summary, host, services[], findings[] }` — see `references/schemas.md`
169
+ Every plugin runs under the server's `PLUGIN_TIMEOUT_MS` (default 30000), including a plugin that
170
+ declares a longer budget of its own; there is no per-call timeout. A plugin that runs out of time
171
+ reads `timeout` in the result's `manifest` — its surface was NOT measured, which is never a clean
172
+ result.
173
+
174
+ **Returns:** `{ host, conclusion, manifest[], pluginsRan, markdown }` — `conclusion.result` is the fused
175
+ record (`summary` is a one-line string; `host`, `services[]`, `evidence[]`), and `manifest[]`
176
+ is every plugin's `{ id, name, status, reason, duration_ms }`. There is no `findings` array. See
177
+ `references/schemas.md`.
169
178
 
170
179
  **Example:**
171
180
  ```json
172
- { "host": "192.168.1.1", "timeout": 10000 }
181
+ { "host": "192.168.1.1" }
173
182
  ```
174
183
 
175
184
  **Important:**
@@ -192,6 +201,10 @@ Out of scope, with the control ids and the per-group out-of-scope reasons.
192
201
  matrix from the plugin inventory, from a scan result, or from documentation — coverage is a property of the shipped
193
202
  framework maps, not of the plugin list, and a derived matrix will disagree with the customer's own report.
194
203
 
204
+ ⚠️ **QUOTE the severity header the scan printed — never re-type it from the findings you read.** `scan_cloud` renders one line per provider, `N CRITICAL · N HIGH · N MEDIUM · N LOW · N INFO · N PASS`, and **the INFO column is load-bearing: it carries the evidence gaps and the deferred-scope boundaries.** Re-typing that line while summarising is how the column goes missing — measured 2026-09-18, a GCP reply reported `5 CRITICAL · 2 HIGH · 11 MEDIUM · 0 LOW · 11 PASS` over a header that read `… 0 LOW · 3 INFO · 11 PASS`. Every other number was right. **This is the exact regression the product itself fixed at EE 0.32.11**, when a header could read `…0 LOW · 17 PASS` over 62 unreported INFO records — reproduced one layer up, in the retelling. If you report tiers at all, report every tier the product printed, INFO included, even when it is zero.
205
+
206
+ ⚠️ **Never collapse a coverage triple into a single percentage or a "fully covers" figure.** State covered, partial and out of scope as three separate numbers with their ids; a percentage or a "fully" phrasing folds partial into covered and overstates coverage — a 19 / 9 / 44 matrix is 19 covered, not "about 39% fully."
207
+
195
208
  ⚠️ `outOfScope` is the **flattened sub-criterion count**, not the number of out-of-scope groups: SOC 2 returns 37
196
209
  ids across 11 groups. Publishing the group count yields a plausible-looking 10 / 4 / 11.
197
210
 
@@ -207,13 +220,15 @@ List all available scanner plugins with metadata.
207
220
  |-----------|------|----------|-------------|
208
221
  | *(none)* | — | — | — |
209
222
 
210
- **Returns:** Array of `{ id, name, description, priority, protocols[], ports[], requirements }`
223
+ **Returns:** Array of `{ id, name, priority, requirements }`
211
224
 
212
225
  **When to use:** Before a scan to understand available plugins, or to help the user select
213
226
  specific plugins for a targeted probe.
214
227
 
215
228
  ---
216
229
 
230
+ ### Pro Tools (Pro license required)
231
+
217
232
  #### `probe_service` *(Pro license required)*
218
233
  Run a single plugin against a specific host:port for deep-dive investigation.
219
234
 
@@ -236,9 +251,9 @@ Run a single plugin against a specific host:port for deep-dive investigation.
236
251
  | 010 | Webapp Detector | Technology stack fingerprinting (Wappalyzer) |
237
252
  | 011 | TLS Scanner | TLS versions, cipher suites, deprecation |
238
253
  | 014 | NetBIOS Scanner | SMB/NetBIOS enumeration, null sessions |
239
- | 040 | TLS Cert & Cipher Auditor | Certificate chain, expiry, weak ciphers *(Pro)* |
240
- | 050 | TRIBE v2 Probe | Debug leaks, stack traces, CORS misconfig *(Pro)* |
241
- | 060 | DNS Security Auditor | SPF/DKIM/DMARC, DNSSEC, zone transfer *(Pro)* |
254
+ | 040 | TLS Cert & Cipher Auditor | Certificate chain, expiry, weak ciphers |
255
+ | 050 | TRIBE v2 Probe | Debug leaks, stack traces, CORS misconfig |
256
+ | 060 | DNS Security Auditor | SPF/DKIM/DMARC, DNSSEC, zone transfer |
242
257
 
243
258
  ---
244
259
 
@@ -250,8 +265,9 @@ Look up known CVEs for a CPE string via the NVD 2.0 API.
250
265
  | `cpe` | string | ✅ | CPE 2.3 format (see CPE guide below) |
251
266
  | `maxResults` | number | ❌ | Max CVE results to return |
252
267
 
253
- **Returns:** `{ cpe, totalResults, vulnerabilities[] }` — each CVE includes ID, description,
254
- CVSS v3.1 score, severity, vector string, publication date.
268
+ **Returns:** `{ cpe, totalResults, cves[] }` — each CVE is `{ cveId, description, cvssScore, severity,
269
+ vectorString, published, lastModified }`; the CVSS fields are NVD's v3.1 metric, else v3.0, else `null`.
270
+ `totalResults` counts the CVEs returned, after `maxResults`.
255
271
 
256
272
  **CPE Construction Guide:**
257
273
 
@@ -274,21 +290,21 @@ Format: `cpe:2.3:a:<vendor>:<product>:<version>:*:*:*:*:*:*:*`
274
290
 
275
291
  ---
276
292
 
277
- ### Pro/Enterprise Tools (license gated)
293
+ ### Enterprise Tools (license gated)
278
294
 
279
295
  These tools return a license upgrade prompt on CE installations:
280
296
 
281
297
  | Tool | Tier | Purpose |
282
298
  |------|------|---------|
283
- | `scan_cloud` | Enterprise | Audit one or more cloud accounts (AWS / GCP / Azure) for security & compliance posture using the server-configured credentials. No network host needed. Input: `{ providers?: ("aws"\|"gcp"\|"azure")[], regions?: string[] }` — **pass only the cloud(s) the user names** (`providers:["aws"]` for "audit my AWS account"); omit `providers` only when the user asks to audit ALL clouds. Use this (not `scan_host`) when the user asks to "audit my AWS account", "audit my AWS and Azure accounts", or "check my cloud compliance". CE/Pro callers get an upgrade message. **`regions` (AWS only)** — AWS region codes (e.g. `["us-east-1","eu-west-1"]`) or `["all"]`. **Default — single region (MOST requests):** a plain "audit my AWS account", a "quick check", or any request that names no region AND does not explicitly ask for all/every/whole-account/complete/full coverage → **OMIT `regions`** (audits ONLY the server-configured `AWS_REGION`, one region; do NOT fan out or batch). Omitting does NOT scan all regions. **Specific regions:** when the user names region(s), pass exactly those. **All regions — ONLY on an explicit "all regions / every region / whole account / full coverage" request:** use the discover-then-batch approach in the region-scope note below — do NOT pass a single `["all"]` call and rely on it (it fans every regional plugin across all enabled regions and usually EXCEEDS the host's MCP tool-call timeout, e.g. Claude Desktop's, returning nothing). Unknown region codes are rejected before the scan runs (the WHOLE call fails — fix the region and re-call; never silently drop it). |
299
+ | `scan_cloud` | Enterprise | Audit one or more cloud accounts (AWS / GCP / Azure) for security & compliance posture using the server-configured credentials. No network host needed. Input: `{ providers?: ("aws"\|"gcp"\|"azure")[], regions?: string[] }` — **pass only the cloud(s) the user names** (`providers:["aws"]` for "audit my AWS account"); omit `providers` only when the user asks to audit ALL clouds. Use this (not `scan_host`) when the user asks to "audit my AWS account", "audit my AWS and Azure accounts", or "check my cloud compliance". CE/Pro callers get an upgrade message. **`regions` (AWS only)** — AWS region codes (e.g. `["us-east-1","eu-west-1"]`) or `["all"]`. **Default — omit `regions` (MOST requests):** a plain "audit my AWS account", a "quick check", or any request that names no region AND does not explicitly ask for all/every/whole-account/complete/full coverage → **OMIT `regions`** (the auditors that take their region from the client audit the server-configured `AWS_REGION` and no other; the three that enumerate their own region list — CloudTrail trail discovery (1040), GuardDuty/Inspector (1200), EC2 instances (1210) — still attempt every enabled region; do NOT fan out or batch). Omitting does NOT scan all regions with every auditor. **Specific regions:** when the user names region(s), pass exactly those. **All regions — ONLY on an explicit "all regions / every region / whole account / full coverage" request:** use the discover-then-batch approach in the region-scope note below — do NOT pass a single `["all"]` call and rely on it (it fans every regional plugin across all enabled regions and usually EXCEEDS the host's MCP tool-call timeout, e.g. Claude Desktop's, returning nothing). Unknown region codes are rejected before the scan runs (the WHOLE call fails — fix the region and re-call; never silently drop it). |
284
300
  | `get_findings` | Enterprise | Drill into the findings of the MOST RECENT `scan_cloud` scan — a per-provider, **per-session** cache (NOT live state; cleared when the MCP server restarts). Input: `{ scanId?, provider?, plugin?, severity?, category?, cursor?, limit? }`. Use it AFTER `scan_cloud` when the summary's **category rollup** shows a category you want to expand to specific resources, or when you need the FULL untruncated text of a finding. Pass the **`scanId` from the `scan_cloud` summary footer** + the `provider`; filter by `category`/`severity`/`plugin`; paginate with `cursor`/`limit` (server-capped at 20 — follow `nextCursor`). If you get a **"re-run scan_cloud"** error the cache was cleared or superseded — **re-run `scan_cloud`, do NOT retry `get_findings`**. CE/Pro callers get the same upgrade message as `scan_cloud`. |
285
301
 
286
- > **Interpreting `scan_cloud` results — never report a false clean:** read **`findingsSummary`** for the findings — it maps each provider to `counts` (per-severity totals) and a `findings` list of the CRITICAL/HIGH items (`{severity, plugin, title}`); report those. A cloud was effectively audited only if it appears in `auditedProviders`. If the result has `audited: false`, any `notes` entries, or `pluginsRan: 0`, the cloud was **NOT** audited (no plugins, missing credentials, or skipped) — report the gap explicitly; an empty result is **not** a clean pass. Do not infer "clean" from an empty `findingsSummary` when the cloud is not in `auditedProviders`. **Beyond CRITICAL/HIGH, `findingsSummary[provider].rollup` groups the remaining findings by `category` with counts (count-descending) across **MEDIUM, LOW *and* INFO** — and the INFO tier is not optional reading, because that is where the evidence gaps and the deferred-scope boundaries live. ⚠️ This sentence said "MEDIUM + LOW" until EE 0.39.0, which was FALSE against the summariser's own three rollup buckets, and false in the direction that hides the tier a reader most needs. Report every tier that occurs; these are actionable too: a category like `sqs-age-alarm-missing` or `*-public` is a real gap, not noise, and reporting only CRITICAL/HIGH while the rollup is non-empty is itself a false clean. To enumerate the specific resources behind a rollup category, or to read a finding's full untruncated text, call `get_findings` with the `scanId` from the summary footer + the `category`.**
302
+ > **Interpreting `scan_cloud` results — never report a false clean:** read **`findingsSummary`** for the findings — it maps each provider to `counts` (per-severity totals) and a `findings` list of the CRITICAL/HIGH items (`{severity, plugin, title}`); report those. A cloud was effectively audited only if it appears in `auditedProviders`. If the result has `audited: false`, any `notes` entries, or `pluginsRan: 0`, the cloud was **NOT** audited (no plugins, missing credentials, or skipped) — report the gap explicitly; an empty result is **not** a clean pass. Do not infer "clean" from an empty `findingsSummary` when the cloud is not in `auditedProviders`. **Beyond CRITICAL/HIGH, `findingsSummary[provider].rollup` groups the remaining findings by `category` with counts (count-descending) across **MEDIUM, LOW *and* INFO** — and the INFO tier is not optional reading: every deferred-scope boundary is INFO, and an evidence gap carries its own finding's severity, which can be any tier up to HIGH, so read every tier. ⚠️ This sentence said "MEDIUM + LOW" until EE 0.39.0, which was FALSE against the summariser's own three rollup buckets, and false in the direction that hides the tier a reader most needs. Report every tier that occurs; these are actionable too: a category like `sqs-age-alarm-missing` or `*-public` is a real gap, not noise, and reporting only CRITICAL/HIGH while the rollup is non-empty is itself a false clean. To enumerate the specific resources behind a rollup category, or to read a finding's full untruncated text, call `get_findings` with the `scanId` from the summary footer + the `category`.**
287
303
 
288
- > **Reporting `scan_cloud` region scope — never overstate coverage:** Report the regions you ACTUALLY scanned, derived from the `regions` you **passed** — NOT from the findings. If you OMITTED `regions`, only the single server-default region (`AWS_REGION`) was scanned — say exactly that and add that the account's OTHER enabled regions were NOT covered (offer to re-run for all regions). **Never escalate a single-region or "quick" request into a multi-region scan.** Do NOT claim "all regions" / "every region" / "across N regions" just because GuardDuty or Inspector list per-region findings: those plugins enumerate every enabled region INTERNALLY regardless of scope, so their per-region findings are NOT evidence the other plugins ran outside the region(s) you passed.
304
+ > **Reporting `scan_cloud` region scope — never overstate coverage:** Report the regions you ACTUALLY scanned, derived from the `regions` you **passed** — NOT from the findings. If you OMITTED `regions`, the auditors that take their region from the client scanned only the server-default region (`AWS_REGION`) — say that, and that the account's OTHER enabled regions were NOT covered by them (offer to re-run for all regions). The three auditors that enumerate their own region list — CloudTrail trail discovery (1040), GuardDuty/Inspector (1200), EC2 instances (1210) — attempt every enabled region when `regions` is omitted. Say one of them covered every region ONLY if it `ran` and carries no multi-region evidence gap (a single-region fallback, or errored or denied regions); any region such a gap names was NOT audited, say so. 1040's CloudWatch-alarm and AWS Config checks cover the configured region only. Never call the whole scan single-region when these three ran. **Never escalate a single-region or "quick" request into a multi-region scan.** Do NOT claim "all regions" / "every region" / "across N regions" for the scan as a whole because those three list per-region findings: their coverage is NOT evidence the other auditors ran outside the region(s) you passed. When you DO pass `regions`, their region-scoped checks follow the list you passed.
289
305
 
290
- > **Full all-region coverage — discover then batch** (use ONLY when the user explicitly asked for all/every/whole-account/complete/full region coverage; NEVER for a plain or "quick" request — those stay single-region per the `regions` default above): a single `regions:["all"]` call usually exceeds the host's MCP tool-call timeout (e.g. Claude Desktop's) and returns nothing. Reliable pattern: (1) run a default scan (omit `regions`) — its GuardDuty/Inspector findings enumerate every enabled region, giving you the full list while auditing the default region; (2) scan the REMAINING regions in small batches (3–5 region codes per `regions:[...]` call) across successive calls until every enabled region is covered; (3) merge and report the TOTAL number of regions actually covered — **count** them, don't guess. If you try `["all"]` and it times out, that result is INCOMPLETE — fall back to batching and continue until complete; never report a timed-out or partial scan as full coverage.
291
- > **⚠️ COMPLIANCE IS A CLI SURFACE, NOT AN MCP TOOL.** `compliance_check` is WITHDRAWN — there is no such tool and there never was, and the evidence pack is produced by `nsauditor-ai scan --host <target> --compliance <fw> --out <dir>`, which runs the compliance PHASE and writes `scan_compliance_<framework>.{json,md,html}` plus its attestation and chain-of-custody sidecars. `scan_cloud` maps findings to frameworks but **never runs that phase**, so no MCP call produces a pack. Use `compliance_matrix` to state COVERAGE (what is covered / partial / out of scope); use the CLI to produce EVIDENCE.
306
+ > **Full all-region coverage — discover then batch** (use ONLY when the user explicitly asked for all/every/whole-account/complete/full region coverage; NEVER for a plain or "quick" request — those stay on the `regions` default above, with no explicit fan-out): a single `regions:["all"]` call usually exceeds the host's MCP tool-call timeout (e.g. Claude Desktop's) and returns nothing. Reliable pattern: (1) run a default scan (omit `regions`) — its GuardDuty/Inspector findings enumerate every enabled region, giving you the full list while the region-scoped auditors cover the default region; (2) scan the REMAINING regions in small batches (3–5 region codes per `regions:[...]` call) across successive calls until every enabled region is covered; (3) merge and report the TOTAL number of regions actually covered — **count** them, don't guess. If you try `["all"]` and it times out, that result is INCOMPLETE — fall back to batching and continue until complete; never report a timed-out or partial scan as full coverage.
307
+ > **⚠️ COMPLIANCE IS A CLI SURFACE, NOT AN MCP TOOL.** `compliance_check` is WITHDRAWN — there is no such tool and there never was, and the evidence pack is produced by `nsauditor-ai scan --host <target> --compliance <fw> --out <dir>`, which runs the compliance PHASE and writes `scan_compliance_<framework>.{json,md,html}` plus its attestation and chain-of-custody sidecars. `scan_cloud` **never runs that phase** and maps no finding to any framework, so no MCP call produces a pack. Use `compliance_matrix` to state COVERAGE (what is covered / partial / out of scope); use the CLI to produce EVIDENCE.
292
308
  >
293
309
  > The framework detail below is accurate and worth keeping — it was attached to a tool name that does not exist:
294
310
  >
@@ -399,16 +415,16 @@ Phase 3: INTELLIGENCE (Pro) CPE generation → NVD CVE lookup → Parallel ve
399
415
  • Service Agent (CVE-specific targeted probes)
400
416
  Output: Structured finding queue
401
417
  ↓
402
- Phase 4: VERIFICATION PLANNED — NOT SHIPPED. The finding-status field and its
403
- (planned, not shipped) risk-weighting scaffolding exist; the active safe probes
404
- do not. Every finding today is emitted UNVERIFIED.
418
+ Phase 4: VERIFICATION WITHDRAWN — not shipped and not planned. The finding-status
419
+ (withdrawn) field exists; no active probe sets it. Every finding is
420
+ emitted UNVERIFIED.
405
421
  ↓
406
422
  Phase 5: SCORING (Pro/Ent) Risk scoring → Pro AI prompts → Compliance mapping
407
423
  Output: Risk report + compliance report (Markdown, HTML, JSON)
408
424
  ```
409
425
 
410
- > **Phase 4 is a roadmap entry, kept in the diagram so the pipeline's shape is legible.** The
411
- > Verification Engine was withdrawn as a capability claim at EE 0.32.7. Do not describe
426
+ > **Phase 4 is WITHDRAWN and on no roadmap; it stays in the diagram only so the pipeline's shape is
427
+ > legible.** The Verification Engine was withdrawn as a capability claim at EE 0.32.7 and is not planned. Do not describe
412
428
  > findings as probe-confirmed (it is WITHDRAWN, not merely unused), and do not tell an
413
429
  > operator a finding was "verified" — see
414
430
  > `references/schemas.md` § Finding Statuses.
@@ -422,8 +438,9 @@ shipped plugin files; verify any of them with `nsauditor-ai license --plugins`,
422
438
  live total and marks each Enterprise plugin `✓ active` or `✗ requires: <tier>`.
423
439
 
424
440
  The Community set groups roughly as service probes, host/network discovery, and intelligence /
425
- meta plugins; three further plugins are Pro-gated (040 TLS Certificate & Cipher Auditor, 050
426
- TRIBE v2 Probe, 060 DNS Security Auditor).
441
+ meta plugins, plus three deep-audit Community plugins (040 TLS Certificate & Cipher Auditor, 050
442
+ TRIBE v2 Probe, 060 DNS Security Auditor): `scan_host` runs them on every tier, and only the
443
+ single-plugin route to them, `probe_service`, needs Pro.
427
444
 
428
445
  > A per-plugin list used to be duplicated here and drifted: it claimed **18** Enterprise plugins
429
446
  > while enumerating **15**, against **28** on disk. One catalog, in `references/plugins.md`.
@@ -448,9 +465,9 @@ User wants to...
448
465
  ├── Check a specific service/port → probe_service (Pro)
449
466
  ├── Look up CVEs for software version → get_vulnerabilities (Pro)
450
467
  ├── See available plugins → list_plugins
451
- ├── Audit TLS certificates → probe_service with plugin 040 (Pro)
452
- ├── Check DNS security (SPF/DKIM/DMARC) → probe_service with plugin 060 (Pro)
453
- ├── Detect debug leaks / CORS issues → probe_service with plugin 050 (Pro)
468
+ ├── Audit TLS certificates → scan_host, or probe_service (Pro) with plugin 040
469
+ ├── Check DNS security (SPF/DKIM/DMARC) → scan_host, or probe_service (Pro) with plugin 060
470
+ ├── Detect debug leaks / CORS issues → scan_host, or probe_service (Pro) with plugin 050
454
471
  ├── Scan a subnet → CLI with --parallel (not MCP)
455
472
  ├── Set up continuous monitoring → CLI with --watch (not MCP)
456
473
  ├── State framework COVERAGE → compliance_matrix (any tier)
@@ -463,10 +480,10 @@ User wants to...
463
480
 
464
481
  See `references/schemas.md` for complete structures:
465
482
 
466
- - **Scan Result** — `{ summary, host{os,mac,vendor,names}, services[], findings[] }`
483
+ - **Scan Result** (`scan_host`) — `{ host, conclusion{ result{ summary, host, services[], evidence[] } }, manifest[], pluginsRan, markdown }`
467
484
  - **ServiceRecord** — `{ port, protocol, service, program, version, status, banner, evidence[] }`
468
- - **Finding** — `{ id, category, severity, title, evidence, remediation, cwe, mitre_attack[] }`
469
- - **CVE Response** — `{ cpe, totalResults, vulnerabilities[]{cve_id, cvss, severity} }`
485
+ - **Finding** (Pro/Enterprise queue) — `{ id, category, status, title, severity, cvss, target, evidence{ source, cve[], mitre[], raw }, remediation{ summary, effort, references[] }, riskScore }`
486
+ - **CVE Response** (`get_vulnerabilities`) — `{ cpe, totalResults, cves[]{ cveId, cvssScore, severity, vectorString } }`
470
487
  - **Plugin Interface** — `{ id, name, priority, run(), conclude(), requirements }`
471
488
  - **SARIF Output** — 2.1.0 format for CI/CD consumers
472
489
 
@@ -498,7 +515,7 @@ See `references/schemas.md` for complete structures:
498
515
 
499
516
  5. **Non-Destructive:** every scanner probe is a read-only query — NSAuditor AI never
500
517
  exploits vulnerabilities or modifies target systems. (Active *verification* probes are
501
- Planned — not shipped; findings are emitted UNVERIFIED.)
518
+ WITHDRAWN — not shipped and not planned; findings are emitted UNVERIFIED.)
502
519
 
503
520
  ---
504
521
 
@@ -509,7 +526,8 @@ See `references/schemas.md` for complete structures:
509
526
  | Variable | Default | Purpose |
510
527
  |----------|---------|---------|
511
528
  | `NSA_ALLOW_ALL_HOSTS` | unset | Set to `1` to scan RFC 1918 private ranges |
512
- | `PLUGIN_TIMEOUT_MS` | 30000 | Global per-plugin timeout |
529
+ | `PLUGIN_TIMEOUT_MS` | 30000 | Per-plugin budget. `scan_host` and `probe_service` bind every plugin to it; on the CLI a plugin that declares its own budget outranks it |
530
+ | `PLUGIN_TIMEOUT_CEILING_MS` | 120000 | Upper bound on any plugin's declared budget — the setting that caps every plugin on the CLI |
513
531
  | `AI_ENABLED` | false | Enable AI analysis |
514
532
  | `AI_PROVIDER` | openai | `openai` · `claude` · `ollama` |
515
533
  | `OPENAI_API_KEY` | — | OpenAI API key (or `keychain:OPENAI_API_KEY`) |
@@ -573,6 +591,10 @@ claude mcp add nsauditor-ai -- npx nsauditor-ai-mcp
573
591
  }
574
592
  ```
575
593
 
594
+ `PLUGIN_TIMEOUT_MS` bounds each plugin `scan_host` runs, not the call: the plugins run one after
595
+ another, so the call takes roughly the sum of their times and fits inside Claude Desktop's ~60 s
596
+ tool-call limit only when most of them finish quickly.
597
+
576
598
  > ⚠️ **`NSA_MCP_AUTH_KEY` is REQUIRED — the server refuses to start without it.** Generate one with
577
599
  > `nsauditor-ai mcp install-key`, then put the SAME value in the `env` block above. Without it the MCP
578
600
  > server exits at startup and the client shows the tools as unavailable. (`NSA_MCP_AUTH_DISABLE=1`
@@ -588,7 +610,7 @@ Add to your MCP configuration with the same command/args pattern.
588
610
  | Edition | Price | Key Features |
589
611
  |---------|-------|-------------|
590
612
  | **Community** | Free / MIT | 27 plugins (service probes + host/network discovery + intelligence/meta), basic AI, CTEM, SARIF, scan history |
591
- | **Pro** | $49/mo | + CVE matching, risk scoring, analysis agents, Pro plugins (040 TLS / 050 TRIBE / 060 DNS) |
613
+ | **Pro** | $49/mo | + CVE matching, risk scoring, analysis agents, the single-plugin and CVE-lookup MCP tools (`probe_service`, `get_vulnerabilities`) |
592
614
  | **Enterprise** | $2k+/yr | + 29 enterprise plugins (1020-1230 range) — 28 cloud-substrate auditors covering AWS / GCP / Azure plus `1023 Zero Trust Assessment` (which declares no cloud provider and scores zero-trust posture from a NETWORK-host scan — it never runs on a cloud pass, and selecting it by id on its own does not run it either) — against every shipped framework (SOC 2 10 covered + 4 partial; HIPAA; NIST CSF 2.0; PCI DSS v4.0.1; ISO/IEC 27001:2022; CIS Controls v8; GDPR Art. 32 infrastructure substrate; NIST SP 800-171 Rev 2 evidence substrate for CMMC Level 2 preparation); SOC 2 evidence-pack generation; SHA-256 chain-of-custody attestations (RFC 3161 timestamping is opt-in via the `NSAUDITOR_TSA_URL` environment variable — there is no CLI flag and no default, it makes an outbound call to the Time-Stamp Authority you name, and it was exercised against a live Time-Stamp Authority on BOTH delivery vehicles — the npm path and, from inside the pushed `:0.33.0` Marketplace image, on 2026-08-08; retained images `:0.32.11` and earlier carry no `openssl`); air-gapped operation (offline licensing + offline CVE matching under `NSAUDITOR_OFFLINE_ONLY=1`) |
593
615
 
594
616
  → [Pricing](https://www.nsauditor.com/ai/pricing/)
@@ -601,7 +623,7 @@ Add to your MCP configuration with the same command/args pattern.
601
623
  |-------|-------|-----------|
602
624
  | SSRF block | Target is loopback/metadata/private | Set `NSA_ALLOW_ALL_HOSTS=1` for local scanning |
603
625
  | License gate (`🔒`) | Pro/Enterprise tool on CE | Upgrade license or use CE alternative |
604
- | Plugin timeout | Network unreachable / slow target | Increase `timeout` param or `PLUGIN_TIMEOUT_MS` |
626
+ | Plugin timeout (`timeout` in `manifest`) | Network unreachable / slow target | Not measured, never clean. Raise `PLUGIN_TIMEOUT_MS` in the server env, or scan from the CLI, where a plugin that declares its own budget gets it |
605
627
  | No DNS banner | Provider blocks CHAOS/TXT queries | Expected; not all DNS servers expose version |
606
628
  | CPE format error | Malformed CPE string | Use `cpe:2.3:a:vendor:product:version:*:*:*:*:*:*:*` |
607
629
  | No services found | Host down or heavily firewalled | Try `NSA_VERBOSE=true` to debug; check connectivity |
@@ -611,7 +633,7 @@ Add to your MCP configuration with the same command/args pattern.
611
633
 
612
634
  ## MITRE ATT&CK Mapping
613
635
 
614
- Findings are auto-tagged with MITRE techniques:
636
+ The CLI's report tags what it finds with MITRE techniques (the `scan_host` tool does not return them):
615
637
 
616
638
  | Finding Type | Technique | ID |
617
639
  |-------------|-----------|-----|
@@ -0,0 +1,142 @@
1
+ #!/usr/bin/env node
2
+ // The delivery CLI for the NSAuditor AI agent skill.
3
+ //
4
+ // ⚠️ WHY THIS EXISTS: `scripts/build_desktop_skill_zip.mjs` already worked and already took
5
+ // `--out`. It was never DELIVERED — `files[]` excluded `scripts/` and there was no `bin` — so the
6
+ // maintainer used it from a checkout every release while it reached zero users. A capability that
7
+ // ships to nobody is indistinguishable from one that does not exist.
8
+ //
9
+ // ⛔ AN EXPLICIT COMMAND, NEVER A `postinstall`. Writing into $HOME from a postinstall is skipped
10
+ // under `--ignore-scripts`, breaks in CI and sandboxes, orphans files on uninstall, and is a
11
+ // supply-chain shape reviewers distrust. The user asks for the install; the install does not
12
+ // happen to them.
13
+ //
14
+ // ⚠️ THE TWO SURFACES ARE NOT THE SAME AND THE DIFFERENCE IS STRUCTURAL, not an oversight in our
15
+ // packaging: Claude Code loads skills from the FILESYSTEM (`~/.claude/skills/`), so `install` is a
16
+ // copy; Claude Desktop accepts skills ONLY as an uploaded zip through Settings, so `build-zip`
17
+ // makes the artifact and a human uploads it. Skills do not sync between surfaces.
18
+ import fs from 'node:fs';
19
+ import os from 'node:os';
20
+ import path from 'node:path';
21
+ import { spawnSync } from 'node:child_process';
22
+ import { fileURLToPath } from 'node:url';
23
+
24
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
25
+ const SKILL_NAME = 'nsauditor-ai';
26
+
27
+ const USAGE = `nsauditor-ai-agent-skill — deliver the NSAuditor AI skill to a Claude surface
28
+
29
+ nsauditor-ai-agent-skill install [--dest <dir>]
30
+ Copy the skill into Claude Code's skills directory (default ~/.claude/skills).
31
+ Claude Code loads skills from the filesystem; restart is not required.
32
+
33
+ nsauditor-ai-agent-skill build-zip [--out <dir|file>]
34
+ Build the Claude Desktop upload zip. Desktop accepts skills ONLY as a zip
35
+ uploaded through Settings > Features — there is no directory it watches.
36
+
37
+ Skills do NOT sync between Claude Code, Claude Desktop and the API.`;
38
+
39
+ const flag = (argv, name) => {
40
+ const i = argv.indexOf(name);
41
+ if (i === -1) return undefined;
42
+ const v = argv[i + 1];
43
+ // A value-less flag is NOT the same as an absent one: absent means "use the default", while a
44
+ // swallowed value is an operator mistake that must not silently produce the default anyway.
45
+ return v === undefined || v.startsWith('--') ? true : v;
46
+ };
47
+
48
+ // DERIVED from disk, exactly as the zip builder derives its members. A hand list here would pass
49
+ // its own test while a real install silently dropped a reference file — and a skill that loads
50
+ // without its references degrades quietly rather than failing.
51
+ function members() {
52
+ const refs = fs.readdirSync(path.join(ROOT, 'references'))
53
+ .filter((f) => f.endsWith('.md')).map((f) => `references/${f}`);
54
+ return ['SKILL.md', ...refs];
55
+ }
56
+
57
+ function install(argv) {
58
+ const destFlag = flag(argv, '--dest');
59
+ if (destFlag === true) {
60
+ console.error('install: --dest needs a directory. A flag that quietly does nothing is how an '
61
+ + 'operator concludes an install succeeded.');
62
+ return 2;
63
+ }
64
+ const dest = destFlag ?? path.join(os.homedir(), '.claude', 'skills');
65
+ const target = path.join(dest, SKILL_NAME);
66
+
67
+ const list = members();
68
+ const missing = list.filter((m) => !fs.existsSync(path.join(ROOT, m)));
69
+ if (missing.length) {
70
+ // Refuse rather than copy what is present: a partial skill loads and is silently poorer.
71
+ console.error(`install: refusing — these members are missing from the package: ${missing.join(', ')}`);
72
+ return 1;
73
+ }
74
+
75
+ try {
76
+ for (const m of list) {
77
+ const to = path.join(target, m);
78
+ fs.mkdirSync(path.dirname(to), { recursive: true });
79
+ fs.copyFileSync(path.join(ROOT, m), to);
80
+ }
81
+ } catch (e) {
82
+ console.error(`install: ${e?.message || e}`);
83
+ return 1;
84
+ }
85
+ console.log(`Installed ${list.length} file(s) to ${target}`);
86
+ console.log('Claude Code will pick it up from the filesystem. This does NOT reach Claude Desktop —');
87
+ console.log('for Desktop run `build-zip` and upload the zip in Settings > Features.');
88
+ return 0;
89
+ }
90
+
91
+ function buildZip(argv) {
92
+ const script = path.join(ROOT, 'scripts', 'build_desktop_skill_zip.mjs');
93
+ // ⚠️ THE BUILDER'S `--out` IS A FILE PATH; THE USEFUL UX IS A DIRECTORY (`--out ~/Desktop`).
94
+ // Handing it a directory throws EISDIR from inside the zip write — a stack trace where the user
95
+ // expected a file. Resolved HERE rather than in the builder, because the builder's contract is
96
+ // guarded (derived members, REQUIRED floor, byte floor, wrapper-folder assertion) and this is a
97
+ // UX concern, not a packaging one. A value-less `--out` is refused for the same reason `install`
98
+ // refuses a value-less `--dest`.
99
+ const out = flag(argv, '--out');
100
+ if (out === true) {
101
+ console.error('build-zip: --out needs a path.');
102
+ return 2;
103
+ }
104
+ let argvOut = argv;
105
+ if (typeof out === 'string') {
106
+ let resolved = out;
107
+ try {
108
+ if (fs.existsSync(out) && fs.statSync(out).isDirectory()) {
109
+ const version = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf8')).version;
110
+ resolved = path.join(out, `nsauditor-ai-skill-${version}.zip`);
111
+ }
112
+ } catch { /* fall through with the literal value; the builder reports its own failure */ }
113
+ argvOut = argv.map((a, i) => (i === argv.indexOf('--out') + 1 ? resolved : a));
114
+ }
115
+ if (!fs.existsSync(script)) {
116
+ console.error(`build-zip: the builder is not in this package (${script}). This is the packaging `
117
+ + 'defect this CLI exists to close — report it rather than working around it.');
118
+ return 1;
119
+ }
120
+ // SPAWNED, not imported: the builder is a top-level script whose exit codes are its contract
121
+ // (1 = a declared member missing or coverage below the floor). Importing it would swallow them.
122
+ const r = spawnSync(process.execPath, [script, ...argvOut], { stdio: 'inherit' });
123
+ return r.status ?? 1;
124
+ }
125
+
126
+ const [verb, ...rest] = process.argv.slice(2);
127
+ let code;
128
+ switch (verb) {
129
+ case 'install': code = install(rest); break;
130
+ case 'build-zip': code = buildZip(rest); break;
131
+ case '--help': case '-h': case 'help': console.log(USAGE); code = 0; break;
132
+ case undefined:
133
+ console.error('nsauditor-ai-agent-skill: a verb is required.\n');
134
+ console.error(USAGE);
135
+ code = 2; break;
136
+ default:
137
+ // Name what was typed. A refusal that does not quote the input reads as a tool failure.
138
+ console.error(`nsauditor-ai-agent-skill: unknown verb \`${verb}\`.\n`);
139
+ console.error(USAGE);
140
+ code = 2;
141
+ }
142
+ process.exit(code);
@@ -131,12 +131,12 @@ Run:
131
131
  This rescans every 15 minutes, detects changes (new/removed services, version
132
132
  changes, new findings), and sends webhook alerts for high+ severity changes.
133
133
 
134
- Scan history is stored in .scan_history/ as JSONL for trend analysis."
134
+ Each scan appends one line per host to scan_history.jsonl in the output directory (--out, default out/)."
135
135
  ```
136
136
 
137
137
  ---
138
138
 
139
- ## Example 6: "Audit DNS security for example.com" (Pro)
139
+ ## Example 6: "Audit DNS security for example.com" — `probe_service` (Pro); `scan_host` runs 060 on every tier
140
140
 
141
141
  **Agent reasoning:** Use the DNS Security Auditor plugin for comprehensive DNS assessment.
142
142
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nsauditor-ai-agent-skill",
3
- "version": "0.2.51",
3
+ "version": "0.2.53",
4
4
  "description": "AI Agent Skill for NSAuditor AI — gives any AI coding agent built-in knowledge of NSAuditor's MCP tools, schemas, plugins, and security audit workflows.",
5
5
  "keywords": [
6
6
  "nsauditor",
@@ -42,13 +42,15 @@
42
42
  "files": [
43
43
  "SKILL.md",
44
44
  "README.md",
45
- "CHANGELOG.md",
46
45
  "LICENSE",
47
46
  "references/",
48
- "examples/"
47
+ "examples/",
48
+ "bin/",
49
+ "scripts/",
50
+ ".claude-plugin/"
49
51
  ],
50
52
  "peerDependencies": {
51
- "nsauditor-ai": ">=0.2.49"
53
+ "nsauditor-ai": ">=0.2.55"
52
54
  },
53
55
  "peerDependenciesMeta": {
54
56
  "nsauditor-ai": {
@@ -56,6 +58,10 @@
56
58
  }
57
59
  },
58
60
  "scripts": {
59
- "build:desktop-zip": "node scripts/build_desktop_skill_zip.mjs"
61
+ "build:desktop-zip": "node scripts/build_desktop_skill_zip.mjs",
62
+ "test": "node --test"
63
+ },
64
+ "bin": {
65
+ "nsauditor-ai-agent-skill": "bin/nsauditor-ai-agent-skill.mjs"
60
66
  }
61
67
  }
@@ -229,7 +229,7 @@ listings, and default pages.
229
229
  | 1080 | AWS Lambda Security Auditor | Enterprise | Runtime EOL detection (case-normalized); public function-URL exposure; resource-policy permissive principals; env-var secret-suggestive name detection (ZDE-safe — names only, never values); VPC config; KMS-CMK; DLQ + reserved concurrency | CC6.1 / CC6.6 / CC7.1 / C1.1 |
230
230
  | 1090 | AWS Secrets Manager + SSM Parameter Store Auditor | Enterprise | ListSecrets + DescribeSecret (rotation cadence, KMS-CMK, prod-tier tagging) + SSM DescribeParameters (String/SecureString + secret-suggestive name detection). ZDE-critical: NEVER calls GetSecretValue / GetParameter — only metadata APIs | CC6.1 / CC6.6 / C1.1 |
231
231
  | 1100 | AWS CodePipeline + CodeBuild Operational Integrity | Enterprise | Pipeline source-stage encryption; CodeBuild `privilegedMode` detection; buildspec inlined-vs-S3 drift; secrets via env vars vs Secrets Manager; IAM role wildcard-Action; S3 artifact-store encryption; stale-execution detection | CC6.1 / CC7.1 / CC8.1 / C1.1 |
232
- | 1110 | AWS IAM Effective Decrypt-Path Auditor | Enterprise | Cross-plugin reconciler: walks IAM policies for kms:Decrypt / kms:ReEncrypt / kms:GenerateDataKey grants and cross-references against destination KMS key policies (plugin 1070) to compute effective decrypt path; closes NotAction-implicit-decrypt false-PASS class | CC6.1 / CC6.6 / C1.1 / C1.2 |
232
+ | 1110 | AWS IAM Effective Decrypt-Path Auditor | Enterprise | Cross-plugin reconciler: walks IAM policies for kms:Decrypt / kms:ReEncrypt / kms:GenerateDataKey grants and cross-references against destination KMS key policies (plugin 1070) to compute effective decrypt path; closes NotAction-implicit-decrypt false-PASS class. KMS key policies and grants are read in the configured region only, so since EE 1.1.0 the Resource:* HIGH is never downgraded on a live run — `summary.kmsDowngrade` records why | CC6.1 / CC6.6 / C1.1 / C1.2 |
233
233
  | 1120 | AWS S3 Lifecycle + Cross-Region Replication Auditor | Enterprise | S3 lifecycle policy enumeration (CC7.1 retention-cadence) + cross-region replication topology (A1.2 DR substrate); destination-bucket reachability verification closes silent-PASS class where replication source FAILED but emitted clean | C1.1 / C1.2 / A1.2 |
234
234
  | 1130 | AWS Backup Auditor — headline thread | Enterprise | The largest single-plugin institutional-hardening arc in the EE codebase (~7800 lines, 545 tests). Audits Plans + Vaults + Recovery Points + Selections + Frameworks + Restore Testing + ReportPlans + Legal Holds + VaultType + Vault Tags + Vault Access Policy. **12-dimension air-gapped vault attestation arc** for LogicallyAirGappedBackupVault: 6 cryptographic-isolation mechanisms (vault TYPE + ARN account-segment-separation + destination KMS key-policy clean + destination KMS Grants clean + MRK-replica topology clean + source-account VPC-endpoint policy clean) + 6 substrate dimensions (PITR/retention/encryption/RestoreTesting/Legal Holds/vault Access Policy) | CC6.3 / CC6.6 / CC7.1 / CC8.1 / C1.1 / C1.2 / A1.2 |
235
235
  | 1140 | AWS RDS Auditor (v3 — extended in EE 0.4.8) | Enterprise | **10 SOC 2 substrate-evidence dimensions** (v1=3 + v2=4 + v3=3). **v1+v2 (preserved):** Multi-AZ (A1.2) + storage encryption at rest with KMS-key custody classification + **kms:DescribeKey cross-reference promotes UNVERIFIABLE `:key/UUID` ARN shapes to deterministic PASS/MEDIUM** + parameter-group SSL enforcement (postgres rds.force_ssl + mysql require_secure_transport) + BackupRetentionPeriod (7-day baseline) + PubliclyAccessible + IAMDatabaseAuthenticationEnabled + snapshot encryption. **v3 NEW (database audit-logging, EE 0.4.8, v3):dim 8 pgAudit enabled** (postgres-only — `DescribeDBParameters → pgaudit.log` non-empty AND `shared_preload_libraries` contains `pgaudit` token per reviewer fold **false-PASS closure** — Postgres silently ignores the GUC when SPL omits pgaudit; new MEDIUM `rds-pgaudit-misconfigured` category; non-postgres engines = INFO + engine-not-applicable) + **dim 9 CloudWatch Logs exports** (`EnabledCloudwatchLogsExports` engine-dispatched: postgres essential=`postgresql`; mysql/mariadb essential=`error`; oracle essential=`audit`+`trace`; sqlserver essential=`error`; empty=HIGH / partial=MEDIUM / complete=PASS) + **dim 10 CloudWatch Logs retention** (`logs:DescribeLogGroups` enumeration on engine-dispatched prefix per reviewer fold **false-INFO closure**: `/aws/rds/instance/<id>/` for non-Aurora, `/aws/rds/cluster/<DBClusterIdentifier>/` for `aurora-*` engines — pre-fold hard-coded the instance path → 0 log groups on every Aurora node = false-INFO MEDIUM across the whole Aurora fleet; 30-day institutional baseline operator-tunable via `opts.auditLogRetentionPassMinDays` clamped 1..3653). **9 same-session v3 reviewer folds** (HIGH-1 Aurora cluster log-path; MEDIUM-2 pgAudit-SPL cross-check; MEDIUM-3/4/5 cwl-opt-out + retentionDistribution + transient-error distinct categories; LOW-8/9/10 + NIT-12). **Real-AWS smoke END-TO-END against `<operator-test-account>`** (in-place modification of rds-compliant-cluster; cost $0): ALL 3 v3 PASS-path classifiers validated + unmodified rds-violator-db validates HIGH path. **First 0.4.x extension cycle to validate BOTH PASS-path AND HIGH-path classifiers** against real AWS in the same smoke run. | A1.2 / CC6.1 / CC6.6 / C1.1 / CC7.2 / CC7.3 |