@skillsmith/core 0.11.7 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. package/CHANGELOG.md +200 -0
  2. package/README.md +2 -2
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/src/api/client.types.d.ts +9 -0
  5. package/dist/src/api/client.types.d.ts.map +1 -1
  6. package/dist/src/api/schemas.d.ts +6 -0
  7. package/dist/src/api/schemas.d.ts.map +1 -1
  8. package/dist/src/api/schemas.js +5 -0
  9. package/dist/src/api/schemas.js.map +1 -1
  10. package/dist/src/api/security-summary.d.ts +10 -1
  11. package/dist/src/api/security-summary.d.ts.map +1 -1
  12. package/dist/src/api/security-summary.js +9 -0
  13. package/dist/src/api/security-summary.js.map +1 -1
  14. package/dist/src/api/security-summary.test.js +79 -1
  15. package/dist/src/api/security-summary.test.js.map +1 -1
  16. package/dist/src/api/types.d.ts +9 -0
  17. package/dist/src/api/types.d.ts.map +1 -1
  18. package/dist/src/exports/services.d.ts +1 -1
  19. package/dist/src/exports/services.d.ts.map +1 -1
  20. package/dist/src/exports/services.js +3 -1
  21. package/dist/src/exports/services.js.map +1 -1
  22. package/dist/src/index.d.ts +2 -2
  23. package/dist/src/index.d.ts.map +1 -1
  24. package/dist/src/index.js +2 -2
  25. package/dist/src/index.js.map +1 -1
  26. package/dist/src/install/agent-config-merge.json-array.d.ts +8 -0
  27. package/dist/src/install/agent-config-merge.json-array.d.ts.map +1 -1
  28. package/dist/src/install/agent-config-merge.json-array.js +22 -2
  29. package/dist/src/install/agent-config-merge.json-array.js.map +1 -1
  30. package/dist/src/install/agent-pack-installer.cursor-hooks.d.ts.map +1 -1
  31. package/dist/src/install/agent-pack-installer.cursor-hooks.js +122 -3
  32. package/dist/src/install/agent-pack-installer.cursor-hooks.js.map +1 -1
  33. package/dist/src/install/agent-pack-installer.cursor-hooks.test.js +146 -1
  34. package/dist/src/install/agent-pack-installer.cursor-hooks.test.js.map +1 -1
  35. package/dist/src/install/agent-pack-installer.harness.d.ts +8 -0
  36. package/dist/src/install/agent-pack-installer.harness.d.ts.map +1 -1
  37. package/dist/src/install/agent-pack-installer.harness.js +8 -1
  38. package/dist/src/install/agent-pack-installer.harness.js.map +1 -1
  39. package/dist/src/scripts/__tests__/scan-imported-skills.test.js +25 -0
  40. package/dist/src/scripts/__tests__/scan-imported-skills.test.js.map +1 -1
  41. package/dist/src/scripts/skill-scanner/trust-scorer.js +1 -1
  42. package/dist/src/scripts/skill-scanner/trust-scorer.js.map +1 -1
  43. package/dist/src/security/scanner/SecurityScanner.archive.d.ts +34 -0
  44. package/dist/src/security/scanner/SecurityScanner.archive.d.ts.map +1 -0
  45. package/dist/src/security/scanner/SecurityScanner.archive.js +218 -0
  46. package/dist/src/security/scanner/SecurityScanner.archive.js.map +1 -0
  47. package/dist/src/security/scanner/SecurityScanner.compound.d.ts +1 -0
  48. package/dist/src/security/scanner/SecurityScanner.compound.d.ts.map +1 -1
  49. package/dist/src/security/scanner/SecurityScanner.compound.js +117 -38
  50. package/dist/src/security/scanner/SecurityScanner.compound.js.map +1 -1
  51. package/dist/src/security/scanner/SecurityScanner.d.ts +41 -4
  52. package/dist/src/security/scanner/SecurityScanner.d.ts.map +1 -1
  53. package/dist/src/security/scanner/SecurityScanner.decoy.d.ts +72 -0
  54. package/dist/src/security/scanner/SecurityScanner.decoy.d.ts.map +1 -0
  55. package/dist/src/security/scanner/SecurityScanner.decoy.js +265 -0
  56. package/dist/src/security/scanner/SecurityScanner.decoy.js.map +1 -0
  57. package/dist/src/security/scanner/SecurityScanner.encoding.d.ts +56 -0
  58. package/dist/src/security/scanner/SecurityScanner.encoding.d.ts.map +1 -0
  59. package/dist/src/security/scanner/SecurityScanner.encoding.js +270 -0
  60. package/dist/src/security/scanner/SecurityScanner.encoding.js.map +1 -0
  61. package/dist/src/security/scanner/SecurityScanner.exec.d.ts +23 -20
  62. package/dist/src/security/scanner/SecurityScanner.exec.d.ts.map +1 -1
  63. package/dist/src/security/scanner/SecurityScanner.exec.js +207 -52
  64. package/dist/src/security/scanner/SecurityScanner.exec.js.map +1 -1
  65. package/dist/src/security/scanner/SecurityScanner.fetch-correlation.d.ts +115 -0
  66. package/dist/src/security/scanner/SecurityScanner.fetch-correlation.d.ts.map +1 -0
  67. package/dist/src/security/scanner/SecurityScanner.fetch-correlation.js +181 -0
  68. package/dist/src/security/scanner/SecurityScanner.fetch-correlation.js.map +1 -0
  69. package/dist/src/security/scanner/SecurityScanner.helpers.d.ts +14 -11
  70. package/dist/src/security/scanner/SecurityScanner.helpers.d.ts.map +1 -1
  71. package/dist/src/security/scanner/SecurityScanner.helpers.js +26 -124
  72. package/dist/src/security/scanner/SecurityScanner.helpers.js.map +1 -1
  73. package/dist/src/security/scanner/SecurityScanner.js +114 -36
  74. package/dist/src/security/scanner/SecurityScanner.js.map +1 -1
  75. package/dist/src/security/scanner/SecurityScanner.paste-host.d.ts +69 -0
  76. package/dist/src/security/scanner/SecurityScanner.paste-host.d.ts.map +1 -0
  77. package/dist/src/security/scanner/SecurityScanner.paste-host.js +342 -0
  78. package/dist/src/security/scanner/SecurityScanner.paste-host.js.map +1 -0
  79. package/dist/src/security/scanner/SecurityScanner.risk-score.d.ts +27 -0
  80. package/dist/src/security/scanner/SecurityScanner.risk-score.d.ts.map +1 -0
  81. package/dist/src/security/scanner/SecurityScanner.risk-score.js +178 -0
  82. package/dist/src/security/scanner/SecurityScanner.risk-score.js.map +1 -0
  83. package/dist/src/security/scanner/SecurityScanner.urls.d.ts +20 -0
  84. package/dist/src/security/scanner/SecurityScanner.urls.d.ts.map +1 -0
  85. package/dist/src/security/scanner/SecurityScanner.urls.js +26 -0
  86. package/dist/src/security/scanner/SecurityScanner.urls.js.map +1 -0
  87. package/dist/src/security/scanner/index.d.ts +2 -2
  88. package/dist/src/security/scanner/index.d.ts.map +1 -1
  89. package/dist/src/security/scanner/index.js +5 -1
  90. package/dist/src/security/scanner/index.js.map +1 -1
  91. package/dist/src/security/scanner/multiline-category-closure.test.js +6 -0
  92. package/dist/src/security/scanner/multiline-category-closure.test.js.map +1 -1
  93. package/dist/src/security/scanner/patterns.d.ts +41 -21
  94. package/dist/src/security/scanner/patterns.d.ts.map +1 -1
  95. package/dist/src/security/scanner/patterns.exec.d.ts +89 -0
  96. package/dist/src/security/scanner/patterns.exec.d.ts.map +1 -0
  97. package/dist/src/security/scanner/patterns.exec.js +115 -0
  98. package/dist/src/security/scanner/patterns.exec.js.map +1 -0
  99. package/dist/src/security/scanner/patterns.js +78 -41
  100. package/dist/src/security/scanner/patterns.js.map +1 -1
  101. package/dist/src/security/scanner/types.d.ts +15 -1
  102. package/dist/src/security/scanner/types.d.ts.map +1 -1
  103. package/dist/src/security/scanner/typosquat.d.ts +10 -0
  104. package/dist/src/security/scanner/typosquat.d.ts.map +1 -1
  105. package/dist/src/security/scanner/typosquat.js +5 -1
  106. package/dist/src/security/scanner/typosquat.js.map +1 -1
  107. package/dist/src/security/scanner/weights.d.ts +12 -3
  108. package/dist/src/security/scanner/weights.d.ts.map +1 -1
  109. package/dist/src/security/scanner/weights.js +50 -1
  110. package/dist/src/security/scanner/weights.js.map +1 -1
  111. package/dist/src/services/agent-pack/prompt-source.js +1 -1
  112. package/dist/src/services/agent-pack/prompt-source.js.map +1 -1
  113. package/dist/src/services/bundled-sibling-scan.d.ts +87 -13
  114. package/dist/src/services/bundled-sibling-scan.d.ts.map +1 -1
  115. package/dist/src/services/bundled-sibling-scan.js +177 -33
  116. package/dist/src/services/bundled-sibling-scan.js.map +1 -1
  117. package/dist/src/services/recommend-guard.d.ts.map +1 -1
  118. package/dist/src/services/recommend-guard.js +8 -1
  119. package/dist/src/services/recommend-guard.js.map +1 -1
  120. package/dist/src/services/recommend-guard.test.js +6 -2
  121. package/dist/src/services/recommend-guard.test.js.map +1 -1
  122. package/dist/src/sync/index.d.ts +1 -0
  123. package/dist/src/sync/index.d.ts.map +1 -1
  124. package/dist/src/sync/index.js +2 -0
  125. package/dist/src/sync/index.js.map +1 -1
  126. package/dist/src/sync/license-status-client.d.ts +56 -0
  127. package/dist/src/sync/license-status-client.d.ts.map +1 -0
  128. package/dist/src/sync/license-status-client.js +118 -0
  129. package/dist/src/sync/license-status-client.js.map +1 -0
  130. package/dist/src/sync/license-status-client.test.d.ts +14 -0
  131. package/dist/src/sync/license-status-client.test.d.ts.map +1 -0
  132. package/dist/src/sync/license-status-client.test.js +136 -0
  133. package/dist/src/sync/license-status-client.test.js.map +1 -0
  134. package/dist/src/types.d.ts +13 -0
  135. package/dist/src/types.d.ts.map +1 -1
  136. package/dist/src/utils/version-check.d.ts +58 -5
  137. package/dist/src/utils/version-check.d.ts.map +1 -1
  138. package/dist/src/utils/version-check.js +73 -2
  139. package/dist/src/utils/version-check.js.map +1 -1
  140. package/dist/src/utils/version-check.test.js +49 -10
  141. package/dist/src/utils/version-check.test.js.map +1 -1
  142. package/dist/tests/SecurityScanner.scoring.test.js +123 -0
  143. package/dist/tests/SecurityScanner.scoring.test.js.map +1 -1
  144. package/dist/tests/security/advisory-severity-guard.test.d.ts +20 -0
  145. package/dist/tests/security/advisory-severity-guard.test.d.ts.map +1 -0
  146. package/dist/tests/security/advisory-severity-guard.test.js +70 -0
  147. package/dist/tests/security/advisory-severity-guard.test.js.map +1 -0
  148. package/dist/tests/security/archive-evasion.test.d.ts +2 -0
  149. package/dist/tests/security/archive-evasion.test.d.ts.map +1 -0
  150. package/dist/tests/security/archive-evasion.test.js +126 -0
  151. package/dist/tests/security/archive-evasion.test.js.map +1 -0
  152. package/dist/tests/security/clawhavoc-e2e.fixtures.d.ts +71 -0
  153. package/dist/tests/security/clawhavoc-e2e.fixtures.d.ts.map +1 -0
  154. package/dist/tests/security/clawhavoc-e2e.fixtures.js +135 -0
  155. package/dist/tests/security/clawhavoc-e2e.fixtures.js.map +1 -0
  156. package/dist/tests/security/co-signal-escalation.test.d.ts +30 -0
  157. package/dist/tests/security/co-signal-escalation.test.d.ts.map +1 -0
  158. package/dist/tests/security/co-signal-escalation.test.js +494 -0
  159. package/dist/tests/security/co-signal-escalation.test.js.map +1 -0
  160. package/dist/tests/security/decoy-misdirection.test.d.ts +2 -0
  161. package/dist/tests/security/decoy-misdirection.test.d.ts.map +1 -0
  162. package/dist/tests/security/decoy-misdirection.test.js +130 -0
  163. package/dist/tests/security/decoy-misdirection.test.js.map +1 -0
  164. package/dist/tests/security/encoded-payload.test.d.ts +2 -0
  165. package/dist/tests/security/encoded-payload.test.d.ts.map +1 -0
  166. package/dist/tests/security/encoded-payload.test.js +199 -0
  167. package/dist/tests/security/encoded-payload.test.js.map +1 -0
  168. package/dist/tests/security/gatekeeper-bypass.test.d.ts +2 -0
  169. package/dist/tests/security/gatekeeper-bypass.test.d.ts.map +1 -0
  170. package/dist/tests/security/gatekeeper-bypass.test.js +167 -0
  171. package/dist/tests/security/gatekeeper-bypass.test.js.map +1 -0
  172. package/dist/tests/security/paste-host-fetch.test.d.ts +2 -0
  173. package/dist/tests/security/paste-host-fetch.test.d.ts.map +1 -0
  174. package/dist/tests/security/paste-host-fetch.test.js +199 -0
  175. package/dist/tests/security/paste-host-fetch.test.js.map +1 -0
  176. package/dist/tests/security/scanner-regression-guard.test.js +5 -0
  177. package/dist/tests/security/scanner-regression-guard.test.js.map +1 -1
  178. package/dist/tests/services/aidefence-feedback.test.js +5 -0
  179. package/dist/tests/services/aidefence-feedback.test.js.map +1 -1
  180. package/dist/tests/services/bundled-sibling-scan.test.js +171 -10
  181. package/dist/tests/services/bundled-sibling-scan.test.js.map +1 -1
  182. package/dist/tests/skill-scanner/allowlist.test.js +5 -0
  183. package/dist/tests/skill-scanner/allowlist.test.js.map +1 -1
  184. package/package.json +1 -1
@@ -7,10 +7,16 @@
7
7
  * Unicode-concealment attacks the prod edge gate currently scores at ~1 point:
8
8
  *
9
9
  * • code_execution — a skill instructing a remote fetch piped into an
10
- * interpreter (curl|bash and friends). Emits ONE medium finding (score 12,
11
- * sub-threshold) on its own; escalated to critical (score 40, quarantines)
12
- * only when it co-occurs with a NON-documentation exfiltration / privilege /
13
- * credential-path / obfuscation signal. The non-doc gate keeps legitimate
10
+ * interpreter (curl|bash and friends), or SMI-6033 Wave 4, Gap 1 — the
11
+ * same instruction written as free-text prose with no shell syntax at all
12
+ * ("download the installer from thisurl.com and run it",
13
+ * `IMPERATIVE_FETCH_EXEC_PROSE` in patterns.exec.ts). Emits ONE medium
14
+ * finding (score 12, sub-threshold) on its own, whichever pattern set
15
+ * fired; escalated to critical (score 40, quarantines) only when it
16
+ * co-occurs with a NON-documentation exfiltration / privilege /
17
+ * credential-path / obfuscation signal, OR — SMI-6033 Wave 4, Gap 6 — with
18
+ * TWO DISTINCT high-confidence advisory-tier signals
19
+ * (`CO_SIGNAL_MIN_SEVERITY` below). The non-doc gate keeps legitimate
14
20
  * security-research skills (which document these techniques inside fenced
15
21
  * examples) below the threshold.
16
22
  *
@@ -30,7 +36,10 @@
30
36
  * representation) is an attack even inside a fenced block — there is no
31
37
  * legitimate reason to ship invisible/homoglyph-spliced directives.
32
38
  */
33
- import { CODE_EXECUTION_PATTERNS } from './patterns.js';
39
+ // SMI-6033 Wave 4 (Gap 1): both code_execution pattern sets now live in the
40
+ // patterns.exec.ts sibling (patterns.ts re-exports them unchanged); imported
41
+ // from their source of truth here.
42
+ import { CODE_EXECUTION_PATTERNS, IMPERATIVE_FETCH_EXEC_PROSE } from './patterns.exec.js';
34
43
  import { safeRegexTest, safeRegexCheck } from './regex-utils.js';
35
44
  import { analyzeMarkdownContext, isDocumentationContext } from './SecurityScanner.helpers.js';
36
45
  // SMI-595: confusable/homoglyph primitives extracted to a standalone module
@@ -81,32 +90,53 @@ const OBFUSCATION_DIRECTIVE_PATTERN = /(?:ignore|disregard|forget)\s+(?:all\s+|t
81
90
  // Detectors
82
91
  // ============================================================================
83
92
  /**
84
- * code_execution: remote fetch piped into an interpreter. Single-emission — at
85
- * most one MEDIUM finding per skill (the first match). escalateCodeExecution()
93
+ * code_execution: remote fetch piped into an interpreter (literal shell
94
+ * syntax), OR SMI-6033 Wave 4, Gap 1 — the same instruction expressed as a
95
+ * natural-language install-and-run imperative with no shell syntax at all.
96
+ *
97
+ * Single-emission — at most one MEDIUM finding per skill (the first match, in
98
+ * line order), whichever pattern set produced it. escalateCodeExecution()
86
99
  * promotes it to CRITICAL on co-occurrence.
100
+ *
101
+ * Ordering is deliberately line-major (each line is tested against the literal
102
+ * syntax set first, then the prose set, before moving to the next line), so a
103
+ * document containing only literal-syntax matches produces byte-identical
104
+ * output to the pre-Gap-1 detector — same line, same message, same severity.
105
+ * The message names WHICH set fired so a reviewer can tell a prose-triggered
106
+ * finding from a syntax-triggered one at a glance.
87
107
  */
88
108
  export function scanCodeExecution(content, lineContexts) {
89
109
  const lines = content.split('\n');
90
110
  const contexts = lineContexts ?? analyzeMarkdownContext(content);
111
+ const emit = (i, line, matched, prefix) => {
112
+ const ctx = contexts[i];
113
+ const inDocContext = ctx ? isDocumentationContext(ctx) : false;
114
+ return [
115
+ {
116
+ type: 'code_execution',
117
+ severity: 'medium',
118
+ message: `${prefix}: "${matched.slice(0, 60)}${matched.length > 60 ? '...' : ''}"`,
119
+ location: line.trim().slice(0, 100),
120
+ lineNumber: i + 1,
121
+ category: 'code_execution',
122
+ inDocumentationContext: inDocContext,
123
+ confidence: 'high',
124
+ },
125
+ ];
126
+ };
91
127
  for (let i = 0; i < lines.length; i++) {
92
128
  const line = lines[i];
93
129
  for (const pattern of CODE_EXECUTION_PATTERNS) {
130
+ const match = safeRegexTest(pattern, line);
131
+ if (match)
132
+ return emit(i, line, match[0], 'Remote fetch piped to an interpreter');
133
+ }
134
+ // SMI-6033 Wave 4 (Gap 1): same finding type, same medium/advisory tier —
135
+ // only the evidence shape differs (free-text imperative, no shell syntax).
136
+ for (const pattern of IMPERATIVE_FETCH_EXEC_PROSE) {
94
137
  const match = safeRegexTest(pattern, line);
95
138
  if (match) {
96
- const ctx = contexts[i];
97
- const inDocContext = ctx ? isDocumentationContext(ctx) : false;
98
- return [
99
- {
100
- type: 'code_execution',
101
- severity: 'medium',
102
- message: `Remote fetch piped to an interpreter: "${match[0].slice(0, 60)}${match[0].length > 60 ? '...' : ''}"`,
103
- location: line.trim().slice(0, 100),
104
- lineNumber: i + 1,
105
- category: 'code_execution',
106
- inDocumentationContext: inDocContext,
107
- confidence: 'high',
108
- },
109
- ];
139
+ return emit(i, line, match[0], 'Natural-language instruction to fetch a remote file and execute it');
110
140
  }
111
141
  }
112
142
  }
@@ -159,35 +189,69 @@ export function scanObfuscatedDirective(content) {
159
189
  return [];
160
190
  }
161
191
  /**
162
- * Co-occurrence types that turn a remote-fetch-to-interpreter from "suspicious"
163
- * (medium) into "supply-chain execution" (critical).
192
+ * SMI-6033 Wave 4 (Gap 6): per-type MINIMUM co-signal severity, replacing the
193
+ * flat `CODE_EXECUTION_CO_OCCURRENCE` set. Equality-parity-pinned against its
194
+ * edge twin (security-scanner-edge.exec.ts) — identical keys AND identical
195
+ * values, not a superset; see the co-signal parity test in
196
+ * scripts/tests/indexer/security-scanner-edge.co-signal-escalation.test.ts.
197
+ *
198
+ * A `'high'` minimum means "one such co-signal escalates on its own" (path a
199
+ * below — today's behavior, byte-identical for the original four types). A
200
+ * `'medium'` minimum means "advisory tier: never sufficient alone, but two
201
+ * DISTINCT such types together escalate" (path b). A type absent from this
202
+ * map is not a co-signal at all.
164
203
  */
165
- const CODE_EXECUTION_CO_OCCURRENCE = new Set([
166
- 'data_exfiltration',
167
- 'privilege_escalation',
168
- 'sensitive_path',
169
- 'obfuscated_directive',
170
- ]);
204
+ const CO_SIGNAL_MIN_SEVERITY = {
205
+ // Existing four — behavior byte-identical to today, min 'high'.
206
+ data_exfiltration: 'high',
207
+ privilege_escalation: 'high',
208
+ sensitive_path: 'high',
209
+ obfuscated_directive: 'high',
210
+ // New ClawHavoc advisory-tier categories — eligible at 'medium'.
211
+ decoy_misdirection: 'medium',
212
+ archive_evasion: 'medium',
213
+ paste_host_fetch: 'medium',
214
+ gatekeeper_bypass: 'medium', // its correlated/critical form already quarantines on its own
215
+ };
216
+ /** Ordinal ranking so "at or above its type's minimum" is a single comparison. */
217
+ const SEVERITY_RANK = { low: 0, medium: 1, high: 2, critical: 3 };
171
218
  /**
172
219
  * Maximum line distance between the `code_execution` finding and the co-signal
173
220
  * that escalates it (SMI-5880). Before this bound, a co-signal ANYWHERE in the
174
221
  * document — any distance, any unrelated section — escalated the single
175
222
  * `code_execution` finding to critical.
176
223
  *
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.
224
+ * Why a bounded window is NOT a bypass vector FOR PATH (a): every `'high'`-
225
+ * minimum co-signal is itself `high` or `critical`, and `scan()`
226
+ * (SecurityScanner.ts) computes `passed = !hasCritical && !hasHigh &&
227
+ * !exceedsThreshold` across ALL findings with no locality filter and no
228
+ * documentation filter. A qualifying path-(a) co-signal anywhere therefore
229
+ * forces `passed === false` on its own, BEFORE this function runs. Splitting
230
+ * the two signals to duck this window cannot turn a failing scan into a
231
+ * passing one; it changes only this finding's own label (medium vs critical)
232
+ * and the risk-score magnitude of a scan that already blocks.
185
233
  * Hostile-update detection is likewise untouched: `detectSupplyChain()`
186
234
  * (SecurityScanner.hostile-update.ts) pairs a NEW non-doc `code_execution` of ANY
187
235
  * severity with a NEW non-doc high/critical co-signal at ANY distance, so a
188
236
  * far-apart rug-pull still returns the `hostile` verdict and its supply-chain
189
237
  * reason string.
190
238
  *
239
+ * SMI-6033 Wave 4 (Gap 6) — that argument does NOT carry over to path (b),
240
+ * and the difference is deliberate, not an oversight. Path (b)'s co-signals
241
+ * are `medium` advisory findings that do NOT independently trip `passed`, so
242
+ * an attacker who spreads the weak `code_execution` finding and its two
243
+ * advisory co-signals more than 40 lines apart genuinely does keep a scan
244
+ * under the bar (12 + 12 + 12 = 36 < the 40 threshold) that would otherwise
245
+ * have escalated to critical. This is the accepted cost of bounding locality
246
+ * at all: the alternative — an unbounded window for fuzzy heuristic signals —
247
+ * re-creates the exact SMI-5880 defect (an unrelated advisory finding in a
248
+ * different section escalating an unrelated `code_execution` finding) with a
249
+ * far higher false-positive rate than the high-tier co-signals ever had,
250
+ * because these signals are approximate by construction. The residual is
251
+ * bounded: the attack still requires the two advisory signals to be real
252
+ * detections in the same document, and `detectSupplyChain()`'s
253
+ * distance-independent rug-pull check above is unaffected.
254
+ *
191
255
  * Deliberately the same 40 as the sibling corroboration mechanism's locality
192
256
  * constant (SecurityScanner.evidence.ts) so the scanner has ONE locality model
193
257
  * — but kept as its own constant, not imported: the two mechanisms are
@@ -205,11 +269,13 @@ const MAX_CODE_EXECUTION_CO_SIGNAL_LINE_DISTANCE = 40;
205
269
  * that pair rather than suppressing the co-signal.
206
270
  *
207
271
  * Unreachable through `scan()` today: `scanCodeExecution` above and every emitter
208
- * of the four CODE_EXECUTION_CO_OCCURRENCE types set `lineNumber` unconditionally
272
+ * of the eight `CO_SIGNAL_MIN_SEVERITY` types set `lineNumber` unconditionally
209
273
  * (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.
274
+ * scanners module, the chmod-compound and Gatekeeper-bypass detectors,
275
+ * `scanObfuscatedDirective` above, and SMI-6033 the archive-evasion,
276
+ * paste-host and decoy-misdirection detectors). Kept as an explicit,
277
+ * directly-tested branch so a future detector that forgets `lineNumber`
278
+ * degrades safe instead of silently ceasing to escalate.
213
279
  */
214
280
  function isWithinCoSignalWindow(codeExecLine, coSignalLine) {
215
281
  if (typeof codeExecLine !== 'number' || typeof coSignalLine !== 'number')
@@ -217,31 +283,120 @@ function isWithinCoSignalWindow(codeExecLine, coSignalLine) {
217
283
  return Math.abs(codeExecLine - coSignalLine) <= MAX_CODE_EXECUTION_CO_SIGNAL_LINE_DISTANCE;
218
284
  }
219
285
  /**
220
- * Escalate the code_execution finding to CRITICAL when a NON-documentation
221
- * high/critical exfiltration / privilege / credential-path / obfuscation signal
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
226
- * non-documentation keeps legitimate security-research skills (whose examples
227
- * live in fenced blocks) at MEDIUM.
286
+ * Escalate the code_execution finding (literal `curl|bash` syntax OR a Gap-1
287
+ * `IMPERATIVE_FETCH_EXEC_PROSE` match same finding type either way) to
288
+ * CRITICAL, on either of two paths. Both require: the `code_execution`
289
+ * finding ITSELF to be non-documentation (see the adversarial-review fix
290
+ * note below), the co-signal to be NON-documentation, and the co-signal to be
291
+ * WITHIN `MAX_CODE_EXECUTION_CO_SIGNAL_LINE_DISTANCE` lines (SMI-5880
292
+ * previously any distance qualified, so an unrelated finding in a different
293
+ * section of a long document escalated an unrelated code_execution finding).
294
+ * Mutates the finding in place.
295
+ *
296
+ * Path (a) — ONE co-signal whose type's `CO_SIGNAL_MIN_SEVERITY` minimum is
297
+ * `'high'`, at `high` or `critical`. Byte-identical to the pre-SMI-6033
298
+ * behavior for the original four types (`data_exfiltration`,
299
+ * `privilege_escalation`, `sensitive_path`, `obfuscated_directive`) EXCEPT
300
+ * for the `codeExec`-own-doc-context fix below (see that note — the
301
+ * pre-existing behavior there was a real gap, not an intentional design):
302
+ * same co-signal membership test, same severity test, same locality gate,
303
+ * same message. Deliberately NO confidence requirement on the CO-SIGNAL here
304
+ * — adding one would change existing behavior, which the Gap 6 design
305
+ * explicitly forbids.
306
+ *
307
+ * Path (b) — SMI-6033 Wave 4 (Gap 6), only evaluated when path (a) did not
308
+ * fire: at least TWO DISTINCT `'medium'`-minimum types (two findings of the
309
+ * SAME type do not count), each at or above its own minimum. A single fuzzy
310
+ * medium signal (one decoy mismatch alone, one transfer.sh link alone) can
311
+ * therefore never flip a legitimate vendor `curl|bash` to critical.
312
+ *
313
+ * Confidence requirement (revised after adversarial review, 2026-08-16 —
314
+ * see `docs/internal/code_review/2026-08-15-smi6033-wave4-escalation-model.md`
315
+ * for the full account): the plan's literal text requires `confidence:
316
+ * 'high'` on both path-(b) co-signals. An earlier revision of this file
317
+ * relaxed that to `confidence !== 'low'` GLOBALLY, reasoning that
318
+ * `paste_host_fetch`'s ANON/TRANSIENT medium form is ALWAYS
319
+ * `confidence: 'medium'` by construction (`SecurityScanner.paste-host.ts`)
320
+ * and would otherwise be permanently unable to satisfy path (b) despite
321
+ * being a registered-eligible type. That reasoning was correct for
322
+ * `paste_host_fetch` specifically, but the GLOBAL relaxation was wrong: it
323
+ * also admitted `archive_evasion`'s prose-only co-occurrence sub-signal
324
+ * (explicitly documented in that file's own header as "the fuzzy, FP-prone
325
+ * case" and capped at `confidence: 'medium'` for exactly that reason) and
326
+ * `decoy_misdirection`'s no-authority-affix form (also `confidence:
327
+ * 'medium'`) — an adversarial review constructed a working false-positive
328
+ * example combining exactly those two fuzzy medium-confidence signals (a
329
+ * mundane "distributed from our company site" skill blurb plus an unrelated
330
+ * "supplied as a zip archive... ask your administrator for the password"
331
+ * sentence) with a real vendor `curl|bash`, and it escalated to a hard
332
+ * quarantine. Fixed by narrowing the confidence carve-out to ONLY
333
+ * `paste_host_fetch` (`CO_SIGNAL_MEDIUM_CONFIDENCE_EXCEPTION` below) — every
334
+ * other medium-minimum type reverts to requiring `confidence: 'high'`,
335
+ * matching the plan's literal text. `gatekeeper_bypass` loses nothing by
336
+ * this (its own detector always emits `confidence: 'high'` regardless of
337
+ * severity); `archive_evasion` and `decoy_misdirection` now only participate
338
+ * via their own higher-confidence forms (CLI-syntax inline-secret-adjacent
339
+ * password mentions; an authority-affix-boosted brand claim), which is
340
+ * exactly the "not noise" signal path (b) was meant to require. See the
341
+ * co-signal escalation tests for the pinned behavior of this decision.
342
+ *
343
+ * `codeExec`-own-doc-context fix (same adversarial review): neither path
344
+ * previously checked `codeExec.inDocumentationContext` itself — only the
345
+ * CO-SIGNAL's doc-context was checked. A `code_execution` finding inside a
346
+ * fenced security-research example could therefore still be escalated by
347
+ * two genuine (non-doc) co-signals elsewhere in the document, contradicting
348
+ * this function's own historical doc comments claiming fenced examples stay
349
+ * at MEDIUM. This gap predates Wave 4 (path (a) had it too) but Wave 4's new
350
+ * path (b) co-signal types meaningfully widen how often it's reachable, so
351
+ * it's fixed here for both paths rather than left in place for path (a)
352
+ * alone.
228
353
  *
229
354
  * Runs BEFORE the sibling corroboration mechanism in `scan()` — see that
230
355
  * function's doc comment (SecurityScanner.evidence.ts) for why the ordering is
231
356
  * safe in both directions.
232
357
  */
358
+ const CO_SIGNAL_MEDIUM_CONFIDENCE_EXCEPTION = new Set([
359
+ 'paste_host_fetch',
360
+ ]);
233
361
  export function escalateCodeExecution(findings) {
234
362
  const codeExec = findings.find((f) => f.type === 'code_execution');
235
363
  if (!codeExec)
236
364
  return;
237
- const hasDangerousCoSignal = findings.some((f) => f !== codeExec &&
238
- CODE_EXECUTION_CO_OCCURRENCE.has(f.type) &&
365
+ if (codeExec.inDocumentationContext === true)
366
+ return;
367
+ const eligible = findings.filter((f) => f !== codeExec &&
368
+ CO_SIGNAL_MIN_SEVERITY[f.type] !== undefined &&
239
369
  f.inDocumentationContext !== true &&
240
- (f.severity === 'high' || f.severity === 'critical') &&
241
370
  isWithinCoSignalWindow(codeExec.lineNumber, f.lineNumber));
371
+ // Path (a): one high-minimum co-signal at high/critical.
372
+ const hasDangerousCoSignal = eligible.some((f) => CO_SIGNAL_MIN_SEVERITY[f.type] === 'high' &&
373
+ (f.severity === 'high' || f.severity === 'critical'));
242
374
  if (hasDangerousCoSignal) {
243
375
  codeExec.severity = 'critical';
244
376
  codeExec.message = `Remote fetch piped to an interpreter, co-occurring with exfiltration/privilege/credential signals — likely supply-chain execution. ${codeExec.message}`;
377
+ return;
378
+ }
379
+ // Path (b): two DISTINCT medium-minimum types, each confidence 'high' —
380
+ // except paste_host_fetch, whose medium form is always confidence:'medium'
381
+ // by construction (see this function's doc comment for the full rationale
382
+ // and the adversarial-review finding that narrowed this from a blanket
383
+ // `!== 'low'` relaxation).
384
+ const advisoryTypes = new Set(eligible
385
+ .filter((f) => {
386
+ const min = CO_SIGNAL_MIN_SEVERITY[f.type];
387
+ const confidenceOk = CO_SIGNAL_MEDIUM_CONFIDENCE_EXCEPTION.has(f.type)
388
+ ? f.confidence !== 'low'
389
+ : f.confidence === 'high';
390
+ return min === 'medium' && SEVERITY_RANK[f.severity] >= SEVERITY_RANK[min] && confidenceOk;
391
+ })
392
+ .map((f) => f.type));
393
+ if (advisoryTypes.size >= 2) {
394
+ codeExec.severity = 'critical';
395
+ codeExec.message = `Remote fetch/execute instruction corroborated by two independent advisory-tier signals (${[
396
+ ...advisoryTypes,
397
+ ]
398
+ .sort()
399
+ .join(', ')}) — likely supply-chain execution. ${codeExec.message}`;
245
400
  }
246
401
  }
247
402
  //# sourceMappingURL=SecurityScanner.exec.js.map
@@ -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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"}
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Security Scanner — shared fetch-correlation utilities
3
+ * @module @skillsmith/core/security/scanner/SecurityScanner.fetch-correlation
4
+ *
5
+ * SMI-6033 Wave 2: extracted out of SecurityScanner.compound.ts so every
6
+ * provenance-conditioned detector (chmod, xattr/gatekeeper_bypass,
7
+ * archive_evasion, paste_host_fetch) correlates a target file against a fetch
8
+ * command's download destination using the SAME fetch-verb regex and the SAME
9
+ * distance-independent matching logic, instead of drifting copies. Pure
10
+ * functions of their arguments — no scanner instance state. Byte-identical
11
+ * structure is mirrored in the edge twin
12
+ * (security-scanner-edge.fetch-correlation.ts) — see that file's header for
13
+ * the parity-test pointer.
14
+ *
15
+ * SMI-6033 Wave 3 (adversarial-review fix): correlation is now
16
+ * DIRECTORY-PATH-AWARE. It previously matched on the final path segment
17
+ * (basename) alone, with zero directory awareness — so two entirely unrelated
18
+ * files that merely shared a filename registered as "correlated": a
19
+ * legitimate `curl -o /tmp/install.sh <url>` fetch alongside a separate,
20
+ * pre-existing `./vendor/other-tool/install.sh` that gets `chmod +x`'d
21
+ * elsewhere in the same skill. Because this utility IS the shared provenance
22
+ * gate behind the xattr / archive / paste-host "correlated ->
23
+ * standalone-critical" escalations, that false positive is a
24
+ * quarantine-level one, not a cosmetic one.
25
+ *
26
+ * The rule, in one sentence: when BOTH sides carry directory information the
27
+ * normalized directories must be EQUAL; when EITHER side is a bare filename
28
+ * (no directory information at all — `wget https://x/install.sh` writes into
29
+ * the CWD, `chmod +x install.sh` names no directory) fall back to
30
+ * final-segment matching, because there is then no path information to
31
+ * distinguish the two by. Normalization (`normalizeCorrelationPath`) strips
32
+ * quotes, collapses empty segments, drops `.` and resolves `..`, so
33
+ * `./vendor/tool/x` and `vendor/tool/x` are the same directory while `/tmp`
34
+ * and `vendor/tool` are not. An absolute path with no intermediate directory
35
+ * (`/install.sh`) normalizes to the real directory `/` — NOT to "no
36
+ * directory information" — so it does not silently fall back.
37
+ */
38
+ /**
39
+ * Matches an actual fetch COMMAND (curl/wget/git-clone, or npx immediately
40
+ * followed by a URL) — not bare prose tokens (`# downloaded`, `See
41
+ * https://…`, `npx tool init`) which false-fire when used as a correlation
42
+ * gate on their own. Generic name (not chmod-specific, formerly
43
+ * `CHMOD_FETCH_CONTEXT`): every provenance-conditioned detector that needs to
44
+ * know whether a line issues a fetch reuses this single pattern.
45
+ */
46
+ export declare const FETCH_COMMAND_PATTERN: RegExp;
47
+ /**
48
+ * Escape a string for safe interpolation into a `new RegExp(...)` pattern.
49
+ */
50
+ export declare function escapeRegExp(s: string): string;
51
+ /**
52
+ * A path split into its normalized directory component and its final segment.
53
+ * `dir` is the empty string ONLY when the raw path carried no directory
54
+ * information at all (a bare filename, or a `./`-prefixed one, which names
55
+ * the same CWD-relative location) — that empty string is what triggers the
56
+ * basename-only fallback in `isCorrelatedWithFetchDestination`.
57
+ */
58
+ export interface NormalizedCorrelationPath {
59
+ dir: string;
60
+ base: string;
61
+ }
62
+ /**
63
+ * Normalize a raw path captured out of a shell command into
64
+ * `{ dir, base }`: strip quotes, drop empty (`//`) and `.` segments, resolve
65
+ * `..` lexically, and preserve absolute-vs-relative as a real distinction
66
+ * (`/tmp/x` and `tmp/x` are different locations, so they must not correlate).
67
+ * Lexical only — the scanner never touches the filesystem, so there is no
68
+ * symlink/realpath resolution to do and none is implied.
69
+ */
70
+ export declare function normalizeCorrelationPath(raw: string): NormalizedCorrelationPath;
71
+ /**
72
+ * The normalized final segment of `rawPath`. Callers use this for their own
73
+ * minimum-length gate (every current caller requires >= 3 chars) before
74
+ * handing the FULL path to `isCorrelatedWithFetchDestination` — the length
75
+ * policy stays a caller decision, exactly as it was before this file became
76
+ * path-aware.
77
+ */
78
+ export declare function correlationTargetBasename(rawPath: string): string;
79
+ /**
80
+ * SMI-5431: the IMPLICIT download destination of a fetch command — the file
81
+ * written with NO explicit -o/-O/--output<space>/> redirect: `wget <url>` (no
82
+ * -O/-o) → URL last segment; `git clone <url>` → repo dir (minus `.git`);
83
+ * `curl --output=<file>` (equals form, missed by the explicit regex). A bare
84
+ * `curl <url>` GET writes to STDOUT → '' (never correlates). ReDoS-safe.
85
+ *
86
+ * SMI-6033 Wave 3: returns the destination PATH, not a bare basename — the
87
+ * `curl --output=/tmp/p` form carries a real directory the correlation check
88
+ * now compares against. The wget / git-clone forms genuinely have no
89
+ * directory component (both write into the CWD), so they still yield a bare
90
+ * final segment; that is a property of those commands, not discarded
91
+ * information.
92
+ */
93
+ export declare function implicitDownloadDestination(line: string): string;
94
+ /**
95
+ * Distance-independent correlation: is `targetPath` (the raw path captured
96
+ * from the caller's own command — quotes and directory prefix INCLUDED; this
97
+ * function normalizes) the DOWNLOAD DESTINATION of any fetch command line,
98
+ * anywhere in the document (not just physically adjacent)? Covers explicit
99
+ * destinations (-o/-O/--output<space>/>, with an optional leading path) via
100
+ * regex, and implicit destinations (wget/git-clone/curl --output=) via
101
+ * `implicitDownloadDestination`. Anchored on the destination, NOT
102
+ * basename-anywhere, so a URL path/query/header value (a known FP class) and
103
+ * a bare `curl <url>` GET (writes to stdout) never correlate.
104
+ *
105
+ * Final segments must always match; directories must additionally match
106
+ * whenever BOTH sides carry one (see this file's header for the full rule and
107
+ * the FP class it closes).
108
+ *
109
+ * Callers gate the minimum basename length themselves before calling (every
110
+ * current caller requires >= 3 chars, via `correlationTargetBasename`) — this
111
+ * function imposes no minimum, so a future caller's own length policy stays a
112
+ * caller decision.
113
+ */
114
+ export declare function isCorrelatedWithFetchDestination(targetPath: string, fetchLines: readonly string[]): boolean;
115
+ //# sourceMappingURL=SecurityScanner.fetch-correlation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SecurityScanner.fetch-correlation.d.ts","sourceRoot":"","sources":["../../../../src/security/scanner/SecurityScanner.fetch-correlation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,QACkC,CAAA;AAEpE;;GAEG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED;;;;;;GAMG;AACH,MAAM,WAAW,yBAAyB;IACxC,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;CACb;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,yBAAyB,CAgB/E;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEjE;AAeD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAehE;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,gCAAgC,CAC9C,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,SAAS,MAAM,EAAE,GAC5B,OAAO,CAoBT"}