@skillsmith/core 0.11.7 → 0.12.1

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