@skillsmith/core 0.11.6 → 0.12.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.
Files changed (211) hide show
  1. package/CHANGELOG.md +208 -0
  2. package/README.md +2 -2
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/src/api/client.types.d.ts +9 -0
  5. package/dist/src/api/client.types.d.ts.map +1 -1
  6. package/dist/src/api/schemas.d.ts +6 -0
  7. package/dist/src/api/schemas.d.ts.map +1 -1
  8. package/dist/src/api/schemas.js +5 -0
  9. package/dist/src/api/schemas.js.map +1 -1
  10. package/dist/src/api/security-summary.d.ts +10 -1
  11. package/dist/src/api/security-summary.d.ts.map +1 -1
  12. package/dist/src/api/security-summary.js +9 -0
  13. package/dist/src/api/security-summary.js.map +1 -1
  14. package/dist/src/api/security-summary.test.js +79 -1
  15. package/dist/src/api/security-summary.test.js.map +1 -1
  16. package/dist/src/api/types.d.ts +9 -0
  17. package/dist/src/api/types.d.ts.map +1 -1
  18. package/dist/src/exports/services.d.ts +2 -2
  19. package/dist/src/exports/services.d.ts.map +1 -1
  20. package/dist/src/exports/services.js +4 -2
  21. package/dist/src/exports/services.js.map +1 -1
  22. package/dist/src/index.d.ts +3 -2
  23. package/dist/src/index.d.ts.map +1 -1
  24. package/dist/src/index.js +8 -2
  25. package/dist/src/index.js.map +1 -1
  26. package/dist/src/install/agent-config-merge.json-array.d.ts +16 -0
  27. package/dist/src/install/agent-config-merge.json-array.d.ts.map +1 -1
  28. package/dist/src/install/agent-config-merge.json-array.js +32 -3
  29. package/dist/src/install/agent-config-merge.json-array.js.map +1 -1
  30. package/dist/src/install/agent-harness-targets.d.ts +5 -0
  31. package/dist/src/install/agent-harness-targets.d.ts.map +1 -1
  32. package/dist/src/install/agent-harness-targets.js +19 -4
  33. package/dist/src/install/agent-harness-targets.js.map +1 -1
  34. package/dist/src/install/agent-pack-installer.cursor-hooks.d.ts +29 -0
  35. package/dist/src/install/agent-pack-installer.cursor-hooks.d.ts.map +1 -0
  36. package/dist/src/install/agent-pack-installer.cursor-hooks.js +233 -0
  37. package/dist/src/install/agent-pack-installer.cursor-hooks.js.map +1 -0
  38. package/dist/src/install/agent-pack-installer.cursor-hooks.test.d.ts +2 -0
  39. package/dist/src/install/agent-pack-installer.cursor-hooks.test.d.ts.map +1 -0
  40. package/dist/src/install/agent-pack-installer.cursor-hooks.test.js +225 -0
  41. package/dist/src/install/agent-pack-installer.cursor-hooks.test.js.map +1 -0
  42. package/dist/src/install/agent-pack-installer.d.ts +10 -5
  43. package/dist/src/install/agent-pack-installer.d.ts.map +1 -1
  44. package/dist/src/install/agent-pack-installer.harness.d.ts +10 -2
  45. package/dist/src/install/agent-pack-installer.harness.d.ts.map +1 -1
  46. package/dist/src/install/agent-pack-installer.harness.js +9 -2
  47. package/dist/src/install/agent-pack-installer.harness.js.map +1 -1
  48. package/dist/src/install/agent-pack-installer.js +15 -6
  49. package/dist/src/install/agent-pack-installer.js.map +1 -1
  50. package/dist/src/install/agent-pack-installer.test.js +3 -0
  51. package/dist/src/install/agent-pack-installer.test.js.map +1 -1
  52. package/dist/src/scripts/__tests__/scan-imported-skills.test.js +25 -0
  53. package/dist/src/scripts/__tests__/scan-imported-skills.test.js.map +1 -1
  54. package/dist/src/scripts/skill-scanner/trust-scorer.js +1 -1
  55. package/dist/src/scripts/skill-scanner/trust-scorer.js.map +1 -1
  56. package/dist/src/security/scanner/SecurityScanner.archive.d.ts +34 -0
  57. package/dist/src/security/scanner/SecurityScanner.archive.d.ts.map +1 -0
  58. package/dist/src/security/scanner/SecurityScanner.archive.js +218 -0
  59. package/dist/src/security/scanner/SecurityScanner.archive.js.map +1 -0
  60. package/dist/src/security/scanner/SecurityScanner.compound.d.ts +1 -0
  61. package/dist/src/security/scanner/SecurityScanner.compound.d.ts.map +1 -1
  62. package/dist/src/security/scanner/SecurityScanner.compound.js +117 -38
  63. package/dist/src/security/scanner/SecurityScanner.compound.js.map +1 -1
  64. package/dist/src/security/scanner/SecurityScanner.d.ts +41 -4
  65. package/dist/src/security/scanner/SecurityScanner.d.ts.map +1 -1
  66. package/dist/src/security/scanner/SecurityScanner.decoy.d.ts +72 -0
  67. package/dist/src/security/scanner/SecurityScanner.decoy.d.ts.map +1 -0
  68. package/dist/src/security/scanner/SecurityScanner.decoy.js +265 -0
  69. package/dist/src/security/scanner/SecurityScanner.decoy.js.map +1 -0
  70. package/dist/src/security/scanner/SecurityScanner.encoding.d.ts +56 -0
  71. package/dist/src/security/scanner/SecurityScanner.encoding.d.ts.map +1 -0
  72. package/dist/src/security/scanner/SecurityScanner.encoding.js +270 -0
  73. package/dist/src/security/scanner/SecurityScanner.encoding.js.map +1 -0
  74. package/dist/src/security/scanner/SecurityScanner.exec.d.ts +23 -20
  75. package/dist/src/security/scanner/SecurityScanner.exec.d.ts.map +1 -1
  76. package/dist/src/security/scanner/SecurityScanner.exec.js +207 -52
  77. package/dist/src/security/scanner/SecurityScanner.exec.js.map +1 -1
  78. package/dist/src/security/scanner/SecurityScanner.fetch-correlation.d.ts +115 -0
  79. package/dist/src/security/scanner/SecurityScanner.fetch-correlation.d.ts.map +1 -0
  80. package/dist/src/security/scanner/SecurityScanner.fetch-correlation.js +181 -0
  81. package/dist/src/security/scanner/SecurityScanner.fetch-correlation.js.map +1 -0
  82. package/dist/src/security/scanner/SecurityScanner.helpers.d.ts +14 -11
  83. package/dist/src/security/scanner/SecurityScanner.helpers.d.ts.map +1 -1
  84. package/dist/src/security/scanner/SecurityScanner.helpers.js +26 -124
  85. package/dist/src/security/scanner/SecurityScanner.helpers.js.map +1 -1
  86. package/dist/src/security/scanner/SecurityScanner.js +114 -36
  87. package/dist/src/security/scanner/SecurityScanner.js.map +1 -1
  88. package/dist/src/security/scanner/SecurityScanner.paste-host.d.ts +69 -0
  89. package/dist/src/security/scanner/SecurityScanner.paste-host.d.ts.map +1 -0
  90. package/dist/src/security/scanner/SecurityScanner.paste-host.js +342 -0
  91. package/dist/src/security/scanner/SecurityScanner.paste-host.js.map +1 -0
  92. package/dist/src/security/scanner/SecurityScanner.risk-score.d.ts +27 -0
  93. package/dist/src/security/scanner/SecurityScanner.risk-score.d.ts.map +1 -0
  94. package/dist/src/security/scanner/SecurityScanner.risk-score.js +178 -0
  95. package/dist/src/security/scanner/SecurityScanner.risk-score.js.map +1 -0
  96. package/dist/src/security/scanner/SecurityScanner.urls.d.ts +20 -0
  97. package/dist/src/security/scanner/SecurityScanner.urls.d.ts.map +1 -0
  98. package/dist/src/security/scanner/SecurityScanner.urls.js +26 -0
  99. package/dist/src/security/scanner/SecurityScanner.urls.js.map +1 -0
  100. package/dist/src/security/scanner/index.d.ts +2 -2
  101. package/dist/src/security/scanner/index.d.ts.map +1 -1
  102. package/dist/src/security/scanner/index.js +5 -1
  103. package/dist/src/security/scanner/index.js.map +1 -1
  104. package/dist/src/security/scanner/multiline-category-closure.test.js +6 -0
  105. package/dist/src/security/scanner/multiline-category-closure.test.js.map +1 -1
  106. package/dist/src/security/scanner/patterns.d.ts +41 -21
  107. package/dist/src/security/scanner/patterns.d.ts.map +1 -1
  108. package/dist/src/security/scanner/patterns.exec.d.ts +89 -0
  109. package/dist/src/security/scanner/patterns.exec.d.ts.map +1 -0
  110. package/dist/src/security/scanner/patterns.exec.js +115 -0
  111. package/dist/src/security/scanner/patterns.exec.js.map +1 -0
  112. package/dist/src/security/scanner/patterns.js +78 -41
  113. package/dist/src/security/scanner/patterns.js.map +1 -1
  114. package/dist/src/security/scanner/types.d.ts +15 -1
  115. package/dist/src/security/scanner/types.d.ts.map +1 -1
  116. package/dist/src/security/scanner/typosquat.d.ts +10 -0
  117. package/dist/src/security/scanner/typosquat.d.ts.map +1 -1
  118. package/dist/src/security/scanner/typosquat.js +5 -1
  119. package/dist/src/security/scanner/typosquat.js.map +1 -1
  120. package/dist/src/security/scanner/weights.d.ts +12 -3
  121. package/dist/src/security/scanner/weights.d.ts.map +1 -1
  122. package/dist/src/security/scanner/weights.js +50 -1
  123. package/dist/src/security/scanner/weights.js.map +1 -1
  124. package/dist/src/services/agent-pack/prompt-source.d.ts +15 -0
  125. package/dist/src/services/agent-pack/prompt-source.d.ts.map +1 -1
  126. package/dist/src/services/agent-pack/prompt-source.js +32 -1
  127. package/dist/src/services/agent-pack/prompt-source.js.map +1 -1
  128. package/dist/src/services/agent-pack/skill-md.d.ts.map +1 -1
  129. package/dist/src/services/agent-pack/skill-md.js +9 -1
  130. package/dist/src/services/agent-pack/skill-md.js.map +1 -1
  131. package/dist/src/services/bundled-sibling-scan.d.ts +87 -13
  132. package/dist/src/services/bundled-sibling-scan.d.ts.map +1 -1
  133. package/dist/src/services/bundled-sibling-scan.js +177 -33
  134. package/dist/src/services/bundled-sibling-scan.js.map +1 -1
  135. package/dist/src/services/recommend-guard.d.ts +19 -0
  136. package/dist/src/services/recommend-guard.d.ts.map +1 -1
  137. package/dist/src/services/recommend-guard.js +28 -0
  138. package/dist/src/services/recommend-guard.js.map +1 -1
  139. package/dist/src/services/recommend-guard.test.js +23 -3
  140. package/dist/src/services/recommend-guard.test.js.map +1 -1
  141. package/dist/src/sync/index.d.ts +1 -0
  142. package/dist/src/sync/index.d.ts.map +1 -1
  143. package/dist/src/sync/index.js +2 -0
  144. package/dist/src/sync/index.js.map +1 -1
  145. package/dist/src/sync/license-status-client.d.ts +56 -0
  146. package/dist/src/sync/license-status-client.d.ts.map +1 -0
  147. package/dist/src/sync/license-status-client.js +118 -0
  148. package/dist/src/sync/license-status-client.js.map +1 -0
  149. package/dist/src/sync/license-status-client.test.d.ts +14 -0
  150. package/dist/src/sync/license-status-client.test.d.ts.map +1 -0
  151. package/dist/src/sync/license-status-client.test.js +136 -0
  152. package/dist/src/sync/license-status-client.test.js.map +1 -0
  153. package/dist/src/types.d.ts +13 -0
  154. package/dist/src/types.d.ts.map +1 -1
  155. package/dist/src/utils/version-check.d.ts +58 -5
  156. package/dist/src/utils/version-check.d.ts.map +1 -1
  157. package/dist/src/utils/version-check.js +73 -2
  158. package/dist/src/utils/version-check.js.map +1 -1
  159. package/dist/src/utils/version-check.test.js +49 -10
  160. package/dist/src/utils/version-check.test.js.map +1 -1
  161. package/dist/tests/SecurityScanner.scoring.test.js +123 -0
  162. package/dist/tests/SecurityScanner.scoring.test.js.map +1 -1
  163. package/dist/tests/security/advisory-severity-guard.test.d.ts +20 -0
  164. package/dist/tests/security/advisory-severity-guard.test.d.ts.map +1 -0
  165. package/dist/tests/security/advisory-severity-guard.test.js +70 -0
  166. package/dist/tests/security/advisory-severity-guard.test.js.map +1 -0
  167. package/dist/tests/security/archive-evasion.test.d.ts +2 -0
  168. package/dist/tests/security/archive-evasion.test.d.ts.map +1 -0
  169. package/dist/tests/security/archive-evasion.test.js +126 -0
  170. package/dist/tests/security/archive-evasion.test.js.map +1 -0
  171. package/dist/tests/security/clawhavoc-e2e.fixtures.d.ts +71 -0
  172. package/dist/tests/security/clawhavoc-e2e.fixtures.d.ts.map +1 -0
  173. package/dist/tests/security/clawhavoc-e2e.fixtures.js +135 -0
  174. package/dist/tests/security/clawhavoc-e2e.fixtures.js.map +1 -0
  175. package/dist/tests/security/co-signal-escalation.test.d.ts +30 -0
  176. package/dist/tests/security/co-signal-escalation.test.d.ts.map +1 -0
  177. package/dist/tests/security/co-signal-escalation.test.js +494 -0
  178. package/dist/tests/security/co-signal-escalation.test.js.map +1 -0
  179. package/dist/tests/security/decoy-misdirection.test.d.ts +2 -0
  180. package/dist/tests/security/decoy-misdirection.test.d.ts.map +1 -0
  181. package/dist/tests/security/decoy-misdirection.test.js +130 -0
  182. package/dist/tests/security/decoy-misdirection.test.js.map +1 -0
  183. package/dist/tests/security/encoded-payload.test.d.ts +2 -0
  184. package/dist/tests/security/encoded-payload.test.d.ts.map +1 -0
  185. package/dist/tests/security/encoded-payload.test.js +199 -0
  186. package/dist/tests/security/encoded-payload.test.js.map +1 -0
  187. package/dist/tests/security/gatekeeper-bypass.test.d.ts +2 -0
  188. package/dist/tests/security/gatekeeper-bypass.test.d.ts.map +1 -0
  189. package/dist/tests/security/gatekeeper-bypass.test.js +167 -0
  190. package/dist/tests/security/gatekeeper-bypass.test.js.map +1 -0
  191. package/dist/tests/security/paste-host-fetch.test.d.ts +2 -0
  192. package/dist/tests/security/paste-host-fetch.test.d.ts.map +1 -0
  193. package/dist/tests/security/paste-host-fetch.test.js +199 -0
  194. package/dist/tests/security/paste-host-fetch.test.js.map +1 -0
  195. package/dist/tests/security/scanner-regression-guard.test.js +5 -0
  196. package/dist/tests/security/scanner-regression-guard.test.js.map +1 -1
  197. package/dist/tests/security/smi5879-corroboration.core.test.d.ts +31 -0
  198. package/dist/tests/security/smi5879-corroboration.core.test.d.ts.map +1 -0
  199. package/dist/tests/security/smi5879-corroboration.core.test.js +157 -0
  200. package/dist/tests/security/smi5879-corroboration.core.test.js.map +1 -0
  201. package/dist/tests/services/aidefence-feedback.test.js +5 -0
  202. package/dist/tests/services/aidefence-feedback.test.js.map +1 -1
  203. package/dist/tests/services/bundled-sibling-scan.test.js +171 -10
  204. package/dist/tests/services/bundled-sibling-scan.test.js.map +1 -1
  205. package/dist/tests/skill-md-cli-fallback-parity.test.d.ts +22 -0
  206. package/dist/tests/skill-md-cli-fallback-parity.test.d.ts.map +1 -0
  207. package/dist/tests/skill-md-cli-fallback-parity.test.js +92 -0
  208. package/dist/tests/skill-md-cli-fallback-parity.test.js.map +1 -0
  209. package/dist/tests/skill-scanner/allowlist.test.js +14 -1
  210. package/dist/tests/skill-scanner/allowlist.test.js.map +1 -1
  211. package/package.json +7 -7
package/CHANGELOG.md CHANGED
@@ -4,6 +4,214 @@ All notable changes to `@skillsmith/core` are documented here.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## v0.12.0
8
+
9
+ - **Cadence**: Mechanical cadence alignment (no changes since v0.11.7).
10
+ - **Feature**: new `resolveSessionTier` client (`sync/license-status-client.ts`, SMI-6098) —
11
+ authenticates the stored device-login session against `/license-status` (proactively refreshing
12
+ via the existing `resolveAccessToken` helper) so the MCP server can resolve a real subscription
13
+ tier for a `skillsmith login` session with no separately-configured `SKILLSMITH_API_KEY`.
14
+ Throws `SessionTierAuthError` (no session / refresh failed) or `SessionTierTransientError`
15
+ (network error, HTTP 429/5xx, unexpected response) so callers never conflate "couldn't check"
16
+ with a definitive `community` result.
17
+ - **Fix**: `analyzeMarkdownContext`'s indented-code-block heuristic (4+ spaces or a tab = a
18
+ markdown documentation example) was being applied unconditionally to every scanned file,
19
+ including the new Gap 8 extended siblings — real, non-markdown source files. Since virtually all
20
+ indented Python/Ruby/Perl/etc. control-flow bodies match this pattern, it silently downgraded
21
+ `sensitive_path` findings from `high` to `medium` and blocked `escalateCodeExecution`'s
22
+ co-signal escalation path in those files, discovered via a genuine multi-signal backdoor fixture
23
+ (a `~/.ssh` read next to a `curl|bash`) that failed to escalate to `critical` purely because its
24
+ containing function body was indented. `analyzeMarkdownContext`/`scanSkillContent` (both edge
25
+ twins) and core's `SecurityScanner.scan()` now take an `isMarkdown` parameter (default `true`,
26
+ byte-identical behavior for every existing caller) that the indexer's extended-sibling scan path
27
+ and `bundled-sibling-scan.ts`'s executable-code candidates now pass `false` for.
28
+ - **Feature**: scoped bundled-file scan expansion to operational code (SMI-6033 Wave 2, Gap 8) —
29
+ the final wave of the ClawHavoc scanner-gap remediation initiative. The indexer previously only
30
+ ever read `SKILL.md` plus 7 fixed sibling filenames, never `scripts/`, `src/`, or `bin/`, so a
31
+ backdoor buried mid-function in otherwise-working operational code was structurally invisible to
32
+ the scanner. New `fetchRepoTreeEntries` (run-scoped memoized, budgeted via
33
+ `MAX_TREE_FETCHES_PER_RUN`/`SKILLSMITH_MAX_TREE_FETCHES_PER_RUN`, default 300) fetches the
34
+ repo's git tree once per repo per run; `enumerateExtendedSiblingTargets` deterministically ranks
35
+ and caps (`MAX_EXTENDED_SIBLING_FILES = 20`) executable-code candidates (`.sh .py .js .mjs .cjs
36
+ .ts .rb .php .ps1 .pl`) by SKILL.md-reference, entry-point naming, path depth, then lexicographic
37
+ order. Two new `skills` columns (`scan_coverage_incomplete`/`scan_coverage_note`) record — never
38
+ silently — when the extended selection couldn't cover everything (count cap, size cap, a
39
+ transient sibling-fetch failure, a Trees API fetch failure, a truncated tree response, or a
40
+ spent per-run fetch budget); a clean 404 does not count. Surfaced in `get_skill`, `search`, and
41
+ `skill_recommend` output as a plain informational caveat (never a rejection signal). Local
42
+ `skill_rescan`/`skill_validate` gets the same ranked, capped selection via
43
+ `bundled-sibling-scan.ts`'s `collectExecutableCodeFiles` (replacing the narrower `.sh`-only
44
+ `collectShFiles`), closing that module's own documented Phase-3 follow-up — this narrows its
45
+ existing local scan-file cap from 50 to 20 to match the registry-side cap, an intentional
46
+ behavior change, not a pure addition.
47
+ - **Fix**: adversarial review of the above (2026-08-16) found that applying the existing
48
+ sibling-rejection rule (any `code_execution`/`obfuscated_directive` finding on a non-doc sibling
49
+ standalone-quarantines) unchanged to the new extended surface would have quarantined ordinary
50
+ installer scripts — a `scripts/install.sh` using the industry-standard `curl | bash` idiom
51
+ (rustup, Homebrew, nvm, bun) scores the SAME `code_execution:medium` finding as an actual
52
+ backdoor, and the plan's own policy is explicit that a bare `curl | bash` must never
53
+ standalone-quarantine. `mergeSiblingScans`/`isExecutionThreat` now require `critical` severity
54
+ for a `code_execution` finding to drive rejection specifically on an extended-surface sibling
55
+ (reached only via the existing co-signal escalation model, i.e. a real secondary signal like a
56
+ `~/.ssh` read nearby) — the original 7 fixed siblings' rejection rule is completely unchanged.
57
+ `obfuscated_directive` remains rejectable at any severity on both surfaces (delta-gated against a
58
+ real decode step, no legitimate-installer shape). The Deno edge indexer's Trees-API memoization
59
+ and per-run fetch budget, previously documented as "run-scoped" on the (incorrect) assumption
60
+ that each invocation is a fresh process, are now actually reset at the top of every
61
+ `Deno.serve` invocation (`index.ts`) — left unreset, a warm isolate could silently report a stale
62
+ scan as fully covered.
63
+ - **Fix**: `prompt-source.ts`'s `agent-pack` T2 quota-forecast job body still had the stale
64
+ `1,000-call`/`10,000 calls` quota numbers, while the committed `packages/mcp-server` bundled
65
+ SKILL.md output was hand-corrected to `100-call`/`1,000 calls` — generator and committed
66
+ artifact had drifted, which `agent-pack.assets.test.ts`'s byte-identical drift-gate test would
67
+ have failed on merge. Fixed at the actual generator source so the two stay in sync
68
+ (SMI-5893 Wave 11, GH#2368 C-19)
69
+ - **Fix**: `formatUpdateNotification` (`utils/version-check.ts`) now includes `result.updateCommand`
70
+ again — an earlier draft dropped the "how do I get the new npm version" instruction entirely
71
+ in favor of on-disk-artifact-refresh commands, which run against the still-old installed
72
+ binary and never upgrade anything; both instructions are necessary and neither substitutes the
73
+ other. The message also now names the artifact-refresh commands (`setup --force[--client]`,
74
+ `agent install`) needed to pick up hooks/SKILL.md/agent-pack changes, since upgrading the npm
75
+ package alone never rewrites those. New `resolveUpdateNotificationClient()` resolves
76
+ `SKILLSMITH_CLIENT` for this message without ever throwing (the only call site awaits it inside
77
+ a `.then()` with an empty `.catch()`, so an uncaught throw on an invalid env value silently
78
+ dropped the entire notification) and without guessing `'claude-code'` when unset (only Cursor's
79
+ generated config sets this env var; defaulting would point every other client at the wrong
80
+ install path) (SMI-5893 Wave 10, GH#2368 C-06/C-07/C-22)
81
+ - **Fix**: Cursor `hooks.json` installation (`agent-pack-installer.cursor-hooks.ts`) now cleans up
82
+ dead legacy `hooks.SessionStart`/`hooks.SessionEnd` keys a pre-Wave-8a install wrote before this
83
+ file's key-casing was corrected — those never got removed once the correct keys started being
84
+ written, so a re-merge left both the correct entries and the orphaned legacy ones in the same
85
+ file. Skips the cleanup entirely when either wire call reports a top-level-default conflict
86
+ (`ensureTopLevelDefaults`), since the real installer deliberately wrote nothing in that case and
87
+ running cleanup regardless would still silently delete the user's legacy hooks with nothing
88
+ installed to replace them. `hookEntryCommand` (`agent-pack-installer.harness.ts`) and
89
+ `writeBackup` (`agent-config-merge.json-array.ts`) are now exported for reuse by this cleanup
90
+ instead of being duplicated (SMI-5893 Wave 10, GH#2368 C-07)
91
+ - **Fix**: PR #2375 post-merge review follow-up (three findings). `recommend`'s shared footer text no longer claims detection "across all clients" — each surface (CLI, MCP) scans exactly one client per call, never a union. Cursor `hooks.json` installation's `ensureTopLevelDefaults` now fails closed (`status: 'conflict'`, no write) instead of silently preserving an existing incompatible top-level value (e.g. a wrong `version`) while still merging hook entries in (SMI-5893)
92
+ - **Fix**: Restored two verified drift points between the core and edge (production) security
93
+ scanners — the edge co-signal escalation set now includes `sensitive_path` (matching core), and
94
+ the edge `escalateCodeExecution` gained the SMI-5880 locality gate so a co-signal elsewhere in a
95
+ long document can no longer escalate a distant, unrelated `code_execution` finding. Exported
96
+ `AUTHORITY_CLAIMING_AFFIXES` from `typosquat.ts` (previously module-private) for reuse by a
97
+ planned decoy-URL detector (SMI-6033 Wave 1)
98
+ - **Feature**: two new security scanner detectors (SMI-6033 Wave 3). `gatekeeper_bypass`
99
+ (`SecurityScanner.compound.ts`) fires standalone-critical on an `xattr -c` (clear all extended
100
+ attributes) or `xattr -d com.apple.quarantine` command when the target basename correlates with
101
+ a fetch destination elsewhere in the content — uncorrelated usage stays medium; a later fix in
102
+ this wave also adds a trust-tier carve-out (see below). `archive_evasion` (new
103
+ `SecurityScanner.archive.ts`) detects password-protected archive usage via two sub-signals — CLI
104
+ invocation syntax (`unzip -P`, `unrar x -p<pw>`, `7z x -p<pw>`, `zip -P <pw> ... -e`) and prose
105
+ co-occurrence (an archive noun + a password noun within a bounded ±2-line window) — and reaches
106
+ standalone-critical only when the CLI form carries an inline literal (not `$VAR`, not a
107
+ placeholder) password AND the archive's target basename correlates with a fetch destination
108
+ elsewhere in the content; every other shape (out-of-band password, uncorrelated CLI usage, or
109
+ prose-only mention) stays medium/advisory. Both new finding types share the top-tier
110
+ weight/coefficient (2.0/0.40) already used by `code_execution`/`obfuscated_directive` — severity
111
+ alone (not a second weight tier) produces the two-outcome split. Both are wired into
112
+ `SecurityScanner.scan()` and mirrored byte-for-byte into the edge twins
113
+ (`supabase/functions/_shared/` and `scripts/indexer/_shared/`).
114
+ - **Feature**: paste/snippet-host reputation detector (SMI-6033 Wave 3, Gap 4). Two reputation
115
+ tiers (`patterns.ts`): `ANON_PASTE_HOSTS` + `URL_SHORTENER_DOMAINS` require real execution
116
+ evidence (direct pipe to an interpreter, npx direct-exec, or a later chmod/exec/source
117
+ correlation) to reach standalone-critical; `TRANSIENT_TRANSFER_HOSTS` (transfer.sh, file.io,
118
+ tmpfiles.org, temp.sh) is always medium, never critical — a deliberate exception for legitimate
119
+ debugging/incident-response fetches of ephemeral reproducers. `extractUrls` is promoted from a
120
+ private `SecurityScanner.ts` method to a shared `SecurityScanner.urls.ts` export so the existing
121
+ `scanUrls` detector and the new one (`SecurityScanner.paste-host.ts`, finding type
122
+ `paste_host_fetch`) share a single URL-extraction implementation. A paste-host URL that is merely
123
+ linked/mentioned, or fetched-but-not-executed, gets no new finding — it stays covered by the
124
+ existing `scanUrls` `url`:medium finding, unchanged. Shares the same top-tier weight/coefficient
125
+ (2.0/0.40) as `gatekeeper_bypass`/`archive_evasion`. Wired into `SecurityScanner.scan()` and
126
+ mirrored byte-for-byte into the edge twins (`supabase/functions/_shared/` and
127
+ `scripts/indexer/_shared/`).
128
+ - **Feature**: encoded-payload decode-and-recursively-rescan detector (SMI-6033 Wave 3, Gap 2).
129
+ Rather than a heuristic "this looks suspicious" flag, the new detector (`SecurityScanner.encoding.ts`,
130
+ finding type `encoded_payload`) finds a contiguous base64-alphabet run (`[A-Za-z0-9+/]{120,}={0,2}`
131
+ — the character class's deliberate exclusion of `-`/`_` is what keeps base64url-encoded JWTs out,
132
+ not a separate check), skips a candidate immediately preceded by a `data:image/`, `data:font/`, or
133
+ `data:audio/` prefix (benign data-URI blobs) or larger than ~200KB, attempts exactly one base64
134
+ decode, and — only when the result is valid UTF-8 with a plausible-text printable-character ratio —
135
+ recursively invokes the SAME scanner's full detector suite against the decoded text, folding its
136
+ findings into the outer `findings` array. This reuses the entire pattern arsenal instead of
137
+ duplicating it: a decoded `curl|bash` natively trips `code_execution` at its own top-tier severity,
138
+ exactly as if the attacker had shipped it undecoded. Recursion is bounded to depth 1 STRUCTURALLY,
139
+ not by convention — `SecurityScanner.ts`'s new private `runDetectors(content, lineContexts,
140
+ skipEncodedPayload)` method is what both the outer scan and the encoded-payload detector's own
141
+ recursive rescan call, and the rescan callback always passes `skipEncodedPayload: true`, so a base64
142
+ blob discovered inside already-decoded content can never itself be decoded. Two resource bounds cap
143
+ the cost of a single document scan: `MAX_BASE64_CANDIDATES = 8` per document and an aggregate
144
+ `MAX_DECODED_TOTAL_BYTES = 256_000` across all candidates. Each finding folded in from decoded
145
+ content carries a new `decodedFrom` field (`types.ts`) set to the OUTER document line the blob was
146
+ found on — the same provenance-marker role `filePath` already plays for a sibling-file finding. The
147
+ wrapper `encoded_payload` finding itself is deliberately advisory-tier only (weight 1.2 / coefficient
148
+ 0.04 — the `sensitive_path`/`typosquat` tier, NOT the 2.0/0.40 tier the other three Wave 3 detectors
149
+ use), since the escalation this gap achieves comes for free from whatever the decoded content's own
150
+ findings already are. Wired into `SecurityScanner.scan()` and mirrored byte-for-byte into the edge
151
+ twins (`supabase/functions/_shared/` and `scripts/indexer/_shared/`).
152
+ - **Fix**: `SecurityScanner.archive.ts` and `SecurityScanner.paste-host.ts` (SMI-6033 Wave 3) each
153
+ had a direct `.match(...)`/`.test(...)` call that bypassed this scanner's established
154
+ `safeRegexTest`/`safeRegexCheck` ReDoS-safe wrappers, flagged by CodeQL as a polynomial regular
155
+ expression on uncontrolled data. Routed all 8 call sites through the wrappers, matching the
156
+ convention already used everywhere else in the scanner, and mirrored the same fix into both edge
157
+ twins (`supabase/functions/_shared/` and `scripts/indexer/_shared/`), adding a local
158
+ `safeRegexCheck` helper alongside the existing local `safeRegexTest` in each Node-port file
159
+ (the edge twins can't share an import across the git-crypt boundary)
160
+ - **Feature**: `decoy_misdirection` URL-misdirection detector (SMI-6033 Wave 4, Gap 6). Catches a
161
+ skill fetching from a domain that doesn't match a vendor brand/authority claim made nearby in
162
+ the skill's own prose (e.g. "the official Anthropic toolkit" fetched from an unrelated domain).
163
+ Reuses `BRAND_ALIASES`/`AUTHORITY_CLAIMING_AFFIXES` from the existing typosquat detector, plus a
164
+ new `BRAND_CANONICAL_DOMAINS` map (`BRAND_ALIASES`' values are GitHub owner slugs, not DNS
165
+ domains — a gap not present in the plan's literal text, resolved during implementation).
166
+ Advisory-tier only (weight 1.2/coefficient 0.04, matching `typosquat`/`sensitive_path`/
167
+ `encoded_payload`) — never standalone-critical, per the plan's reconciliation table. Extracted
168
+ `calculateRiskScore` out of `SecurityScanner.helpers.ts` (which crossed the 500-line file gate
169
+ once this detector's breakdown wiring landed) into a new sibling `SecurityScanner.risk-score.ts`.
170
+ Wired into `SecurityScanner.scan()` and mirrored byte-for-byte into the edge twins
171
+ (`supabase/functions/_shared/security-scanner-edge.decoy.ts` +
172
+ `security-scanner-edge.brand-data.ts`, `scripts/indexer/_shared/` twins).
173
+ - **Feature**: `CO_SIGNAL_MIN_SEVERITY` escalation model replaces the flat
174
+ `CODE_EXECUTION_CO_OCCURRENCE` co-signal set (SMI-6033 Wave 4, Gap 1 + Gap 6). Path (a) — one
175
+ co-signal at or above its type's "high" minimum — is byte-identical to the pre-existing
176
+ behavior for the original four types (`data_exfiltration`, `privilege_escalation`,
177
+ `sensitive_path`, `obfuscated_directive`). Path (b) is new: at least two DISTINCT advisory-tier
178
+ types (`decoy_misdirection`, `archive_evasion`, `paste_host_fetch`, `gatekeeper_bypass`), each
179
+ non-documentation-context and within the existing 40-line locality window, escalate a weak
180
+ `code_execution` finding to critical — the direct fix for skills combining several individually
181
+ sub-threshold signals. Also lands Gap 1: a new `IMPERATIVE_FETCH_EXEC_PROSE` pattern set catches
182
+ natural-language install-and-run imperatives with no shell syntax ("download the installer from
183
+ thisurl.com and run it"), strengthening (not replacing) the precise low-FP literal-syntax
184
+ detector. Bumps `SCANNER_RULESET_VERSION` to `2026-08-15.1` (local MCP audit baseline scope
185
+ only).
186
+ - **Fix**: two real false-positive bugs found by a cross-model (GPT-5.6-Sol) adversarial review of
187
+ the `CO_SIGNAL_MIN_SEVERITY` model and the `decoy_misdirection` detector above, both reproduced
188
+ and pinned with regression tests before fixing. (1) Path (b)'s confidence gate was originally
189
+ relaxed to `confidence !== 'low'` for ALL eligible types, but `archive_evasion`'s prose-only
190
+ sub-signal and `decoy_misdirection`'s no-authority-affix form are both `confidence: 'medium'` by
191
+ construction — same as `paste_host_fetch`, the type the relaxation was actually meant to unblock
192
+ — so two fuzzy medium-confidence signals could co-escalate a weak `code_execution` finding on
193
+ completely benign content (reproduced: a benign vendor mention + a real vendor `curl|bash` + an
194
+ unrelated archive-password prose mention scored 51/quarantined before the fix, 23/clean after).
195
+ Narrowed the confidence carve-out to ONLY `paste_host_fetch`; every other type now requires
196
+ `confidence: 'high'`, matching the plan's literal text. (2) `escalateCodeExecution` never
197
+ checked the `code_execution` finding's OWN documentation context, only the co-signal's — a
198
+ finding inside a fenced security-research example could still be escalated by genuine non-doc
199
+ co-signals elsewhere in the document. (3) Two detector-precision fixes in `decoy_misdirection`
200
+ itself: a URL merely mentioned in prose on the same line as an unrelated fetch-verb usage (e.g.
201
+ `curl --version; see mirror docs at <url>`) was wrongly treated as the fetch target — fixed with
202
+ a strict `isActualFetchTarget` tokenization check; and the authority-affix search scanned the
203
+ entire ±5-line correlation window instead of the brand token's own line, letting an unrelated
204
+ nearby "official"/"authorized" phrase wrongly inflate confidence to `high` — scoped to the brand
205
+ token's own line. All four fixes applied identically to both edge twins.
206
+
207
+ ## v0.11.7
208
+
209
+ - **Fix**: Cursor UAT follow-up — website onboarding, CLI/MCP parity, hooks schema (#2375)
210
+ - **Feature**: SMI-5879 Wave 1 -- implement G-5 fixture-corpus corroboration (#2354)
211
+ - **Fix**: Cursor's `hooks.json` is now written in Cursor's actual native schema (`{ version: 1, hooks: { sessionStart: [{ command }] } }`) instead of Claude Code's entry shape (`{ matcher, hooks: [{ type, command }] }`) — the prior shape was silently invalid, and Cursor drops all hooks when the required top-level `version` key is missing. New `install/agent-pack-installer.cursor-hooks.ts` builder, split out from the shared Claude/generic JSON-hooks installer since the two schemas are structurally different, not just differently-keyed (SMI-5893 Wave 8, GH#2368)
212
+ - **Feature**: bundled agent-pack SKILL.md generation (`services/agent-pack/skill-md.ts`) now includes a "CLI Fallback" section, matching the pattern already shipped in `@skillsmith/cli`'s bundled skill, so an agent with a disconnected MCP server has a documented fallback instead of instructing dead tool calls (SMI-5893 Wave 6, GH#2368)
213
+ - **Feature**: new `services/recommend-guard.ts` shared helper (footer-text + dedup-by-`skill.id`) used by both `@skillsmith/cli` and `@skillsmith/mcp-server`'s `recommend` implementations, replacing two independent hardcoded-footer implementations (SMI-5893 Wave 7, GH#2368)
214
+
7
215
  ## v0.11.6
8
216
 
9
217
  - **Fix**: Harden manifest concurrency (uninstall lock, temp-file races) (#2331)
package/README.md CHANGED
@@ -6,13 +6,13 @@ Part of Skillsmith: a lifecycle layer for agent skills across teams.
6
6
 
7
7
  ## Contents
8
8
 
9
- - [What's New](#whats-new-in-v0116)
9
+ - [What's New](#whats-new-in-v0120)
10
10
  - [Installation](#installation)
11
11
  - [Quick Start](#quick-start)
12
12
  - [Features](#features)
13
13
  - [Exports](#exports)
14
14
 
15
- ## What's New in v0.11.6
15
+ ## What's New in v0.12.0
16
16
 
17
17
  - **Security-status reporting unified**: CLI and MCP now derive security-scan status (pass/fail, risk score, findings count) from one shared `deriveSecuritySummaryFromApiSkill`/`deriveSecuritySummaryFromSkillRow`, instead of two independently-maintained copies that could disagree on the same skill.
18
18
  - **`skill_compare` reuses `get_skill`'s resolution**: new shared `resolveSkillApiFirst()` fixes compare only ever hitting the local SQLite cache (no longer kept in sync with the remote-first registry), which made real, searchable skills report "not found."