@skillsmith/core 0.11.3 → 0.11.5

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 (304) hide show
  1. package/CHANGELOG.md +34 -9
  2. package/README.md +10 -7
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/src/api/client.d.ts.map +1 -1
  5. package/dist/src/api/client.js +15 -5
  6. package/dist/src/api/client.js.map +1 -1
  7. package/dist/src/api/client.private-registry.d.ts +80 -0
  8. package/dist/src/api/client.private-registry.d.ts.map +1 -0
  9. package/dist/src/api/client.private-registry.js +97 -0
  10. package/dist/src/api/client.private-registry.js.map +1 -0
  11. package/dist/src/api/client.private-registry.test.d.ts +9 -0
  12. package/dist/src/api/client.private-registry.test.d.ts.map +1 -0
  13. package/dist/src/api/client.private-registry.test.js +127 -0
  14. package/dist/src/api/client.private-registry.test.js.map +1 -0
  15. package/dist/src/api/client.toSkill.test.d.ts +25 -0
  16. package/dist/src/api/client.toSkill.test.d.ts.map +1 -0
  17. package/dist/src/api/client.toSkill.test.js +97 -0
  18. package/dist/src/api/client.toSkill.test.js.map +1 -0
  19. package/dist/src/api/client.token-refresh.d.ts +8 -0
  20. package/dist/src/api/client.token-refresh.d.ts.map +1 -1
  21. package/dist/src/api/client.token-refresh.js +26 -0
  22. package/dist/src/api/client.token-refresh.js.map +1 -1
  23. package/dist/src/api/client.token-refresh.test.js +82 -0
  24. package/dist/src/api/client.token-refresh.test.js.map +1 -1
  25. package/dist/src/api/index.d.ts +2 -0
  26. package/dist/src/api/index.d.ts.map +1 -1
  27. package/dist/src/api/index.js +9 -0
  28. package/dist/src/api/index.js.map +1 -1
  29. package/dist/src/api/security-summary.d.ts +73 -0
  30. package/dist/src/api/security-summary.d.ts.map +1 -0
  31. package/dist/src/api/security-summary.js +88 -0
  32. package/dist/src/api/security-summary.js.map +1 -0
  33. package/dist/src/api/security-summary.test.d.ts +9 -0
  34. package/dist/src/api/security-summary.test.d.ts.map +1 -0
  35. package/dist/src/api/security-summary.test.js +94 -0
  36. package/dist/src/api/security-summary.test.js.map +1 -0
  37. package/dist/src/config/config-atomic-write.d.ts +30 -10
  38. package/dist/src/config/config-atomic-write.d.ts.map +1 -1
  39. package/dist/src/config/config-atomic-write.js +33 -80
  40. package/dist/src/config/config-atomic-write.js.map +1 -1
  41. package/dist/src/config/config-atomic-write.test.d.ts +8 -0
  42. package/dist/src/config/config-atomic-write.test.d.ts.map +1 -1
  43. package/dist/src/config/config-atomic-write.test.js +64 -12
  44. package/dist/src/config/config-atomic-write.test.js.map +1 -1
  45. package/dist/src/config/owned-lock.acquire.d.ts +53 -0
  46. package/dist/src/config/owned-lock.acquire.d.ts.map +1 -0
  47. package/dist/src/config/owned-lock.acquire.js +140 -0
  48. package/dist/src/config/owned-lock.acquire.js.map +1 -0
  49. package/dist/src/config/owned-lock.claim.d.ts +84 -0
  50. package/dist/src/config/owned-lock.claim.d.ts.map +1 -0
  51. package/dist/src/config/owned-lock.claim.js +310 -0
  52. package/dist/src/config/owned-lock.claim.js.map +1 -0
  53. package/dist/src/config/owned-lock.d.ts +113 -0
  54. package/dist/src/config/owned-lock.d.ts.map +1 -0
  55. package/dist/src/config/owned-lock.js +121 -0
  56. package/dist/src/config/owned-lock.js.map +1 -0
  57. package/dist/src/config/owned-lock.test.d.ts +12 -0
  58. package/dist/src/config/owned-lock.test.d.ts.map +1 -0
  59. package/dist/src/config/owned-lock.test.js +412 -0
  60. package/dist/src/config/owned-lock.test.js.map +1 -0
  61. package/dist/src/config/owned-lock.types.d.ts +78 -0
  62. package/dist/src/config/owned-lock.types.d.ts.map +1 -0
  63. package/dist/src/config/owned-lock.types.js +30 -0
  64. package/dist/src/config/owned-lock.types.js.map +1 -0
  65. package/dist/src/db/createDatabase.d.ts.map +1 -1
  66. package/dist/src/db/createDatabase.js +19 -7
  67. package/dist/src/db/createDatabase.js.map +1 -1
  68. package/dist/src/embeddings/hnsw-store.exports.d.ts +11 -0
  69. package/dist/src/embeddings/hnsw-store.exports.d.ts.map +1 -0
  70. package/dist/src/embeddings/hnsw-store.exports.js +10 -0
  71. package/dist/src/embeddings/hnsw-store.exports.js.map +1 -0
  72. package/dist/src/embeddings/index.d.ts +1 -2
  73. package/dist/src/embeddings/index.d.ts.map +1 -1
  74. package/dist/src/embeddings/index.js +17 -3
  75. package/dist/src/embeddings/index.js.map +1 -1
  76. package/dist/src/embeddings/probe.d.ts.map +1 -1
  77. package/dist/src/embeddings/probe.js +4 -8
  78. package/dist/src/embeddings/probe.js.map +1 -1
  79. package/dist/src/exports/services.d.ts +4 -1
  80. package/dist/src/exports/services.d.ts.map +1 -1
  81. package/dist/src/exports/services.js +22 -0
  82. package/dist/src/exports/services.js.map +1 -1
  83. package/dist/src/index.d.ts +5 -3
  84. package/dist/src/index.d.ts.map +1 -1
  85. package/dist/src/index.js +17 -3
  86. package/dist/src/index.js.map +1 -1
  87. package/dist/src/install/index.d.ts +1 -1
  88. package/dist/src/install/index.d.ts.map +1 -1
  89. package/dist/src/install/index.js +1 -1
  90. package/dist/src/install/index.js.map +1 -1
  91. package/dist/src/install/paths.d.ts +10 -0
  92. package/dist/src/install/paths.d.ts.map +1 -1
  93. package/dist/src/install/paths.js +19 -0
  94. package/dist/src/install/paths.js.map +1 -1
  95. package/dist/src/logging/logger.test.js +57 -0
  96. package/dist/src/logging/logger.test.js.map +1 -1
  97. package/dist/src/logging/rotation.d.ts +27 -2
  98. package/dist/src/logging/rotation.d.ts.map +1 -1
  99. package/dist/src/logging/rotation.js +44 -3
  100. package/dist/src/logging/rotation.js.map +1 -1
  101. package/dist/src/logging/rotation.test.js +69 -0
  102. package/dist/src/logging/rotation.test.js.map +1 -1
  103. package/dist/src/logging/types.d.ts +8 -2
  104. package/dist/src/logging/types.d.ts.map +1 -1
  105. package/dist/src/security/index.d.ts +2 -1
  106. package/dist/src/security/index.d.ts.map +1 -1
  107. package/dist/src/security/index.js +3 -0
  108. package/dist/src/security/index.js.map +1 -1
  109. package/dist/src/security/scanner/SecurityScanner.d.ts +2 -2
  110. package/dist/src/security/scanner/SecurityScanner.d.ts.map +1 -1
  111. package/dist/src/security/scanner/SecurityScanner.evidence.d.ts +76 -0
  112. package/dist/src/security/scanner/SecurityScanner.evidence.d.ts.map +1 -0
  113. package/dist/src/security/scanner/SecurityScanner.evidence.js +176 -0
  114. package/dist/src/security/scanner/SecurityScanner.evidence.js.map +1 -0
  115. package/dist/src/security/scanner/SecurityScanner.exec.d.ts +8 -1
  116. package/dist/src/security/scanner/SecurityScanner.exec.d.ts.map +1 -1
  117. package/dist/src/security/scanner/SecurityScanner.exec.js +58 -2
  118. package/dist/src/security/scanner/SecurityScanner.exec.js.map +1 -1
  119. package/dist/src/security/scanner/SecurityScanner.helpers.d.ts +35 -10
  120. package/dist/src/security/scanner/SecurityScanner.helpers.d.ts.map +1 -1
  121. package/dist/src/security/scanner/SecurityScanner.helpers.js +106 -59
  122. package/dist/src/security/scanner/SecurityScanner.helpers.js.map +1 -1
  123. package/dist/src/security/scanner/SecurityScanner.js +41 -13
  124. package/dist/src/security/scanner/SecurityScanner.js.map +1 -1
  125. package/dist/src/security/scanner/SecurityScanner.pii.d.ts.map +1 -1
  126. package/dist/src/security/scanner/SecurityScanner.pii.js +23 -1
  127. package/dist/src/security/scanner/SecurityScanner.pii.js.map +1 -1
  128. package/dist/src/security/scanner/SecurityScanner.scanners.d.ts.map +1 -1
  129. package/dist/src/security/scanner/SecurityScanner.scanners.js +15 -3
  130. package/dist/src/security/scanner/SecurityScanner.scanners.js.map +1 -1
  131. package/dist/src/security/scanner/SecurityScanner.ssrf.d.ts +11 -1
  132. package/dist/src/security/scanner/SecurityScanner.ssrf.d.ts.map +1 -1
  133. package/dist/src/security/scanner/SecurityScanner.ssrf.js +18 -7
  134. package/dist/src/security/scanner/SecurityScanner.ssrf.js.map +1 -1
  135. package/dist/src/security/scanner/index.d.ts +3 -3
  136. package/dist/src/security/scanner/index.d.ts.map +1 -1
  137. package/dist/src/security/scanner/index.js +5 -2
  138. package/dist/src/security/scanner/index.js.map +1 -1
  139. package/dist/src/security/scanner/multiline-category-closure.test.d.ts +80 -0
  140. package/dist/src/security/scanner/multiline-category-closure.test.d.ts.map +1 -0
  141. package/dist/src/security/scanner/multiline-category-closure.test.js +287 -0
  142. package/dist/src/security/scanner/multiline-category-closure.test.js.map +1 -0
  143. package/dist/src/security/scanner/patterns.d.ts +41 -14
  144. package/dist/src/security/scanner/patterns.d.ts.map +1 -1
  145. package/dist/src/security/scanner/patterns.jailbreak.d.ts +41 -0
  146. package/dist/src/security/scanner/patterns.jailbreak.d.ts.map +1 -0
  147. package/dist/src/security/scanner/patterns.jailbreak.evidence.d.ts +35 -0
  148. package/dist/src/security/scanner/patterns.jailbreak.evidence.d.ts.map +1 -0
  149. package/dist/src/security/scanner/patterns.jailbreak.evidence.js +119 -0
  150. package/dist/src/security/scanner/patterns.jailbreak.evidence.js.map +1 -0
  151. package/dist/src/security/scanner/patterns.jailbreak.js +370 -0
  152. package/dist/src/security/scanner/patterns.jailbreak.js.map +1 -0
  153. package/dist/src/security/scanner/patterns.js +101 -82
  154. package/dist/src/security/scanner/patterns.js.map +1 -1
  155. package/dist/src/security/scanner/patterns.scope.d.ts +62 -0
  156. package/dist/src/security/scanner/patterns.scope.d.ts.map +1 -0
  157. package/dist/src/security/scanner/patterns.scope.js +207 -0
  158. package/dist/src/security/scanner/patterns.scope.js.map +1 -0
  159. package/dist/src/security/scanner/regex-utils.d.ts +20 -0
  160. package/dist/src/security/scanner/regex-utils.d.ts.map +1 -1
  161. package/dist/src/security/scanner/regex-utils.js +20 -0
  162. package/dist/src/security/scanner/regex-utils.js.map +1 -1
  163. package/dist/src/security/scanner/types.d.ts +35 -0
  164. package/dist/src/security/scanner/types.d.ts.map +1 -1
  165. package/dist/src/services/recommend-guard.d.ts +28 -0
  166. package/dist/src/services/recommend-guard.d.ts.map +1 -0
  167. package/dist/src/services/recommend-guard.js +34 -0
  168. package/dist/src/services/recommend-guard.js.map +1 -0
  169. package/dist/src/services/recommend-guard.test.d.ts +2 -0
  170. package/dist/src/services/recommend-guard.test.d.ts.map +1 -0
  171. package/dist/src/services/recommend-guard.test.js +28 -0
  172. package/dist/src/services/recommend-guard.test.js.map +1 -0
  173. package/dist/src/services/skill-installation.content.d.ts +88 -0
  174. package/dist/src/services/skill-installation.content.d.ts.map +1 -0
  175. package/dist/src/services/skill-installation.content.js +319 -0
  176. package/dist/src/services/skill-installation.content.js.map +1 -0
  177. package/dist/src/services/skill-installation.helpers.d.ts +32 -1
  178. package/dist/src/services/skill-installation.helpers.d.ts.map +1 -1
  179. package/dist/src/services/skill-installation.helpers.js +39 -6
  180. package/dist/src/services/skill-installation.helpers.js.map +1 -1
  181. package/dist/src/services/skill-installation.helpers.test.js +34 -2
  182. package/dist/src/services/skill-installation.helpers.test.js.map +1 -1
  183. package/dist/src/services/skill-installation.io.d.ts.map +1 -1
  184. package/dist/src/services/skill-installation.io.js +86 -2
  185. package/dist/src/services/skill-installation.io.js.map +1 -1
  186. package/dist/src/services/skill-installation.service.d.ts +20 -1
  187. package/dist/src/services/skill-installation.service.d.ts.map +1 -1
  188. package/dist/src/services/skill-installation.service.js +26 -4
  189. package/dist/src/services/skill-installation.service.js.map +1 -1
  190. package/dist/src/services/skill-installation.types.d.ts +42 -1
  191. package/dist/src/services/skill-installation.types.d.ts.map +1 -1
  192. package/dist/src/services/skill-installation.types.js.map +1 -1
  193. package/dist/src/services/skill-resolution.d.ts +67 -0
  194. package/dist/src/services/skill-resolution.d.ts.map +1 -0
  195. package/dist/src/services/skill-resolution.js +67 -0
  196. package/dist/src/services/skill-resolution.js.map +1 -0
  197. package/dist/src/services/skill-resolution.test.d.ts +2 -0
  198. package/dist/src/services/skill-resolution.test.d.ts.map +1 -0
  199. package/dist/src/services/skill-resolution.test.js +143 -0
  200. package/dist/src/services/skill-resolution.test.js.map +1 -0
  201. package/dist/src/sync/inventory-collector.d.ts.map +1 -1
  202. package/dist/src/sync/inventory-collector.js +2 -2
  203. package/dist/src/sync/inventory-collector.js.map +1 -1
  204. package/dist/src/utils/index.d.ts +1 -0
  205. package/dist/src/utils/index.d.ts.map +1 -1
  206. package/dist/src/utils/index.js +4 -0
  207. package/dist/src/utils/index.js.map +1 -1
  208. package/dist/src/utils/quiet-mode.d.ts +21 -0
  209. package/dist/src/utils/quiet-mode.d.ts.map +1 -0
  210. package/dist/src/utils/quiet-mode.js +26 -0
  211. package/dist/src/utils/quiet-mode.js.map +1 -0
  212. package/dist/tests/EmbeddingService.test.js +58 -1
  213. package/dist/tests/EmbeddingService.test.js.map +1 -1
  214. package/dist/tests/SecurityScanner.ai-defence.test.js +32 -7
  215. package/dist/tests/SecurityScanner.ai-defence.test.js.map +1 -1
  216. package/dist/tests/SecurityScanner.exec.test.js +79 -0
  217. package/dist/tests/SecurityScanner.exec.test.js.map +1 -1
  218. package/dist/tests/SecurityScanner.performance.test.js +5 -2
  219. package/dist/tests/SecurityScanner.performance.test.js.map +1 -1
  220. package/dist/tests/db/database-abstraction.test.js +45 -1
  221. package/dist/tests/db/database-abstraction.test.js.map +1 -1
  222. package/dist/tests/helpers/deterministic-dead-pid.d.ts +13 -0
  223. package/dist/tests/helpers/deterministic-dead-pid.d.ts.map +1 -0
  224. package/dist/tests/helpers/deterministic-dead-pid.js +31 -0
  225. package/dist/tests/helpers/deterministic-dead-pid.js.map +1 -0
  226. package/dist/tests/integration/owned-lock-lost-update.test.d.ts +12 -0
  227. package/dist/tests/integration/owned-lock-lost-update.test.d.ts.map +1 -0
  228. package/dist/tests/integration/owned-lock-lost-update.test.js +52 -0
  229. package/dist/tests/integration/owned-lock-lost-update.test.js.map +1 -0
  230. package/dist/tests/integration/owned-lock-reclaim-race.test.d.ts +19 -0
  231. package/dist/tests/integration/owned-lock-reclaim-race.test.d.ts.map +1 -0
  232. package/dist/tests/integration/owned-lock-reclaim-race.test.js +177 -0
  233. package/dist/tests/integration/owned-lock-reclaim-race.test.js.map +1 -0
  234. package/dist/tests/security/ContinuousSecurity.performance.test.js +6 -0
  235. package/dist/tests/security/ContinuousSecurity.performance.test.js.map +1 -1
  236. package/dist/tests/security/ContinuousSecurity.test.js +15 -0
  237. package/dist/tests/security/ContinuousSecurity.test.js.map +1 -1
  238. package/dist/tests/security/ad-crlf-injection.test.d.ts +18 -0
  239. package/dist/tests/security/ad-crlf-injection.test.d.ts.map +1 -0
  240. package/dist/tests/security/ad-crlf-injection.test.js +150 -0
  241. package/dist/tests/security/ad-crlf-injection.test.js.map +1 -0
  242. package/dist/tests/security/ai-defence-multiline-promotion.test.d.ts +25 -0
  243. package/dist/tests/security/ai-defence-multiline-promotion.test.d.ts.map +1 -0
  244. package/dist/tests/security/ai-defence-multiline-promotion.test.js +138 -0
  245. package/dist/tests/security/ai-defence-multiline-promotion.test.js.map +1 -0
  246. package/dist/tests/security/nested-instruction-long-attribute.test.d.ts +15 -0
  247. package/dist/tests/security/nested-instruction-long-attribute.test.d.ts.map +1 -0
  248. package/dist/tests/security/nested-instruction-long-attribute.test.js +60 -0
  249. package/dist/tests/security/nested-instruction-long-attribute.test.js.map +1 -0
  250. package/dist/tests/security/pattern-scope.test.d.ts +17 -0
  251. package/dist/tests/security/pattern-scope.test.d.ts.map +1 -0
  252. package/dist/tests/security/pattern-scope.test.js +128 -0
  253. package/dist/tests/security/pattern-scope.test.js.map +1 -0
  254. package/dist/tests/security/pii-detection.test.js +39 -0
  255. package/dist/tests/security/pii-detection.test.js.map +1 -1
  256. package/dist/tests/security/scanner-evidence-tiers.test.d.ts +30 -0
  257. package/dist/tests/security/scanner-evidence-tiers.test.d.ts.map +1 -0
  258. package/dist/tests/security/scanner-evidence-tiers.test.js +258 -0
  259. package/dist/tests/security/scanner-evidence-tiers.test.js.map +1 -0
  260. package/dist/tests/security/scanner-privesc-severity.test.d.ts +23 -0
  261. package/dist/tests/security/scanner-privesc-severity.test.d.ts.map +1 -0
  262. package/dist/tests/security/scanner-privesc-severity.test.js +43 -0
  263. package/dist/tests/security/scanner-privesc-severity.test.js.map +1 -0
  264. package/dist/tests/security/scanner-regression-guard.exec-locality.test.d.ts +20 -0
  265. package/dist/tests/security/scanner-regression-guard.exec-locality.test.d.ts.map +1 -0
  266. package/dist/tests/security/scanner-regression-guard.exec-locality.test.js +71 -0
  267. package/dist/tests/security/scanner-regression-guard.exec-locality.test.js.map +1 -0
  268. package/dist/tests/security/scanner-regression-guard.test.d.ts +24 -0
  269. package/dist/tests/security/scanner-regression-guard.test.d.ts.map +1 -1
  270. package/dist/tests/security/scanner-regression-guard.test.js +59 -3
  271. package/dist/tests/security/scanner-regression-guard.test.js.map +1 -1
  272. package/dist/tests/security/scanner-ssrf-word-boundary.test.d.ts +19 -0
  273. package/dist/tests/security/scanner-ssrf-word-boundary.test.d.ts.map +1 -0
  274. package/dist/tests/security/scanner-ssrf-word-boundary.test.js +65 -0
  275. package/dist/tests/security/scanner-ssrf-word-boundary.test.js.map +1 -0
  276. package/dist/tests/security.test.js +11 -2
  277. package/dist/tests/security.test.js.map +1 -1
  278. package/dist/tests/skill-scanner/allowlist.test.js +15 -7
  279. package/dist/tests/skill-scanner/allowlist.test.js.map +1 -1
  280. package/dist/tests/unit/services/skill-installation.content.test.d.ts +10 -0
  281. package/dist/tests/unit/services/skill-installation.content.test.d.ts.map +1 -0
  282. package/dist/tests/unit/services/skill-installation.content.test.js +290 -0
  283. package/dist/tests/unit/services/skill-installation.content.test.js.map +1 -0
  284. package/dist/tests/unit/services/skill-installation.io.symlink.test.d.ts +8 -0
  285. package/dist/tests/unit/services/skill-installation.io.symlink.test.d.ts.map +1 -0
  286. package/dist/tests/unit/services/skill-installation.io.symlink.test.js +72 -0
  287. package/dist/tests/unit/services/skill-installation.io.symlink.test.js.map +1 -0
  288. package/dist/tests/unit/services/skill-installation.io.test.js +54 -1
  289. package/dist/tests/unit/services/skill-installation.io.test.js.map +1 -1
  290. package/dist/tests/unit/services/skill-installation.service.error-codes.test.d.ts +15 -0
  291. package/dist/tests/unit/services/skill-installation.service.error-codes.test.d.ts.map +1 -0
  292. package/dist/tests/unit/services/skill-installation.service.error-codes.test.js +190 -0
  293. package/dist/tests/unit/services/skill-installation.service.error-codes.test.js.map +1 -0
  294. package/dist/tests/unit/services/skill-installation.service.multi-client.test.d.ts +17 -0
  295. package/dist/tests/unit/services/skill-installation.service.multi-client.test.d.ts.map +1 -0
  296. package/dist/tests/unit/services/skill-installation.service.multi-client.test.js +126 -0
  297. package/dist/tests/unit/services/skill-installation.service.multi-client.test.js.map +1 -0
  298. package/dist/tests/unit/services/skill-installation.service.test.js +7 -112
  299. package/dist/tests/unit/services/skill-installation.service.test.js.map +1 -1
  300. package/dist/tests/utils/quiet-mode.test.d.ts +12 -0
  301. package/dist/tests/utils/quiet-mode.test.d.ts.map +1 -0
  302. package/dist/tests/utils/quiet-mode.test.js +42 -0
  303. package/dist/tests/utils/quiet-mode.test.js.map +1 -0
  304. package/package.json +17 -5
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Security Scanner — evidence-tier severity resolution (SMI-5876)
3
+ * @module @skillsmith/core/security/scanner/SecurityScanner.evidence
4
+ *
5
+ * Turns a pattern's `EvidenceType` (classified in
6
+ * `patterns.jailbreak.evidence.ts`'s `EVIDENCE_TYPE_BY_PATTERN`, SMI-5881)
7
+ * plus documentation context into a severity/confidence pair, and provides
8
+ * the corroboration escalation that lifts a `mention`-tier finding when it
9
+ * co-occurs with a genuinely dangerous non-documentation signal elsewhere in
10
+ * the same scan.
11
+ */
12
+ import { EVIDENCE_TYPE_BY_PATTERN } from './patterns.jailbreak.evidence.js';
13
+ /**
14
+ * Classify a pattern's evidence tier by object identity. Unmapped patterns
15
+ * (i.e. a pattern added to JAILBREAK_PATTERNS/AI_DEFENCE_PATTERNS without an
16
+ * `EVIDENCE_TYPE_BY_PATTERN` entry) default to `imperative_instruction` —
17
+ * fail-closed: forgetting to classify a new pattern makes it the STRONGEST
18
+ * tier, never the weakest, so a missed classification can never silently
19
+ * soften detection.
20
+ */
21
+ export function classifyEvidence(p) {
22
+ return EVIDENCE_TYPE_BY_PATTERN.get(p) ?? 'imperative_instruction';
23
+ }
24
+ /**
25
+ * Relative strength ordering used by the merge-loop in
26
+ * `SecurityScanner.helpers.ts` to pick the strongest evidence per line across
27
+ * both the multi-line and single-line scan passes. `imperative_instruction`,
28
+ * `instruction_override`, and `state_assertion` are equal rank — the
29
+ * severity table below treats them identically, and none needs to out-rank
30
+ * the others. `state_assertion` (SMI-5876 design-pass follow-up) is a
31
+ * distinct machine-readable REASON CODE for a declarative jailbroken-state
32
+ * assertion ("Jailbreak activated") rather than a directive aimed at the
33
+ * model — same severity tuple, different diagnostic label.
34
+ */
35
+ export const EVIDENCE_RANK = {
36
+ mention: 0,
37
+ role_turn_with_body: 1,
38
+ imperative_instruction: 2,
39
+ instruction_override: 2,
40
+ state_assertion: 2,
41
+ };
42
+ /** The highest rank in `EVIDENCE_RANK` — once reached, no later pattern on the same line can beat it. */
43
+ export const MAX_EVIDENCE_RANK = 2;
44
+ /**
45
+ * Severity/confidence decision table (SMI-5876 design §4.1). Doc-context
46
+ * confidence for the directive tiers is `medium`, not `low` — `low` would be
47
+ * self-contradictory next to a still-failing severity (it would also cut the
48
+ * risk-score weight to 0.3x, which is not the intent for a directive payload
49
+ * that merely happens to sit inside a fenced example).
50
+ */
51
+ export const EVIDENCE_SEVERITY_TABLE = {
52
+ mention: {
53
+ nonDoc: { severity: 'low', confidence: 'low' },
54
+ doc: { severity: 'low', confidence: 'low' },
55
+ },
56
+ role_turn_with_body: {
57
+ nonDoc: { severity: 'high', confidence: 'high' },
58
+ doc: { severity: 'medium', confidence: 'medium' },
59
+ },
60
+ imperative_instruction: {
61
+ nonDoc: { severity: 'critical', confidence: 'high' },
62
+ doc: { severity: 'high', confidence: 'medium' },
63
+ },
64
+ instruction_override: {
65
+ nonDoc: { severity: 'critical', confidence: 'high' },
66
+ doc: { severity: 'high', confidence: 'medium' },
67
+ },
68
+ // SMI-5876 design-pass follow-up: same tuple as imperative_instruction —
69
+ // state_assertion is a distinct reason code, not a distinct severity tier.
70
+ state_assertion: {
71
+ nonDoc: { severity: 'critical', confidence: 'high' },
72
+ doc: { severity: 'high', confidence: 'medium' },
73
+ },
74
+ };
75
+ /** Resolve the severity/confidence pair for an evidence tier + documentation context. */
76
+ export function resolveEvidenceSeverity(tier, inDocumentationContext) {
77
+ const entry = EVIDENCE_SEVERITY_TABLE[tier];
78
+ return inDocumentationContext ? entry.doc : entry.nonDoc;
79
+ }
80
+ /**
81
+ * SMI-5876 (post-implementation correction): corroborator eligibility is an
82
+ * ALLOWLIST of INSTRUCTION-BEARING attack categories, not a denylist of
83
+ * everything-except-self.
84
+ *
85
+ * The original design specified `type ∉ {jailbreak, ai_defence}`, reasoning only
86
+ * about same-category circularity. That is the classic denylist failure — it
87
+ * silently admits every category nobody considered. It admitted `pii`, whose
88
+ * plain-email pattern fires on the author-contact bullet that nearly every
89
+ * SKILL.md carries, so an unrelated `- Email: support@…` 74 lines away lifted a
90
+ * benign "jailbreak" mention to high in this repo's OWN bundled SKILL.md — one of
91
+ * SMI-5876's three acceptance fixtures.
92
+ *
93
+ * Both existing precedents here are allowlists: CODE_EXECUTION_CO_OCCURRENCE
94
+ * (SecurityScanner.exec.ts) and SUPPLY_CHAIN_CO_SIGNALS
95
+ * (SecurityScanner.hostile-update.ts). This restores that discipline.
96
+ *
97
+ * Inclusion rule — the category must be INSTRUCTION-BEARING (its patterns match
98
+ * an instruction to do something adversarial). Deliberately excluded:
99
+ * • `pii` / `sensitive_path` — REFERENCE-bearing. A contact email or a `.env`
100
+ * path says something about data hygiene, nothing about whether a jailbreak
101
+ * ATTEMPT is present.
102
+ * • `suspicious_pattern` — `eval(` / `child_process` are ordinary vocabulary in
103
+ * dev-tooling skills.
104
+ * • `social_engineering` / `prompt_leaking` — semantically ADJACENT to jailbreak;
105
+ * admitting them recreates the very circularity the self-exclusion prevents
106
+ * (a persona/roleplay skill would lift its own jailbreak mentions).
107
+ * • `typosquat` — a property of the skill NAME, not its content.
108
+ * • `url` — capped at medium, so it could never qualify anyway.
109
+ * • `jailbreak` / `ai_defence` — same-category self-lift (original exclusion).
110
+ */
111
+ const CORROBORATING_SIGNALS = new Set([
112
+ 'code_execution',
113
+ 'obfuscated_directive',
114
+ 'data_exfiltration',
115
+ 'privilege_escalation',
116
+ 'ssrf',
117
+ ]);
118
+ /**
119
+ * Maximum line distance between a mention and its corroborator.
120
+ *
121
+ * Why a bounded window is NOT a bypass vector: corroboration is a risk-SCORE and
122
+ * hostile-update amplifier only — it can never flip `passed`, because any
123
+ * qualifying corroborator is itself high/critical and therefore already fails the
124
+ * scan on its own (`passed = !hasCritical && !hasHigh && !exceedsThreshold`). An
125
+ * attacker who separates the two signals to duck the window still fails on the
126
+ * corroborator alone; all they achieve is a slightly lower numeric score on a
127
+ * scan that already blocks.
128
+ *
129
+ * 40 lines comfortably spans a real payload block (persona preamble plus the
130
+ * instructions it sets up) while excluding the "unrelated finding in a different
131
+ * section of a long document" case that motivated this constraint.
132
+ */
133
+ const MAX_CORROBORATION_LINE_DISTANCE = 40;
134
+ /**
135
+ * Escalate a `mention`-tier finding when an ALLOWLISTED non-documentation
136
+ * high/critical finding is ALSO present within `MAX_CORROBORATION_LINE_DISTANCE`
137
+ * lines in the same scan (SMI-5876 design §4.3, corrected post-implementation —
138
+ * see `CORROBORATING_SIGNALS`' doc comment for the denylist->allowlist fix and
139
+ * the locality-window rationale). Mutates findings in place, mirroring
140
+ * `escalateCodeExecution`'s mutation style (SecurityScanner.exec.ts).
141
+ *
142
+ * MUST run after `escalateCodeExecution` (SecurityScanner.ts's `scan()`) so a
143
+ * freshly-critical `code_execution` finding can itself serve as a
144
+ * corroborator. No feedback loop the other way: `CODE_EXECUTION_CO_OCCURRENCE`
145
+ * (SecurityScanner.exec.ts) contains only `data_exfiltration`,
146
+ * `privilege_escalation`, `sensitive_path`, and `obfuscated_directive` — never
147
+ * `jailbreak` or `ai_defence` — so lifting a mention here can never
148
+ * retroactively change `escalateCodeExecution`'s own decision (verified by
149
+ * reading SecurityScanner.exec.ts directly, SMI-5876 implementation).
150
+ */
151
+ export function escalateCorroboratedMentions(findings) {
152
+ const corroborators = findings.filter((f) => (f.severity === 'high' || f.severity === 'critical') &&
153
+ f.inDocumentationContext !== true &&
154
+ CORROBORATING_SIGNALS.has(f.type) &&
155
+ typeof f.lineNumber === 'number');
156
+ if (corroborators.length === 0)
157
+ return;
158
+ for (const finding of findings) {
159
+ if (finding.evidenceType !== 'mention')
160
+ continue;
161
+ if (typeof finding.lineNumber !== 'number')
162
+ continue;
163
+ const corroborator = corroborators.find((c) => Math.abs(c.lineNumber - finding.lineNumber) <=
164
+ MAX_CORROBORATION_LINE_DISTANCE);
165
+ if (!corroborator)
166
+ continue;
167
+ const inDoc = finding.inDocumentationContext === true;
168
+ finding.severity = inDoc ? 'medium' : 'high';
169
+ finding.confidence = inDoc ? 'low' : 'medium';
170
+ finding.corroborated = true;
171
+ finding.message =
172
+ `Corroborated by a co-occurring non-documentation ${corroborator.type} ` +
173
+ `finding at line ${corroborator.lineNumber} — ${finding.message}`;
174
+ }
175
+ }
176
+ //# sourceMappingURL=SecurityScanner.evidence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SecurityScanner.evidence.js","sourceRoot":"","sources":["../../../../src/security/scanner/SecurityScanner.evidence.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AASH,OAAO,EAAE,wBAAwB,EAAE,MAAM,kCAAkC,CAAA;AAE3E;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB,CAAC,CAAS;IACxC,OAAO,wBAAwB,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,wBAAwB,CAAA;AACpE,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,aAAa,GAAiC;IACzD,OAAO,EAAE,CAAC;IACV,mBAAmB,EAAE,CAAC;IACtB,sBAAsB,EAAE,CAAC;IACzB,oBAAoB,EAAE,CAAC;IACvB,eAAe,EAAE,CAAC;CACnB,CAAA;AAED,yGAAyG;AACzG,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAA;AAElC;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAMhC;IACF,OAAO,EAAE;QACP,MAAM,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE;QAC9C,GAAG,EAAE,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE;KAC5C;IACD,mBAAmB,EAAE;QACnB,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE;QAChD,GAAG,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE;KAClD;IACD,sBAAsB,EAAE;QACtB,MAAM,EAAE,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE;QACpD,GAAG,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE;KAChD;IACD,oBAAoB,EAAE;QACpB,MAAM,EAAE,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE;QACpD,GAAG,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE;KAChD;IACD,yEAAyE;IACzE,2EAA2E;IAC3E,eAAe,EAAE;QACf,MAAM,EAAE,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,EAAE;QACpD,GAAG,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE;KAChD;CACF,CAAA;AAED,yFAAyF;AACzF,MAAM,UAAU,uBAAuB,CACrC,IAAkB,EAClB,sBAA+B;IAE/B,MAAM,KAAK,GAAG,uBAAuB,CAAC,IAAI,CAAC,CAAA;IAC3C,OAAO,sBAAsB,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAA;AAC1D,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,qBAAqB,GAAqC,IAAI,GAAG,CAAC;IACtE,gBAAgB;IAChB,sBAAsB;IACtB,mBAAmB;IACnB,sBAAsB;IACtB,MAAM;CACP,CAAC,CAAA;AAEF;;;;;;;;;;;;;;GAcG;AACH,MAAM,+BAA+B,GAAG,EAAE,CAAA;AAE1C;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,4BAA4B,CAAC,QAA2B;IACtE,MAAM,aAAa,GAAG,QAAQ,CAAC,MAAM,CACnC,CAAC,CAAC,EAAE,EAAE,CACJ,CAAC,CAAC,CAAC,QAAQ,KAAK,MAAM,IAAI,CAAC,CAAC,QAAQ,KAAK,UAAU,CAAC;QACpD,CAAC,CAAC,sBAAsB,KAAK,IAAI;QACjC,qBAAqB,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;QACjC,OAAO,CAAC,CAAC,UAAU,KAAK,QAAQ,CACnC,CAAA;IACD,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC;QAAE,OAAM;IAEtC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,IAAI,OAAO,CAAC,YAAY,KAAK,SAAS;YAAE,SAAQ;QAChD,IAAI,OAAO,OAAO,CAAC,UAAU,KAAK,QAAQ;YAAE,SAAQ;QACpD,MAAM,YAAY,GAAG,aAAa,CAAC,IAAI,CACrC,CAAC,CAAC,EAAE,EAAE,CACJ,IAAI,CAAC,GAAG,CAAE,CAAC,CAAC,UAAqB,GAAI,OAAO,CAAC,UAAqB,CAAC;YACnE,+BAA+B,CAClC,CAAA;QACD,IAAI,CAAC,YAAY;YAAE,SAAQ;QAE3B,MAAM,KAAK,GAAG,OAAO,CAAC,sBAAsB,KAAK,IAAI,CAAA;QACrD,OAAO,CAAC,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAA;QAC5C,OAAO,CAAC,UAAU,GAAG,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAA;QAC7C,OAAO,CAAC,YAAY,GAAG,IAAI,CAAA;QAC3B,OAAO,CAAC,OAAO;YACb,oDAAoD,YAAY,CAAC,IAAI,GAAG;gBACxE,mBAAmB,YAAY,CAAC,UAAU,MAAM,OAAO,CAAC,OAAO,EAAE,CAAA;IACrE,CAAC;AACH,CAAC"}
@@ -55,9 +55,16 @@ export declare function scanObfuscatedDirective(content: string): SecurityFindin
55
55
  /**
56
56
  * Escalate the code_execution finding to CRITICAL when a NON-documentation
57
57
  * high/critical exfiltration / privilege / credential-path / obfuscation signal
58
- * is also present. Mutates the finding in place. Requiring the co-signal to be
58
+ * is also present WITHIN `MAX_CODE_EXECUTION_CO_SIGNAL_LINE_DISTANCE` lines
59
+ * (SMI-5880 — previously any distance qualified, so an unrelated finding in a
60
+ * different section of a long document escalated an unrelated code_execution
61
+ * finding). Mutates the finding in place. Requiring the co-signal to be
59
62
  * non-documentation keeps legitimate security-research skills (whose examples
60
63
  * live in fenced blocks) at MEDIUM.
64
+ *
65
+ * Runs BEFORE the sibling corroboration mechanism in `scan()` — see that
66
+ * function's doc comment (SecurityScanner.evidence.ts) for why the ordering is
67
+ * safe in both directions.
61
68
  */
62
69
  export declare function escalateCodeExecution(findings: SecurityFinding[]): void;
63
70
  //# sourceMappingURL=SecurityScanner.exec.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SecurityScanner.exec.d.ts","sourceRoot":"","sources":["../../../../src/security/scanner/SecurityScanner.exec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAuB,MAAM,YAAY,CAAA;AAGtE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAA;AA4B/D;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEhD;AA0BD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,YAAY,CAAC,EAAE,WAAW,EAAE,GAC3B,eAAe,EAAE,CA6BnB;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe,EAAE,CAuC1E;AAaD;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,eAAe,EAAE,GAAG,IAAI,CAgBvE"}
1
+ {"version":3,"file":"SecurityScanner.exec.d.ts","sourceRoot":"","sources":["../../../../src/security/scanner/SecurityScanner.exec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAuB,MAAM,YAAY,CAAA;AAGtE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAA;AA4B/D;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEhD;AA0BD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,MAAM,EACf,YAAY,CAAC,EAAE,WAAW,EAAE,GAC3B,eAAe,EAAE,CA6BnB;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe,EAAE,CAuC1E;AA8DD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,eAAe,EAAE,GAAG,IAAI,CAiBvE"}
@@ -168,12 +168,67 @@ const CODE_EXECUTION_CO_OCCURRENCE = new Set([
168
168
  'sensitive_path',
169
169
  'obfuscated_directive',
170
170
  ]);
171
+ /**
172
+ * Maximum line distance between the `code_execution` finding and the co-signal
173
+ * that escalates it (SMI-5880). Before this bound, a co-signal ANYWHERE in the
174
+ * document — any distance, any unrelated section — escalated the single
175
+ * `code_execution` finding to critical.
176
+ *
177
+ * Why a bounded window is NOT a bypass vector: every eligible co-signal is
178
+ * already `high` or `critical`, and `scan()` (SecurityScanner.ts) computes
179
+ * `passed = !hasCritical && !hasHigh && !exceedsThreshold` across ALL findings
180
+ * with no locality filter and no documentation filter. A qualifying co-signal
181
+ * anywhere therefore forces `passed === false` on its own, BEFORE this function
182
+ * runs. Splitting the two signals to duck this window cannot turn a failing scan
183
+ * into a passing one; it changes only this finding's own label (medium vs
184
+ * critical) and the risk-score magnitude of a scan that already blocks.
185
+ * Hostile-update detection is likewise untouched: `detectSupplyChain()`
186
+ * (SecurityScanner.hostile-update.ts) pairs a NEW non-doc `code_execution` of ANY
187
+ * severity with a NEW non-doc high/critical co-signal at ANY distance, so a
188
+ * far-apart rug-pull still returns the `hostile` verdict and its supply-chain
189
+ * reason string.
190
+ *
191
+ * Deliberately the same 40 as the sibling corroboration mechanism's locality
192
+ * constant (SecurityScanner.evidence.ts) so the scanner has ONE locality model
193
+ * — but kept as its own constant, not imported: the two mechanisms are
194
+ * independently tunable, and the edge twins (SMI-5879) cannot import from core
195
+ * and must carry their own copy.
196
+ */
197
+ const MAX_CODE_EXECUTION_CO_SIGNAL_LINE_DISTANCE = 40;
198
+ /**
199
+ * Locality gate for `escalateCodeExecution`. Fail-CLOSED on missing metadata:
200
+ * when either side has no `lineNumber` the distance is unknowable, and this
201
+ * subsystem's convention is that missing metadata must never SILENTLY weaken
202
+ * detection — cf. `classifyEvidence` (SecurityScanner.evidence.ts), whose
203
+ * unmapped-pattern default is the STRONGEST tier for exactly this reason. An
204
+ * unknowable distance therefore preserves the pre-SMI-5880 unbounded behavior for
205
+ * that pair rather than suppressing the co-signal.
206
+ *
207
+ * Unreachable through `scan()` today: `scanCodeExecution` above and every emitter
208
+ * of the four CODE_EXECUTION_CO_OCCURRENCE types set `lineNumber` unconditionally
209
+ * (the sensitive-path/data-exfiltration/privilege-escalation scanners in the
210
+ * scanners module, the chmod-compound detector, and `scanObfuscatedDirective`
211
+ * above). Kept as an explicit, directly-tested branch so a future detector that
212
+ * forgets `lineNumber` degrades safe instead of silently ceasing to escalate.
213
+ */
214
+ function isWithinCoSignalWindow(codeExecLine, coSignalLine) {
215
+ if (typeof codeExecLine !== 'number' || typeof coSignalLine !== 'number')
216
+ return true;
217
+ return Math.abs(codeExecLine - coSignalLine) <= MAX_CODE_EXECUTION_CO_SIGNAL_LINE_DISTANCE;
218
+ }
171
219
  /**
172
220
  * Escalate the code_execution finding to CRITICAL when a NON-documentation
173
221
  * high/critical exfiltration / privilege / credential-path / obfuscation signal
174
- * is also present. Mutates the finding in place. Requiring the co-signal to be
222
+ * is also present WITHIN `MAX_CODE_EXECUTION_CO_SIGNAL_LINE_DISTANCE` lines
223
+ * (SMI-5880 — previously any distance qualified, so an unrelated finding in a
224
+ * different section of a long document escalated an unrelated code_execution
225
+ * finding). Mutates the finding in place. Requiring the co-signal to be
175
226
  * non-documentation keeps legitimate security-research skills (whose examples
176
227
  * live in fenced blocks) at MEDIUM.
228
+ *
229
+ * Runs BEFORE the sibling corroboration mechanism in `scan()` — see that
230
+ * function's doc comment (SecurityScanner.evidence.ts) for why the ordering is
231
+ * safe in both directions.
177
232
  */
178
233
  export function escalateCodeExecution(findings) {
179
234
  const codeExec = findings.find((f) => f.type === 'code_execution');
@@ -182,7 +237,8 @@ export function escalateCodeExecution(findings) {
182
237
  const hasDangerousCoSignal = findings.some((f) => f !== codeExec &&
183
238
  CODE_EXECUTION_CO_OCCURRENCE.has(f.type) &&
184
239
  f.inDocumentationContext !== true &&
185
- (f.severity === 'high' || f.severity === 'critical'));
240
+ (f.severity === 'high' || f.severity === 'critical') &&
241
+ isWithinCoSignalWindow(codeExec.lineNumber, f.lineNumber));
186
242
  if (hasDangerousCoSignal) {
187
243
  codeExec.severity = 'critical';
188
244
  codeExec.message = `Remote fetch piped to an interpreter, co-occurring with exfiltration/privilege/credential signals — likely supply-chain execution. ${codeExec.message}`;
@@ -1 +1 @@
1
- {"version":3,"file":"SecurityScanner.exec.js","sourceRoot":"","sources":["../../../../src/security/scanner/SecurityScanner.exec.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"SecurityScanner.exec.js","sourceRoot":"","sources":["../../../../src/security/scanner/SecurityScanner.exec.ts"],"names":[],"mappings":"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"}
@@ -2,7 +2,7 @@
2
2
  * Security Scanner Helper Functions
3
3
  * @module @skillsmith/core/security/scanner/SecurityScanner.helpers
4
4
  */
5
- import type { SecurityFinding, SecurityFindingType, RiskScoreBreakdown, SecuritySeverity } from './types.js';
5
+ import type { SecurityFinding, SecurityFindingType, RiskScoreBreakdown, EvidenceType } from './types.js';
6
6
  /**
7
7
  * Context information for each line in markdown content
8
8
  */
@@ -21,12 +21,6 @@ export interface LineContext {
21
21
  */
22
22
  inFrontmatter: boolean;
23
23
  }
24
- /**
25
- * SMI-1532: Check if a regex pattern requires multi-line matching
26
- * Patterns that contain newline/carriage-return characters or start with
27
- * multi-line anchors need to be tested against full content, not line-by-line.
28
- */
29
- export declare function isMultilinePattern(pattern: RegExp): boolean;
30
24
  /**
31
25
  * Analyze markdown content and return context for each line
32
26
  * Used to reduce false positives in documentation/examples
@@ -59,14 +53,45 @@ interface MultilineScanConfig {
59
53
  type: SecurityFindingType;
60
54
  messagePrefix: string;
61
55
  patterns: RegExp[];
62
- /** Severity pair: [inDocContext, normalContext] */
63
- severities: [SecuritySeverity, SecuritySeverity];
56
+ /**
57
+ * SMI-5876: replaces the flat `[docContext, normal]` severity pair — returns
58
+ * the evidence tier for a given pattern (by object identity) so
59
+ * severity/confidence can be resolved per-line via `resolveEvidenceSeverity`
60
+ * once the strongest tier for that line is known.
61
+ */
62
+ classify: (pattern: RegExp) => EvidenceType;
64
63
  }
65
64
  /**
66
65
  * Scan content for patterns that may span multiple lines.
67
66
  * Multi-line patterns are tested against full content; single-line patterns per-line.
67
+ *
68
+ * SMI-5876: patterns within a single category (jailbreak / ai_defence) can now
69
+ * carry DIFFERENT evidence tiers (a bare "jailbreak" mention vs. an explicit
70
+ * "ignore all previous instructions" override), so array-declaration order is
71
+ * no longer sufficient to decide which match wins on a line where multiple
72
+ * patterns fire — the STRONGEST evidence tier per line wins, computed via a
73
+ * merge across both passes (`bestByLine`), not "first match, in array order."
74
+ *
75
+ * Two hazards this closes (see the SMI-5876 design doc §5 for the full
76
+ * argument):
77
+ * Hazard A — pass 2 used to `break` on the FIRST matching pattern
78
+ * regardless of tier, so a weaker mention declared earlier in the array
79
+ * could shadow a stronger directive declared later on the same line.
80
+ * Hazard B — a multiline pattern match used to suppress ALL single-line
81
+ * patterns on that line (`flaggedLines`), so a mention-tier multiline
82
+ * match could hide a directive-tier single-line match on the same line.
83
+ * `bestByLine` SEEDS pass 2 with pass 1's result instead of skipping the
84
+ * line outright, so pass 2 can still find something stronger.
85
+ */
86
+ export declare function scanPatternsWithMultilineSupport(content: string, config: MultilineScanConfig, lineContexts?: LineContext[],
87
+ /**
88
+ * SMI-5881: explicit cap (UTF-16 code units) for the pass-1 full-content
89
+ * regex scan — `Math.min(MAX_CONTENT_LENGTH_FOR_REGEX, maxContentLength)`,
90
+ * computed once by SecurityScanner.scan() and threaded through so the
91
+ * truncation finding it emits matches what actually gets scanned. Omitted
92
+ * (undefined) falls back to safeRegexTest's own default.
68
93
  */
69
- export declare function scanPatternsWithMultilineSupport(content: string, config: MultilineScanConfig, lineContexts?: LineContext[]): SecurityFinding[];
94
+ maxLength?: number): SecurityFinding[];
70
95
  /**
71
96
  * SMI-685: Calculate risk score from findings
72
97
  * SMI-1513: Accounts for confidence levels (low confidence = reduced weight)
@@ -1 +1 @@
1
- {"version":3,"file":"SecurityScanner.helpers.d.ts","sourceRoot":"","sources":["../../../../src/security/scanner/SecurityScanner.helpers.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EACV,eAAe,EACf,mBAAmB,EACnB,kBAAkB,EAElB,gBAAgB,EACjB,MAAM,YAAY,CAAA;AAQnB;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,EAAE,OAAO,CAAA;IACpB,OAAO,EAAE,OAAO,CAAA;IAChB,cAAc,EAAE,OAAO,CAAA;IACvB,YAAY,EAAE,OAAO,CAAA;IACrB;;;;;;OAMG;IACH,aAAa,EAAE,OAAO,CAAA;CACvB;AAMD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAK3D;AAMD;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,EAAE,CA0ErE;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAEhE;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAW5E;AAMD,UAAU,mBAAmB;IAC3B,IAAI,EAAE,mBAAmB,CAAA;IACzB,aAAa,EAAE,MAAM,CAAA;IACrB,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,mDAAmD;IACnD,UAAU,EAAE,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,CAAA;CACjD;AAED;;;GAGG;AACH,wBAAgB,gCAAgC,CAC9C,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,mBAAmB,EAC3B,YAAY,CAAC,EAAE,WAAW,EAAE,GAC3B,eAAe,EAAE,CAoEnB;AAMD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,eAAe,EAAE,GAAG;IAC/D,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,kBAAkB,CAAA;CAC9B,CA6HA"}
1
+ {"version":3,"file":"SecurityScanner.helpers.d.ts","sourceRoot":"","sources":["../../../../src/security/scanner/SecurityScanner.helpers.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EACV,eAAe,EACf,mBAAmB,EACnB,kBAAkB,EAElB,YAAY,EACb,MAAM,YAAY,CAAA;AAcnB;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,EAAE,OAAO,CAAA;IACpB,OAAO,EAAE,OAAO,CAAA;IAChB,cAAc,EAAE,OAAO,CAAA;IACvB,YAAY,EAAE,OAAO,CAAA;IACrB;;;;;;OAMG;IACH,aAAa,EAAE,OAAO,CAAA;CACvB;AAMD;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,EAAE,CA0ErE;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAEhE;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAW5E;AAMD,UAAU,mBAAmB;IAC3B,IAAI,EAAE,mBAAmB,CAAA;IACzB,aAAa,EAAE,MAAM,CAAA;IACrB,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB;;;;;OAKG;IACH,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,YAAY,CAAA;CAC5C;AAUD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,gCAAgC,CAC9C,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,mBAAmB,EAC3B,YAAY,CAAC,EAAE,WAAW,EAAE;AAC5B;;;;;;GAMG;AACH,SAAS,CAAC,EAAE,MAAM,GACjB,eAAe,EAAE,CAmGnB;AAMD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,eAAe,EAAE,GAAG;IAC/D,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,kBAAkB,CAAA;CAC9B,CA6HA"}
@@ -4,18 +4,8 @@
4
4
  */
5
5
  import { SEVERITY_WEIGHTS, CATEGORY_WEIGHTS } from './weights.js';
6
6
  import { safeRegexTest } from './regex-utils.js';
7
- // ============================================================================
8
- // Pattern Helpers
9
- // ============================================================================
10
- /**
11
- * SMI-1532: Check if a regex pattern requires multi-line matching
12
- * Patterns that contain newline/carriage-return characters or start with
13
- * multi-line anchors need to be tested against full content, not line-by-line.
14
- */
15
- export function isMultilinePattern(pattern) {
16
- const patternStr = pattern.source;
17
- return (patternStr.includes('\\r') || patternStr.includes('\\n') || patternStr.startsWith('(?:^|\\n)'));
18
- }
7
+ import { EVIDENCE_RANK, MAX_EVIDENCE_RANK, resolveEvidenceSeverity, } from './SecurityScanner.evidence.js';
8
+ import { resolvePatternScope } from './patterns.scope.js';
19
9
  // ============================================================================
20
10
  // Markdown Context Analysis
21
11
  // ============================================================================
@@ -130,70 +120,127 @@ export function isWithinInlineCode(line, matchIndex) {
130
120
  /**
131
121
  * Scan content for patterns that may span multiple lines.
132
122
  * Multi-line patterns are tested against full content; single-line patterns per-line.
123
+ *
124
+ * SMI-5876: patterns within a single category (jailbreak / ai_defence) can now
125
+ * carry DIFFERENT evidence tiers (a bare "jailbreak" mention vs. an explicit
126
+ * "ignore all previous instructions" override), so array-declaration order is
127
+ * no longer sufficient to decide which match wins on a line where multiple
128
+ * patterns fire — the STRONGEST evidence tier per line wins, computed via a
129
+ * merge across both passes (`bestByLine`), not "first match, in array order."
130
+ *
131
+ * Two hazards this closes (see the SMI-5876 design doc §5 for the full
132
+ * argument):
133
+ * Hazard A — pass 2 used to `break` on the FIRST matching pattern
134
+ * regardless of tier, so a weaker mention declared earlier in the array
135
+ * could shadow a stronger directive declared later on the same line.
136
+ * Hazard B — a multiline pattern match used to suppress ALL single-line
137
+ * patterns on that line (`flaggedLines`), so a mention-tier multiline
138
+ * match could hide a directive-tier single-line match on the same line.
139
+ * `bestByLine` SEEDS pass 2 with pass 1's result instead of skipping the
140
+ * line outright, so pass 2 can still find something stronger.
133
141
  */
134
- export function scanPatternsWithMultilineSupport(content, config, lineContexts) {
135
- const findings = [];
142
+ export function scanPatternsWithMultilineSupport(content, config, lineContexts,
143
+ /**
144
+ * SMI-5881: explicit cap (UTF-16 code units) for the pass-1 full-content
145
+ * regex scan — `Math.min(MAX_CONTENT_LENGTH_FOR_REGEX, maxContentLength)`,
146
+ * computed once by SecurityScanner.scan() and threaded through so the
147
+ * truncation finding it emits matches what actually gets scanned. Omitted
148
+ * (undefined) falls back to safeRegexTest's own default.
149
+ */
150
+ maxLength) {
136
151
  const lines = content.split('\n');
137
152
  const contexts = lineContexts ?? analyzeMarkdownContext(content);
138
- const flaggedLines = new Set();
139
- // First pass: multi-line patterns against full content
153
+ const bestByLine = new Map();
154
+ const rank = (t) => EVIDENCE_RANK[t];
155
+ // First pass: 'content' | 'both'-scope patterns against full content. No
156
+ // break — every such pattern is independently tested (same cost as before:
157
+ // at most one full-content scan per pattern), and the strongest tier per
158
+ // line wins.
140
159
  for (const pattern of config.patterns) {
141
- if (isMultilinePattern(pattern)) {
142
- const match = safeRegexTest(pattern, content);
143
- if (match) {
144
- const matchIndex = content.indexOf(match[0]);
145
- const lineNumber = content.slice(0, matchIndex).split('\n').length;
146
- const ctx = contexts[lineNumber - 1];
147
- const matchLine = lines[lineNumber - 1] ?? '';
148
- const lineOffset = content.lastIndexOf('\n', matchIndex - 1) + 1;
149
- const matchCol = matchIndex - lineOffset;
150
- const inInlineCode = ctx?.isInlineCode && isWithinInlineCode(matchLine, matchCol);
151
- const inDocContext = ctx ? isDocumentationContext(ctx) || inInlineCode : false;
152
- const confidence = inDocContext ? 'low' : 'high';
153
- const severity = inDocContext ? config.severities[0] : config.severities[1];
154
- const truncated = match[0].slice(0, 50);
155
- findings.push({
156
- type: config.type,
157
- severity,
158
- message: `${config.messagePrefix}: "${truncated}${match[0].length > 50 ? '...' : ''}"`,
159
- location: match[0].trim().slice(0, 100),
160
- lineNumber,
161
- category: config.type,
162
- inDocumentationContext: inDocContext,
163
- confidence,
164
- });
165
- flaggedLines.add(lineNumber);
166
- }
160
+ if (resolvePatternScope(pattern) === 'line')
161
+ continue;
162
+ const match = safeRegexTest(pattern, content, maxLength);
163
+ if (!match)
164
+ continue;
165
+ // SMI-5876: use the match's own reported index rather than a fresh
166
+ // content.indexOf(match[0]) the latter misreports the line whenever the
167
+ // matched text ALSO occurs earlier in the content (a real hazard fixed in
168
+ // the same pass as the evidence-tier merge).
169
+ const matchIndex = match.index ?? content.indexOf(match[0]);
170
+ const lineNumber = content.slice(0, matchIndex).split('\n').length;
171
+ const ctx = contexts[lineNumber - 1];
172
+ const matchLine = lines[lineNumber - 1] ?? '';
173
+ const lineOffset = content.lastIndexOf('\n', matchIndex - 1) + 1;
174
+ const matchCol = matchIndex - lineOffset;
175
+ const inInlineCode = ctx?.isInlineCode && isWithinInlineCode(matchLine, matchCol);
176
+ const inDocContext = ctx ? isDocumentationContext(ctx) || inInlineCode : false;
177
+ const tier = config.classify(pattern);
178
+ const incumbent = bestByLine.get(lineNumber);
179
+ if (!incumbent || rank(tier) > rank(incumbent.tier)) {
180
+ bestByLine.set(lineNumber, {
181
+ tier,
182
+ matchText: match[0],
183
+ location: match[0].trim().slice(0, 100),
184
+ inDocContext,
185
+ });
167
186
  }
168
187
  }
169
- // Second pass: single-line patterns per-line
188
+ // Second pass: 'line' | 'both'-scope patterns, per-line. Seeds `best` from
189
+ // pass 1 (does NOT skip a line pass 1 already flagged) so a line-local
190
+ // directive can still beat a weaker multiline mention on the same line
191
+ // (Hazard B).
170
192
  lines.forEach((line, index) => {
171
- if (flaggedLines.has(index + 1))
172
- return;
193
+ const lineNumber = index + 1;
173
194
  const ctx = contexts[index];
195
+ let best = bestByLine.get(lineNumber) ?? null;
174
196
  for (const pattern of config.patterns) {
175
- if (isMultilinePattern(pattern))
197
+ if (resolvePatternScope(pattern) === 'content')
176
198
  continue;
177
199
  const match = safeRegexTest(pattern, line);
178
- if (match) {
200
+ if (!match)
201
+ continue;
202
+ const tier = config.classify(pattern);
203
+ if (!best || rank(tier) > rank(best.tier)) {
179
204
  const inInlineCode = ctx?.isInlineCode && isWithinInlineCode(line, match.index ?? 0);
180
205
  const inDocContext = ctx ? isDocumentationContext(ctx) || inInlineCode : false;
181
- const confidence = inDocContext ? 'low' : 'high';
182
- const severity = inDocContext ? config.severities[0] : config.severities[1];
183
- findings.push({
184
- type: config.type,
185
- severity,
186
- message: `${config.messagePrefix}: "${match[0].slice(0, 50)}${match[0].length > 50 ? '...' : ''}"`,
206
+ best = {
207
+ tier,
208
+ matchText: match[0],
187
209
  location: line.trim().slice(0, 100),
188
- lineNumber: index + 1,
189
- category: config.type,
190
- inDocumentationContext: inDocContext,
191
- confidence,
192
- });
193
- break;
210
+ inDocContext,
211
+ };
194
212
  }
213
+ // Cannot be beaten — stop scanning this line's remaining patterns.
214
+ if (rank(tier) === MAX_EVIDENCE_RANK)
215
+ break;
195
216
  }
217
+ if (best)
218
+ bestByLine.set(lineNumber, best);
196
219
  });
220
+ // Emit one finding per flagged line, in ascending line-number order
221
+ // (matches the line-by-line order callers/tests expect from a per-line
222
+ // scan; emission cardinality only ever tightens — one finding per line per
223
+ // category, same as before the merge).
224
+ const findings = [];
225
+ const orderedLines = Array.from(bestByLine.keys()).sort((a, b) => a - b);
226
+ for (const lineNumber of orderedLines) {
227
+ const candidate = bestByLine.get(lineNumber);
228
+ if (!candidate)
229
+ continue;
230
+ const { severity, confidence } = resolveEvidenceSeverity(candidate.tier, candidate.inDocContext);
231
+ const truncated = candidate.matchText.slice(0, 50);
232
+ findings.push({
233
+ type: config.type,
234
+ severity,
235
+ message: `${config.messagePrefix}: "${truncated}${candidate.matchText.length > 50 ? '...' : ''}"`,
236
+ location: candidate.location,
237
+ lineNumber,
238
+ category: config.type,
239
+ inDocumentationContext: candidate.inDocContext,
240
+ confidence,
241
+ evidenceType: candidate.tier,
242
+ });
243
+ }
197
244
  return findings;
198
245
  }
199
246
  // ============================================================================