@skillsmith/core 0.11.7 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (192) hide show
  1. package/CHANGELOG.md +210 -0
  2. package/README.md +7 -8
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/src/api/client.types.d.ts +9 -0
  5. package/dist/src/api/client.types.d.ts.map +1 -1
  6. package/dist/src/api/schemas.d.ts +6 -0
  7. package/dist/src/api/schemas.d.ts.map +1 -1
  8. package/dist/src/api/schemas.js +5 -0
  9. package/dist/src/api/schemas.js.map +1 -1
  10. package/dist/src/api/security-summary.d.ts +10 -1
  11. package/dist/src/api/security-summary.d.ts.map +1 -1
  12. package/dist/src/api/security-summary.js +9 -0
  13. package/dist/src/api/security-summary.js.map +1 -1
  14. package/dist/src/api/security-summary.test.js +79 -1
  15. package/dist/src/api/security-summary.test.js.map +1 -1
  16. package/dist/src/api/types.d.ts +9 -0
  17. package/dist/src/api/types.d.ts.map +1 -1
  18. package/dist/src/config/token-credentials.d.ts +20 -0
  19. package/dist/src/config/token-credentials.d.ts.map +1 -1
  20. package/dist/src/config/token-credentials.js +59 -0
  21. package/dist/src/config/token-credentials.js.map +1 -1
  22. package/dist/src/config/token-credentials.test.d.ts +3 -0
  23. package/dist/src/config/token-credentials.test.d.ts.map +1 -1
  24. package/dist/src/config/token-credentials.test.js +68 -0
  25. package/dist/src/config/token-credentials.test.js.map +1 -1
  26. package/dist/src/exports/services.d.ts +1 -1
  27. package/dist/src/exports/services.d.ts.map +1 -1
  28. package/dist/src/exports/services.js +3 -1
  29. package/dist/src/exports/services.js.map +1 -1
  30. package/dist/src/index.d.ts +3 -3
  31. package/dist/src/index.d.ts.map +1 -1
  32. package/dist/src/index.js +3 -3
  33. package/dist/src/index.js.map +1 -1
  34. package/dist/src/install/agent-config-merge.json-array.d.ts +8 -0
  35. package/dist/src/install/agent-config-merge.json-array.d.ts.map +1 -1
  36. package/dist/src/install/agent-config-merge.json-array.js +22 -2
  37. package/dist/src/install/agent-config-merge.json-array.js.map +1 -1
  38. package/dist/src/install/agent-pack-installer.cursor-hooks.d.ts.map +1 -1
  39. package/dist/src/install/agent-pack-installer.cursor-hooks.js +122 -3
  40. package/dist/src/install/agent-pack-installer.cursor-hooks.js.map +1 -1
  41. package/dist/src/install/agent-pack-installer.cursor-hooks.test.js +146 -1
  42. package/dist/src/install/agent-pack-installer.cursor-hooks.test.js.map +1 -1
  43. package/dist/src/install/agent-pack-installer.harness.d.ts +8 -0
  44. package/dist/src/install/agent-pack-installer.harness.d.ts.map +1 -1
  45. package/dist/src/install/agent-pack-installer.harness.js +8 -1
  46. package/dist/src/install/agent-pack-installer.harness.js.map +1 -1
  47. package/dist/src/scripts/__tests__/scan-imported-skills.test.js +25 -0
  48. package/dist/src/scripts/__tests__/scan-imported-skills.test.js.map +1 -1
  49. package/dist/src/scripts/skill-scanner/trust-scorer.js +1 -1
  50. package/dist/src/scripts/skill-scanner/trust-scorer.js.map +1 -1
  51. package/dist/src/security/scanner/SecurityScanner.archive.d.ts +34 -0
  52. package/dist/src/security/scanner/SecurityScanner.archive.d.ts.map +1 -0
  53. package/dist/src/security/scanner/SecurityScanner.archive.js +218 -0
  54. package/dist/src/security/scanner/SecurityScanner.archive.js.map +1 -0
  55. package/dist/src/security/scanner/SecurityScanner.compound.d.ts +1 -0
  56. package/dist/src/security/scanner/SecurityScanner.compound.d.ts.map +1 -1
  57. package/dist/src/security/scanner/SecurityScanner.compound.js +117 -38
  58. package/dist/src/security/scanner/SecurityScanner.compound.js.map +1 -1
  59. package/dist/src/security/scanner/SecurityScanner.d.ts +41 -4
  60. package/dist/src/security/scanner/SecurityScanner.d.ts.map +1 -1
  61. package/dist/src/security/scanner/SecurityScanner.decoy.d.ts +72 -0
  62. package/dist/src/security/scanner/SecurityScanner.decoy.d.ts.map +1 -0
  63. package/dist/src/security/scanner/SecurityScanner.decoy.js +265 -0
  64. package/dist/src/security/scanner/SecurityScanner.decoy.js.map +1 -0
  65. package/dist/src/security/scanner/SecurityScanner.encoding.d.ts +56 -0
  66. package/dist/src/security/scanner/SecurityScanner.encoding.d.ts.map +1 -0
  67. package/dist/src/security/scanner/SecurityScanner.encoding.js +270 -0
  68. package/dist/src/security/scanner/SecurityScanner.encoding.js.map +1 -0
  69. package/dist/src/security/scanner/SecurityScanner.exec.d.ts +23 -20
  70. package/dist/src/security/scanner/SecurityScanner.exec.d.ts.map +1 -1
  71. package/dist/src/security/scanner/SecurityScanner.exec.js +207 -52
  72. package/dist/src/security/scanner/SecurityScanner.exec.js.map +1 -1
  73. package/dist/src/security/scanner/SecurityScanner.fetch-correlation.d.ts +115 -0
  74. package/dist/src/security/scanner/SecurityScanner.fetch-correlation.d.ts.map +1 -0
  75. package/dist/src/security/scanner/SecurityScanner.fetch-correlation.js +181 -0
  76. package/dist/src/security/scanner/SecurityScanner.fetch-correlation.js.map +1 -0
  77. package/dist/src/security/scanner/SecurityScanner.helpers.d.ts +14 -11
  78. package/dist/src/security/scanner/SecurityScanner.helpers.d.ts.map +1 -1
  79. package/dist/src/security/scanner/SecurityScanner.helpers.js +26 -124
  80. package/dist/src/security/scanner/SecurityScanner.helpers.js.map +1 -1
  81. package/dist/src/security/scanner/SecurityScanner.js +114 -36
  82. package/dist/src/security/scanner/SecurityScanner.js.map +1 -1
  83. package/dist/src/security/scanner/SecurityScanner.paste-host.d.ts +69 -0
  84. package/dist/src/security/scanner/SecurityScanner.paste-host.d.ts.map +1 -0
  85. package/dist/src/security/scanner/SecurityScanner.paste-host.js +342 -0
  86. package/dist/src/security/scanner/SecurityScanner.paste-host.js.map +1 -0
  87. package/dist/src/security/scanner/SecurityScanner.risk-score.d.ts +27 -0
  88. package/dist/src/security/scanner/SecurityScanner.risk-score.d.ts.map +1 -0
  89. package/dist/src/security/scanner/SecurityScanner.risk-score.js +178 -0
  90. package/dist/src/security/scanner/SecurityScanner.risk-score.js.map +1 -0
  91. package/dist/src/security/scanner/SecurityScanner.urls.d.ts +20 -0
  92. package/dist/src/security/scanner/SecurityScanner.urls.d.ts.map +1 -0
  93. package/dist/src/security/scanner/SecurityScanner.urls.js +26 -0
  94. package/dist/src/security/scanner/SecurityScanner.urls.js.map +1 -0
  95. package/dist/src/security/scanner/index.d.ts +2 -2
  96. package/dist/src/security/scanner/index.d.ts.map +1 -1
  97. package/dist/src/security/scanner/index.js +5 -1
  98. package/dist/src/security/scanner/index.js.map +1 -1
  99. package/dist/src/security/scanner/multiline-category-closure.test.js +6 -0
  100. package/dist/src/security/scanner/multiline-category-closure.test.js.map +1 -1
  101. package/dist/src/security/scanner/patterns.d.ts +41 -21
  102. package/dist/src/security/scanner/patterns.d.ts.map +1 -1
  103. package/dist/src/security/scanner/patterns.exec.d.ts +89 -0
  104. package/dist/src/security/scanner/patterns.exec.d.ts.map +1 -0
  105. package/dist/src/security/scanner/patterns.exec.js +115 -0
  106. package/dist/src/security/scanner/patterns.exec.js.map +1 -0
  107. package/dist/src/security/scanner/patterns.js +78 -41
  108. package/dist/src/security/scanner/patterns.js.map +1 -1
  109. package/dist/src/security/scanner/types.d.ts +15 -1
  110. package/dist/src/security/scanner/types.d.ts.map +1 -1
  111. package/dist/src/security/scanner/typosquat.d.ts +10 -0
  112. package/dist/src/security/scanner/typosquat.d.ts.map +1 -1
  113. package/dist/src/security/scanner/typosquat.js +5 -1
  114. package/dist/src/security/scanner/typosquat.js.map +1 -1
  115. package/dist/src/security/scanner/weights.d.ts +12 -3
  116. package/dist/src/security/scanner/weights.d.ts.map +1 -1
  117. package/dist/src/security/scanner/weights.js +50 -1
  118. package/dist/src/security/scanner/weights.js.map +1 -1
  119. package/dist/src/services/agent-pack/prompt-source.js +1 -1
  120. package/dist/src/services/agent-pack/prompt-source.js.map +1 -1
  121. package/dist/src/services/bundled-sibling-scan.d.ts +87 -13
  122. package/dist/src/services/bundled-sibling-scan.d.ts.map +1 -1
  123. package/dist/src/services/bundled-sibling-scan.js +177 -33
  124. package/dist/src/services/bundled-sibling-scan.js.map +1 -1
  125. package/dist/src/services/recommend-guard.d.ts.map +1 -1
  126. package/dist/src/services/recommend-guard.js +8 -1
  127. package/dist/src/services/recommend-guard.js.map +1 -1
  128. package/dist/src/services/recommend-guard.test.js +6 -2
  129. package/dist/src/services/recommend-guard.test.js.map +1 -1
  130. package/dist/src/sync/index.d.ts +1 -0
  131. package/dist/src/sync/index.d.ts.map +1 -1
  132. package/dist/src/sync/index.js +2 -0
  133. package/dist/src/sync/index.js.map +1 -1
  134. package/dist/src/sync/license-status-client.d.ts +56 -0
  135. package/dist/src/sync/license-status-client.d.ts.map +1 -0
  136. package/dist/src/sync/license-status-client.js +118 -0
  137. package/dist/src/sync/license-status-client.js.map +1 -0
  138. package/dist/src/sync/license-status-client.test.d.ts +14 -0
  139. package/dist/src/sync/license-status-client.test.d.ts.map +1 -0
  140. package/dist/src/sync/license-status-client.test.js +136 -0
  141. package/dist/src/sync/license-status-client.test.js.map +1 -0
  142. package/dist/src/types.d.ts +13 -0
  143. package/dist/src/types.d.ts.map +1 -1
  144. package/dist/src/utils/version-check.d.ts +58 -5
  145. package/dist/src/utils/version-check.d.ts.map +1 -1
  146. package/dist/src/utils/version-check.js +73 -2
  147. package/dist/src/utils/version-check.js.map +1 -1
  148. package/dist/src/utils/version-check.test.js +49 -10
  149. package/dist/src/utils/version-check.test.js.map +1 -1
  150. package/dist/tests/SecurityScanner.scoring.test.js +123 -0
  151. package/dist/tests/SecurityScanner.scoring.test.js.map +1 -1
  152. package/dist/tests/security/advisory-severity-guard.test.d.ts +20 -0
  153. package/dist/tests/security/advisory-severity-guard.test.d.ts.map +1 -0
  154. package/dist/tests/security/advisory-severity-guard.test.js +70 -0
  155. package/dist/tests/security/advisory-severity-guard.test.js.map +1 -0
  156. package/dist/tests/security/archive-evasion.test.d.ts +2 -0
  157. package/dist/tests/security/archive-evasion.test.d.ts.map +1 -0
  158. package/dist/tests/security/archive-evasion.test.js +126 -0
  159. package/dist/tests/security/archive-evasion.test.js.map +1 -0
  160. package/dist/tests/security/clawhavoc-e2e.fixtures.d.ts +71 -0
  161. package/dist/tests/security/clawhavoc-e2e.fixtures.d.ts.map +1 -0
  162. package/dist/tests/security/clawhavoc-e2e.fixtures.js +135 -0
  163. package/dist/tests/security/clawhavoc-e2e.fixtures.js.map +1 -0
  164. package/dist/tests/security/co-signal-escalation.test.d.ts +30 -0
  165. package/dist/tests/security/co-signal-escalation.test.d.ts.map +1 -0
  166. package/dist/tests/security/co-signal-escalation.test.js +494 -0
  167. package/dist/tests/security/co-signal-escalation.test.js.map +1 -0
  168. package/dist/tests/security/decoy-misdirection.test.d.ts +2 -0
  169. package/dist/tests/security/decoy-misdirection.test.d.ts.map +1 -0
  170. package/dist/tests/security/decoy-misdirection.test.js +130 -0
  171. package/dist/tests/security/decoy-misdirection.test.js.map +1 -0
  172. package/dist/tests/security/encoded-payload.test.d.ts +2 -0
  173. package/dist/tests/security/encoded-payload.test.d.ts.map +1 -0
  174. package/dist/tests/security/encoded-payload.test.js +199 -0
  175. package/dist/tests/security/encoded-payload.test.js.map +1 -0
  176. package/dist/tests/security/gatekeeper-bypass.test.d.ts +2 -0
  177. package/dist/tests/security/gatekeeper-bypass.test.d.ts.map +1 -0
  178. package/dist/tests/security/gatekeeper-bypass.test.js +167 -0
  179. package/dist/tests/security/gatekeeper-bypass.test.js.map +1 -0
  180. package/dist/tests/security/paste-host-fetch.test.d.ts +2 -0
  181. package/dist/tests/security/paste-host-fetch.test.d.ts.map +1 -0
  182. package/dist/tests/security/paste-host-fetch.test.js +199 -0
  183. package/dist/tests/security/paste-host-fetch.test.js.map +1 -0
  184. package/dist/tests/security/scanner-regression-guard.test.js +5 -0
  185. package/dist/tests/security/scanner-regression-guard.test.js.map +1 -1
  186. package/dist/tests/services/aidefence-feedback.test.js +5 -0
  187. package/dist/tests/services/aidefence-feedback.test.js.map +1 -1
  188. package/dist/tests/services/bundled-sibling-scan.test.js +171 -10
  189. package/dist/tests/services/bundled-sibling-scan.test.js.map +1 -1
  190. package/dist/tests/skill-scanner/allowlist.test.js +12 -1
  191. package/dist/tests/skill-scanner/allowlist.test.js.map +1 -1
  192. package/package.json +2 -2
@@ -0,0 +1,342 @@
1
+ /**
2
+ * Security Scanner — paste/snippet-host reputation + fetch-context escalation
3
+ * @module @skillsmith/core/security/scanner/SecurityScanner.paste-host
4
+ *
5
+ * SMI-6033 Wave 3 (Gap 4 fix): the originally-shipped version of this
6
+ * detector (commit b74c18436) implemented a SIMPLER, and in one specific way
7
+ * INCORRECT, design relative to the approved plan
8
+ * (docs/internal/implementation/smi-6033-clawhavoc-scanner-gaps.md, Gap 4):
9
+ * it used a single flat PASTE_HOST_DOMAINS list (incorrectly including
10
+ * transfer.sh/file.io alongside glot.io/pastebin.com) and treated ANY
11
+ * same-line fetch of a paste-host URL as standalone-critical, with no
12
+ * requirement that the fetched content actually be EXECUTED. This file fixes
13
+ * both:
14
+ *
15
+ * 1. Two separate reputation tiers (patterns.ts): `ANON_PASTE_HOSTS` (+
16
+ * `URL_SHORTENER_DOMAINS`, execution-gated the same way) vs.
17
+ * `TRANSIENT_TRANSFER_HOSTS` (never standalone-critical — a deliberate,
18
+ * documented exception for legitimate debugging/incident-response
19
+ * fetches of ephemeral reproducers).
20
+ * 2. Execution evidence is now REQUIRED for `ANON_PASTE_HOSTS`/shorteners
21
+ * to reach critical — either (a) the URL is piped directly to an
22
+ * interpreter on the same line (`curl <url> | bash`, no intermediate
23
+ * file), (b) `npx <url>` directly executes the fetched content (no
24
+ * pipe, no intermediate file either), or (c) the fetch destination is
25
+ * subsequently executed/chmod'd/sourced ELSEWHERE in the content,
26
+ * correlated via the shared `isCorrelatedWithFetchDestination` utility
27
+ * (SecurityScanner.fetch-correlation.ts). Fetched-but-not-executed, or
28
+ * merely linked, now correctly stays at the existing scanUrls()
29
+ * `url`:medium finding — no new finding here.
30
+ *
31
+ * (a)/(b) are a NEW, local execution-detection shape distinct from (c):
32
+ * `isCorrelatedWithFetchDestination` only detects "was this basename later
33
+ * written as a fetch command's DOWNLOAD DESTINATION, then referenced
34
+ * elsewhere" — it has no notion of a same-line direct pipe/direct-exec (no
35
+ * intermediate file at all). The direct-pipe/npx-direct-exec patterns below
36
+ * are local to this file (not promoted to the shared fetch-correlation
37
+ * module, which Gap 5's concurrent xattr fix does not touch) and are
38
+ * deliberately scoped to "the URL being piped/executed is specifically a
39
+ * paste-host or shortener domain" — `CODE_EXECUTION_PATTERNS`' own curl|bash
40
+ * pattern (patterns.ts) already fires independently for ANY curl|bash
41
+ * regardless of domain reputation; this file adds the DOMAIN-REPUTATION-aware
42
+ * escalation on top.
43
+ *
44
+ * `TRANSIENT_TRANSFER_HOSTS` never reaches critical: it emits
45
+ * `paste_host_fetch` at medium whenever it is an actual fetch target (the URL
46
+ * is the ARGUMENT of a same-line fetch verb — see `isActualFetchTarget`
47
+ * below), regardless of execution evidence — `curl file.io/x | bash` alone
48
+ * staying sub-threshold is intentional, not a gap (see patterns.ts's
49
+ * `TRANSIENT_TRANSFER_HOSTS` comment for the rationale).
50
+ *
51
+ * SMI-6033 Wave 3 (adversarial-review fix): the "is this URL fetched?" gate
52
+ * used to be a bare `FETCH_COMMAND_PATTERN` test against the whole LINE, so a
53
+ * fetch verb ANYWHERE on the line counted — `curl --version; see mirror docs
54
+ * at https://pastebin.com/abc` wrongly registered the pastebin URL as
55
+ * fetched. Replaced with `isActualFetchTarget`, which binds the URL to the
56
+ * verb by tokenizing the prefix that precedes it. Same bug class as Wave 4's
57
+ * `decoy_misdirection` fix; see `isActualFetchTarget`'s own doc comment for
58
+ * the two deliberate adaptations to this detector's inputs. This affects ONLY
59
+ * the same-line fetch-target gate — the direct-pipe and npx-direct-exec
60
+ * execution-evidence paths below are a different code path and are unchanged.
61
+ *
62
+ * Known, documented residual (static scanner, no network I/O): redirect-
63
+ * chain/final-host resolution is out of scope by design — custom domains,
64
+ * redirects, and compromised allowlisted hosts are a known residual.
65
+ */
66
+ import { analyzeMarkdownContext, isDocumentationContext } from './SecurityScanner.helpers.js';
67
+ import { extractUrls } from './SecurityScanner.urls.js';
68
+ import { ANON_PASTE_HOSTS, TRANSIENT_TRANSFER_HOSTS, URL_SHORTENER_DOMAINS } from './patterns.js';
69
+ import { safeRegexCheck, safeRegexTest } from './regex-utils.js';
70
+ import { correlationTargetBasename, isCorrelatedWithFetchDestination, } from './SecurityScanner.fetch-correlation.js';
71
+ // URL_SHORTENER_DOMAINS folds into the SAME critical-eligibility set as
72
+ // ANON_PASTE_HOSTS (both require execution evidence to reach critical) —
73
+ // they are exported separately from patterns.ts purely for documentation
74
+ // clarity (a shortener is not a "content host" the way pastebin is).
75
+ const ANON_PASTE_HOST_SET = new Set([...ANON_PASTE_HOSTS, ...URL_SHORTENER_DOMAINS].map((d) => d.toLowerCase()));
76
+ const TRANSIENT_TRANSFER_HOST_SET = new Set(TRANSIENT_TRANSFER_HOSTS.map((d) => d.toLowerCase()));
77
+ /** Which reputation tier (if any) does `url`'s hostname belong to? */
78
+ function classifyPasteHostTier(url) {
79
+ let hostname;
80
+ try {
81
+ hostname = new URL(url).hostname.toLowerCase();
82
+ }
83
+ catch {
84
+ return null;
85
+ }
86
+ const matchesSet = (set) => {
87
+ for (const domain of set) {
88
+ if (hostname === domain || hostname.endsWith('.' + domain))
89
+ return true;
90
+ }
91
+ return false;
92
+ };
93
+ if (matchesSet(ANON_PASTE_HOST_SET))
94
+ return 'anon';
95
+ if (matchesSet(TRANSIENT_TRANSFER_HOST_SET))
96
+ return 'transient';
97
+ return null;
98
+ }
99
+ // Direct pipe to an interpreter, no intermediate file: `curl <url> | bash`.
100
+ // Anchored at the FIRST pipe (mirrors CODE_EXECUTION_PATTERNS' own curl|bash
101
+ // shape in patterns.ts) and scoped to "the piped URL is specifically the
102
+ // paste-host/shortener URL under test" by requiring the URL text to appear
103
+ // BEFORE that pipe, alongside a fetch verb (curl/wget).
104
+ const FETCH_VERB_PATTERN = /\b(?:curl|wget)\b/i;
105
+ const PIPE_TO_INTERPRETER_TAIL = /^\|\s*(?:sudo\s+(?:-[A-Za-z]+\s+)?)?(?:(?:ba|z|da)?sh|python[23]?|node|ruby|perl|php|fish|bun|deno)\b/i;
106
+ function isDirectPipeToInterpreter(url, lineContent) {
107
+ const pipeIndex = lineContent.indexOf('|');
108
+ if (pipeIndex < 0)
109
+ return false;
110
+ const beforePipe = lineContent.slice(0, pipeIndex);
111
+ if (!beforePipe.includes(url))
112
+ return false;
113
+ if (!safeRegexCheck(FETCH_VERB_PATTERN, beforePipe))
114
+ return false;
115
+ return safeRegexCheck(PIPE_TO_INTERPRETER_TAIL, lineContent.slice(pipeIndex));
116
+ }
117
+ // npx directly executes whatever it fetches from a URL — no pipe, no
118
+ // intermediate file — so this is its own direct-execution shape (npx never
119
+ // just downloads, so a line that fetches a URL through npx has, by that
120
+ // fact alone, executed it).
121
+ const NPX_DIRECT_EXEC_PATTERN = /\bnpx\b[^\n]{0,80}https?:\/\//i;
122
+ // ============================================================================
123
+ // Same-line fetch-TARGET binding (SMI-6033 Wave 3 adversarial-review fix)
124
+ // ============================================================================
125
+ /**
126
+ * This detector used to decide "is this paste-host URL fetched?" with a bare
127
+ * `FETCH_COMMAND_PATTERN` test against the whole LINE — i.e. "does a fetch
128
+ * verb appear anywhere on this line", not "is THIS URL the argument to that
129
+ * verb". `curl --version; see mirror docs at https://pastebin.com/abc`
130
+ * therefore registered the pastebin URL as fetched purely because the token
131
+ * `curl` appeared earlier in an unrelated command.
132
+ *
133
+ * Same bug class as the one already fixed in Wave 4's `decoy_misdirection`
134
+ * detector (`SecurityScanner.decoy.ts`'s `isActualFetchTarget`), and the fix
135
+ * is the same idea — tokenize the line prefix that precedes the URL and
136
+ * require the URL to actually be a fetch verb's argument — with two
137
+ * deliberate adaptations for this detector's inputs:
138
+ *
139
+ * 1. A command separator (`;`, `|`, `&`) does not disqualify the whole
140
+ * line, it just bounds the search: only the segment the URL itself sits
141
+ * in can bind it. `cd /tmp && curl <url> | bash` is a real attack shape
142
+ * that decoy.ts's stricter "reject on any separator" rule would drop,
143
+ * and this detector's whole point is catching piped/chained execution.
144
+ * 2. The verb need not be the FIRST token of that segment. Paste-host
145
+ * fetches routinely arrive wrapped in markdown/shell decoration
146
+ * (`- Run \`curl <url> | bash\``, `$ curl <url>`, `bash <(curl <url>)`),
147
+ * so the check anchors on the LAST fetch verb in the segment and then
148
+ * requires every token between that verb and the URL to be a flag (or
149
+ * the value argument of a value-taking flag). Prose between the verb
150
+ * and the URL — "see mirror docs at" — still fails, which is exactly
151
+ * the FP being closed.
152
+ */
153
+ const FETCH_VERB_TOKENS = new Set(['curl', 'wget', 'npx']);
154
+ /** A bare flag token, e.g. `-o`, `-fsSL`, `--output`, `--data=x`. */
155
+ const FLAG_TOKEN = /^-{1,2}[A-Za-z][\w-]*$/;
156
+ /**
157
+ * Common curl/wget/npx flags that take a SEPARATE value token (e.g.
158
+ * `-o setup.sh`, `-X POST`) — that value token must be consumed as part of
159
+ * the flag, not mistaken for prose.
160
+ */
161
+ const VALUE_TAKING_FLAGS = new Set([
162
+ '-o',
163
+ '-x',
164
+ '-h',
165
+ '-d',
166
+ '-a',
167
+ '-e',
168
+ '-u',
169
+ '-b',
170
+ '--output',
171
+ '--request',
172
+ '--header',
173
+ '--data',
174
+ '--data-raw',
175
+ '--data-binary',
176
+ '--data-urlencode',
177
+ '--user-agent',
178
+ '--referer',
179
+ '--proxy',
180
+ '--cookie',
181
+ ]);
182
+ /**
183
+ * Strip markdown/shell decoration off a token (leading backticks, quotes,
184
+ * `$`/`>` prompt markers, `<(`; trailing quotes, backticks, brackets) while
185
+ * preserving the leading `-`/`--` that makes a flag a flag. Lowercased, so
186
+ * the verb and flag lookups above stay case-insensitive.
187
+ */
188
+ function bareToken(token) {
189
+ return token
190
+ .replace(/^[^A-Za-z0-9/.-]+/, '')
191
+ .replace(/[^A-Za-z0-9/._-]+$/, '')
192
+ .toLowerCase();
193
+ }
194
+ /** Is the URL that follows `prefix` on this line the argument of a fetch verb? */
195
+ function isFetchVerbArgument(prefix) {
196
+ // Only the segment after the LAST command separator can bind the URL —
197
+ // anything before it belongs to a different command.
198
+ const segment = prefix.split(/[;|&]/).pop() ?? '';
199
+ const tokens = segment
200
+ .trim()
201
+ .split(/\s+/)
202
+ .map(bareToken)
203
+ .filter((t) => t.length > 0);
204
+ let afterVerb = -1;
205
+ for (let i = 0; i < tokens.length; i++) {
206
+ if (tokens[i] === 'git' && tokens[i + 1] === 'clone')
207
+ afterVerb = i + 2;
208
+ else if (FETCH_VERB_TOKENS.has(tokens[i]))
209
+ afterVerb = i + 1;
210
+ }
211
+ if (afterVerb < 0)
212
+ return false;
213
+ for (let i = afterVerb; i < tokens.length; i++) {
214
+ const token = tokens[i];
215
+ if (!safeRegexCheck(FLAG_TOKEN, token))
216
+ return false;
217
+ if (VALUE_TAKING_FLAGS.has(token) && i + 1 < tokens.length)
218
+ i++; // consume the flag's value
219
+ }
220
+ return true;
221
+ }
222
+ /**
223
+ * True when `url` is actually the argument to a fetch verb somewhere on
224
+ * `lineContent` — checked at EVERY occurrence of the URL on the line, so a
225
+ * prose mention followed by a real fetch of the same URL still binds.
226
+ */
227
+ function isActualFetchTarget(lineContent, url) {
228
+ for (let from = 0;;) {
229
+ const urlIndex = lineContent.indexOf(url, from);
230
+ if (urlIndex < 0)
231
+ return false;
232
+ if (isFetchVerbArgument(lineContent.slice(0, urlIndex)))
233
+ return true;
234
+ from = urlIndex + 1;
235
+ }
236
+ }
237
+ // Execution/chmod/source instructions whose target path is correlated
238
+ // against a paste-host/shortener fetch's download destination via the
239
+ // shared `isCorrelatedWithFetchDestination`. Deliberately local to this file
240
+ // (not imported from SecurityScanner.compound.ts, which Gap 5's concurrent
241
+ // xattr fix is editing) — same normalization (strip quotes, ≥3-char gate on
242
+ // the final path segment) as compound.ts's own chmod correlation.
243
+ const CHMOD_TARGET = /\bchmod\s+(?:-[A-Za-z]+\s+)?(?:[0-7]{3,4}|[ugoa]*(?:[+\-=][rwxXstugo]+(?:,[ugoa]*[+\-=][rwxXstugo]*)*)+)\s+(\S+)/i;
244
+ const DIRECT_EXEC_TARGET = /\b(?:sudo\s+)?(?:(?:ba|z|da)?sh|python[23]?|node|ruby|perl|php|fish|bun|deno)\s+(\.{0,2}\/?[\w-]+(?:[./][\w-]+)*)/i;
245
+ const DOT_SLASH_EXEC_TARGET = /(?:^|[\s;&|])\.\/([\w-]+(?:[./][\w-]+)*)/;
246
+ // "sourced" is deliberately scoped to the portable `source X` keyword, not
247
+ // the bare POSIX `. X` alias — the latter is too easily confused with prose
248
+ // ("... . More info at ...") to be worth the extra correlation-signal noise.
249
+ const SOURCE_TARGET = /(?:^|[;&|]\s*)source\s+(\.{0,2}\/?[\w-]+(?:[./][\w-]+)+)/i;
250
+ const EXEC_TARGET_PATTERNS = [
251
+ CHMOD_TARGET,
252
+ DIRECT_EXEC_TARGET,
253
+ DOT_SLASH_EXEC_TARGET,
254
+ SOURCE_TARGET,
255
+ ];
256
+ /**
257
+ * Every exec/chmod/source target PATH (final segment ≥3 chars, quotes
258
+ * stripped) across the document. SMI-6033 Wave 3: full paths, not basenames —
259
+ * the shared correlation utility is directory-aware and must not be handed a
260
+ * path-stripped target.
261
+ */
262
+ function collectExecTargetPaths(lines) {
263
+ const paths = [];
264
+ for (const line of lines) {
265
+ for (const pattern of EXEC_TARGET_PATTERNS) {
266
+ const m = safeRegexTest(pattern, line);
267
+ if (!m?.[1])
268
+ continue;
269
+ const targetPath = m[1].replace(/['"]/g, '');
270
+ if (correlationTargetBasename(targetPath).length >= 3)
271
+ paths.push(targetPath);
272
+ }
273
+ }
274
+ return paths;
275
+ }
276
+ /** Was `fetchLine`'s download destination later executed/chmod'd/sourced elsewhere? */
277
+ function isExecutedElsewhereCorrelated(fetchLine, execTargetPaths) {
278
+ return execTargetPaths.some((path) => isCorrelatedWithFetchDestination(path, [fetchLine]));
279
+ }
280
+ export function scanPasteHostFetch(content, lineContexts) {
281
+ const findings = [];
282
+ const lines = content.split('\n');
283
+ const contexts = lineContexts ?? analyzeMarkdownContext(content);
284
+ const urls = extractUrls(content);
285
+ const execTargetPaths = collectExecTargetPaths(lines);
286
+ for (const { url, line } of urls) {
287
+ const tier = classifyPasteHostTier(url);
288
+ if (!tier)
289
+ continue;
290
+ const lineIndex = line - 1;
291
+ const lineContent = lines[lineIndex] ?? '';
292
+ // The URL is a fetch TARGET only when it is ACTUALLY the argument of a
293
+ // fetch verb on the SAME line (SMI-6033 Wave 3 — see isActualFetchTarget
294
+ // above for the FP this replaced). Deliberately not widened to a bounded
295
+ // window (unlike the chmod/archive compound signals) since a URL
296
+ // literally is the thing being fetched when it is that verb's argument;
297
+ // widening would risk correlating an unrelated fetch command to an
298
+ // unrelated nearby paste-host mention.
299
+ if (!isActualFetchTarget(lineContent, url))
300
+ continue; // merely linked -> scanUrls()'s url:medium finding already covers this
301
+ const ctx = contexts[lineIndex];
302
+ const inDocContext = ctx ? isDocumentationContext(ctx) : false;
303
+ if (tier === 'transient') {
304
+ // Always medium/co-signal-eligible, NEVER standalone-critical —
305
+ // regardless of execution evidence. See patterns.ts's
306
+ // TRANSIENT_TRANSFER_HOSTS comment for the rationale.
307
+ findings.push({
308
+ type: 'paste_host_fetch',
309
+ severity: inDocContext ? 'low' : 'medium',
310
+ message: `Ephemeral file-transfer host URL fetched (never standalone-critical by design): ${url}`,
311
+ location: lineContent.trim().slice(0, 100),
312
+ lineNumber: line,
313
+ category: 'paste_host_fetch',
314
+ inDocumentationContext: inDocContext,
315
+ confidence: inDocContext ? 'low' : 'medium',
316
+ });
317
+ continue;
318
+ }
319
+ // tier === 'anon' (ANON_PASTE_HOSTS or a URL shortener): critical
320
+ // requires actual EXECUTION evidence, not just a fetch.
321
+ const executed = isDirectPipeToInterpreter(url, lineContent) ||
322
+ safeRegexCheck(NPX_DIRECT_EXEC_PATTERN, lineContent) ||
323
+ isExecutedElsewhereCorrelated(lineContent, execTargetPaths);
324
+ if (!executed)
325
+ continue; // fetched but not executed -> scanUrls()'s url:medium finding already covers this
326
+ findings.push({
327
+ type: 'paste_host_fetch',
328
+ // Standalone-critical (execution evidence required, see above) — doc
329
+ // context is the only downgrade, matching every other detector's
330
+ // noise-reduction convention in this Wave.
331
+ severity: inDocContext ? 'low' : 'critical',
332
+ message: `Paste/snippet-host URL is the target of an execution instruction: ${url}`,
333
+ location: lineContent.trim().slice(0, 100),
334
+ lineNumber: line,
335
+ category: 'paste_host_fetch',
336
+ inDocumentationContext: inDocContext,
337
+ confidence: inDocContext ? 'low' : 'high',
338
+ });
339
+ }
340
+ return findings;
341
+ }
342
+ //# sourceMappingURL=SecurityScanner.paste-host.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SecurityScanner.paste-host.js","sourceRoot":"","sources":["../../../../src/security/scanner/SecurityScanner.paste-host.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Security Scanner — risk score calculation
3
+ * @module @skillsmith/core/security/scanner/SecurityScanner.risk-score
4
+ *
5
+ * SMI-6033 Wave 4: extracted out of `SecurityScanner.helpers.ts` (which had
6
+ * grown to 504/500 lines, over this repo's file-length gate, once Wave 4's
7
+ * `decoyMisdirection` breakdown field/switch-arm/cap/sum entries landed).
8
+ * This filename intentionally matches the one already referenced by the
9
+ * separate, still-open SMI-5879 Wave 2 PR (#2192,
10
+ * `feat/smi-5879-wave2-pattern-port` — see that branch's
11
+ * `scripts/indexer/smi5879-corroboration.types.ts`, which already names
12
+ * `packages/core/src/security/scanner/SecurityScanner.risk-score.ts` as its
13
+ * own planned extraction target for this same function): reusing the name
14
+ * that PR already committed to avoids a second, differently-named
15
+ * competing extraction landing later and forcing a rename/merge fight.
16
+ */
17
+ import type { SecurityFinding, RiskScoreBreakdown } from './types.js';
18
+ /**
19
+ * SMI-685: Calculate risk score from findings
20
+ * SMI-1513: Accounts for confidence levels (low confidence = reduced weight)
21
+ * Aggregates multiple findings into a risk score from 0-100
22
+ */
23
+ export declare function calculateRiskScore(findings: SecurityFinding[]): {
24
+ total: number;
25
+ breakdown: RiskScoreBreakdown;
26
+ };
27
+ //# sourceMappingURL=SecurityScanner.risk-score.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SecurityScanner.risk-score.d.ts","sourceRoot":"","sources":["../../../../src/security/scanner/SecurityScanner.risk-score.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,kBAAkB,EAAqB,MAAM,YAAY,CAAA;AAGxF;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,eAAe,EAAE,GAAG;IAC/D,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,kBAAkB,CAAA;CAC9B,CAqKA"}
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Security Scanner — risk score calculation
3
+ * @module @skillsmith/core/security/scanner/SecurityScanner.risk-score
4
+ *
5
+ * SMI-6033 Wave 4: extracted out of `SecurityScanner.helpers.ts` (which had
6
+ * grown to 504/500 lines, over this repo's file-length gate, once Wave 4's
7
+ * `decoyMisdirection` breakdown field/switch-arm/cap/sum entries landed).
8
+ * This filename intentionally matches the one already referenced by the
9
+ * separate, still-open SMI-5879 Wave 2 PR (#2192,
10
+ * `feat/smi-5879-wave2-pattern-port` — see that branch's
11
+ * `scripts/indexer/smi5879-corroboration.types.ts`, which already names
12
+ * `packages/core/src/security/scanner/SecurityScanner.risk-score.ts` as its
13
+ * own planned extraction target for this same function): reusing the name
14
+ * that PR already committed to avoids a second, differently-named
15
+ * competing extraction landing later and forcing a rename/merge fight.
16
+ */
17
+ import { SEVERITY_WEIGHTS, CATEGORY_WEIGHTS } from './weights.js';
18
+ /**
19
+ * SMI-685: Calculate risk score from findings
20
+ * SMI-1513: Accounts for confidence levels (low confidence = reduced weight)
21
+ * Aggregates multiple findings into a risk score from 0-100
22
+ */
23
+ export function calculateRiskScore(findings) {
24
+ const breakdown = {
25
+ jailbreak: 0,
26
+ socialEngineering: 0,
27
+ promptLeaking: 0,
28
+ dataExfiltration: 0,
29
+ privilegeEscalation: 0,
30
+ suspiciousCode: 0,
31
+ sensitivePaths: 0,
32
+ externalUrls: 0,
33
+ aiDefence: 0,
34
+ ssrf: 0,
35
+ pii: 0,
36
+ codeExecution: 0,
37
+ obfuscatedDirective: 0,
38
+ typosquat: 0,
39
+ gatekeeperBypass: 0,
40
+ archiveEvasion: 0,
41
+ pasteHostFetch: 0,
42
+ encodedPayload: 0,
43
+ decoyMisdirection: 0,
44
+ };
45
+ const confidenceWeights = {
46
+ high: 1.0,
47
+ medium: 0.7,
48
+ low: 0.3,
49
+ };
50
+ for (const finding of findings) {
51
+ const severityWeight = SEVERITY_WEIGHTS[finding.severity];
52
+ const categoryWeight = CATEGORY_WEIGHTS[finding.type] ?? 1.0;
53
+ const confidenceWeight = confidenceWeights[finding.confidence ?? 'high'];
54
+ const score = severityWeight * categoryWeight * confidenceWeight;
55
+ switch (finding.type) {
56
+ case 'jailbreak':
57
+ breakdown.jailbreak += score;
58
+ break;
59
+ case 'social_engineering':
60
+ breakdown.socialEngineering += score;
61
+ break;
62
+ case 'prompt_leaking':
63
+ breakdown.promptLeaking += score;
64
+ break;
65
+ case 'data_exfiltration':
66
+ breakdown.dataExfiltration += score;
67
+ break;
68
+ case 'privilege_escalation':
69
+ breakdown.privilegeEscalation += score;
70
+ break;
71
+ case 'suspicious_pattern':
72
+ breakdown.suspiciousCode += score;
73
+ break;
74
+ case 'sensitive_path':
75
+ breakdown.sensitivePaths += score;
76
+ break;
77
+ case 'url':
78
+ breakdown.externalUrls += score;
79
+ break;
80
+ case 'ai_defence':
81
+ breakdown.aiDefence += score;
82
+ break;
83
+ case 'ssrf':
84
+ breakdown.ssrf += score;
85
+ break;
86
+ case 'pii':
87
+ breakdown.pii += score;
88
+ break;
89
+ case 'code_execution':
90
+ breakdown.codeExecution += score;
91
+ break;
92
+ case 'obfuscated_directive':
93
+ breakdown.obfuscatedDirective += score;
94
+ break;
95
+ case 'typosquat':
96
+ // SMI-595: this switch has NO default case — a missing arm here would
97
+ // silently drop every typosquat finding from the breakdown with zero
98
+ // compile-time or runtime error. See the switch-case coverage test in
99
+ // SecurityScanner.scoring.test.ts, added specifically to guard this.
100
+ breakdown.typosquat += score;
101
+ break;
102
+ case 'gatekeeper_bypass':
103
+ breakdown.gatekeeperBypass += score;
104
+ break;
105
+ case 'archive_evasion':
106
+ breakdown.archiveEvasion += score;
107
+ break;
108
+ case 'paste_host_fetch':
109
+ breakdown.pasteHostFetch += score;
110
+ break;
111
+ case 'encoded_payload':
112
+ breakdown.encodedPayload += score;
113
+ break;
114
+ case 'decoy_misdirection':
115
+ breakdown.decoyMisdirection += score;
116
+ break;
117
+ }
118
+ }
119
+ // Cap each category at 100
120
+ breakdown.jailbreak = Math.min(100, breakdown.jailbreak);
121
+ breakdown.socialEngineering = Math.min(100, breakdown.socialEngineering);
122
+ breakdown.promptLeaking = Math.min(100, breakdown.promptLeaking);
123
+ breakdown.dataExfiltration = Math.min(100, breakdown.dataExfiltration);
124
+ breakdown.privilegeEscalation = Math.min(100, breakdown.privilegeEscalation);
125
+ breakdown.suspiciousCode = Math.min(100, breakdown.suspiciousCode);
126
+ breakdown.sensitivePaths = Math.min(100, breakdown.sensitivePaths);
127
+ breakdown.externalUrls = Math.min(100, breakdown.externalUrls);
128
+ breakdown.aiDefence = Math.min(100, breakdown.aiDefence);
129
+ breakdown.ssrf = Math.min(100, breakdown.ssrf);
130
+ breakdown.pii = Math.min(100, breakdown.pii);
131
+ breakdown.codeExecution = Math.min(100, breakdown.codeExecution);
132
+ breakdown.obfuscatedDirective = Math.min(100, breakdown.obfuscatedDirective);
133
+ breakdown.typosquat = Math.min(100, breakdown.typosquat);
134
+ breakdown.gatekeeperBypass = Math.min(100, breakdown.gatekeeperBypass);
135
+ breakdown.archiveEvasion = Math.min(100, breakdown.archiveEvasion);
136
+ breakdown.pasteHostFetch = Math.min(100, breakdown.pasteHostFetch);
137
+ breakdown.encodedPayload = Math.min(100, breakdown.encodedPayload);
138
+ breakdown.decoyMisdirection = Math.min(100, breakdown.decoyMisdirection);
139
+ // SMI-5359 Wave 4.2: the two new categories use a 0.40 coefficient and are ADDITIVE
140
+ // (the original eleven coefficients sum to 1.0; these add on top). No code assumes the
141
+ // coefficients sum to 1.0, and the final Math.min(100, ...) cap is preserved — so a skill
142
+ // that triggers neither new category scores exactly as before (both breakdown entries 0).
143
+ // SMI-595: typosquat is likewise ADDITIVE, at the 0.04 coefficient already used for
144
+ // sensitivePaths/externalUrls/ssrf (the "advisory tier" of this second-stage formula) —
145
+ // NOT folded into the eleven-category budget that sums to 1.0. See weights.ts's
146
+ // CATEGORY_WEIGHTS.typosquat comment for the worked calculation this pairs with.
147
+ // SMI-6033 Wave 2: gatekeeperBypass/archiveEvasion/pasteHostFetch are
148
+ // ADDITIVE at the 0.40 coefficient (matching codeExecution/
149
+ // obfuscatedDirective's top tier) — see weights.ts's
150
+ // CATEGORY_WEIGHTS.gatekeeper_bypass comment for the worked calculation
151
+ // showing how ONE weight/coefficient pair produces both the
152
+ // standalone-critical and advisory-medium outcomes via severity alone.
153
+ // encodedPayload is likewise ADDITIVE, at the 0.04 coefficient (the same
154
+ // advisory tier as sensitivePaths/externalUrls/ssrf/typosquat) — see
155
+ // weights.ts's CATEGORY_WEIGHTS.encoded_payload comment.
156
+ const total = Math.min(100, Math.round(breakdown.jailbreak * 0.2 +
157
+ breakdown.socialEngineering * 0.11 +
158
+ breakdown.promptLeaking * 0.11 +
159
+ breakdown.dataExfiltration * 0.08 +
160
+ breakdown.privilegeEscalation * 0.11 +
161
+ breakdown.suspiciousCode * 0.07 +
162
+ breakdown.sensitivePaths * 0.04 +
163
+ breakdown.externalUrls * 0.04 +
164
+ breakdown.aiDefence * 0.12 +
165
+ breakdown.ssrf * 0.04 +
166
+ breakdown.pii * 0.08 +
167
+ breakdown.codeExecution * 0.4 +
168
+ breakdown.obfuscatedDirective * 0.4 +
169
+ breakdown.typosquat * 0.04 +
170
+ breakdown.gatekeeperBypass * 0.4 +
171
+ breakdown.archiveEvasion * 0.4 +
172
+ breakdown.pasteHostFetch * 0.4 +
173
+ breakdown.encodedPayload * 0.04 +
174
+ // SMI-6033 Wave 4 (Gap 6): additive 0.04, same advisory tier as encodedPayload/typosquat.
175
+ breakdown.decoyMisdirection * 0.04));
176
+ return { total, breakdown };
177
+ }
178
+ //# sourceMappingURL=SecurityScanner.risk-score.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SecurityScanner.risk-score.js","sourceRoot":"","sources":["../../../../src/security/scanner/SecurityScanner.risk-score.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Security Scanner — URL extraction
3
+ * @module @skillsmith/core/security/scanner/SecurityScanner.urls
4
+ *
5
+ * SMI-6033 Wave 2 (Gap 4): split out of SecurityScanner.helpers.ts, which the
6
+ * promotion below pushed over the 500-line audit:standards gate. Promoted
7
+ * out of a private SecurityScanner.ts method (`extractUrls`) so `scanUrls`
8
+ * and the new `scanPasteHostFetch` detector (SecurityScanner.paste-host.ts)
9
+ * share ONE extraction implementation instead of two
10
+ * independently-drifting regex sweeps over the same content. Pure
11
+ * extraction — behavior is unchanged from the prior private method (same
12
+ * regex, same per-line scan, same return shape).
13
+ */
14
+ /** A URL found in scanned content, plus the (1-indexed) line it appeared on. */
15
+ export interface ExtractedUrl {
16
+ url: string;
17
+ line: number;
18
+ }
19
+ export declare function extractUrls(content: string): ExtractedUrl[];
20
+ //# sourceMappingURL=SecurityScanner.urls.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SecurityScanner.urls.d.ts","sourceRoot":"","sources":["../../../../src/security/scanner/SecurityScanner.urls.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,gFAAgF;AAChF,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;CACb;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,EAAE,CAa3D"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Security Scanner — URL extraction
3
+ * @module @skillsmith/core/security/scanner/SecurityScanner.urls
4
+ *
5
+ * SMI-6033 Wave 2 (Gap 4): split out of SecurityScanner.helpers.ts, which the
6
+ * promotion below pushed over the 500-line audit:standards gate. Promoted
7
+ * out of a private SecurityScanner.ts method (`extractUrls`) so `scanUrls`
8
+ * and the new `scanPasteHostFetch` detector (SecurityScanner.paste-host.ts)
9
+ * share ONE extraction implementation instead of two
10
+ * independently-drifting regex sweeps over the same content. Pure
11
+ * extraction — behavior is unchanged from the prior private method (same
12
+ * regex, same per-line scan, same return shape).
13
+ */
14
+ export function extractUrls(content) {
15
+ const urlPattern = /https?:\/\/[^\s<>"')\]]+/gi;
16
+ const lines = content.split('\n');
17
+ const results = [];
18
+ lines.forEach((line, index) => {
19
+ let match;
20
+ while ((match = urlPattern.exec(line)) !== null) {
21
+ results.push({ url: match[0], line: index + 1 });
22
+ }
23
+ });
24
+ return results;
25
+ }
26
+ //# sourceMappingURL=SecurityScanner.urls.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SecurityScanner.urls.js","sourceRoot":"","sources":["../../../../src/security/scanner/SecurityScanner.urls.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAQH,MAAM,UAAU,WAAW,CAAC,OAAe;IACzC,MAAM,UAAU,GAAG,4BAA4B,CAAA;IAC/C,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IACjC,MAAM,OAAO,GAAmB,EAAE,CAAA;IAElC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;QAC5B,IAAI,KAAK,CAAA;QACT,OAAO,CAAC,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;YAChD,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,GAAG,CAAC,EAAE,CAAC,CAAA;QAClD,CAAC;IACH,CAAC,CAAC,CAAA;IAEF,OAAO,OAAO,CAAA;AAChB,CAAC"}
@@ -4,12 +4,12 @@
4
4
  * Re-exports for security scanning functionality.
5
5
  */
6
6
  export type { SecurityFindingType, SecuritySeverity, SecurityFinding, RiskScoreBreakdown, ScanReport, ScannerOptions, HostileUpdateVerdict, TyposquatEnforcementMode, EvidenceType, } from './types.js';
7
- export { DEFAULT_ALLOWED_DOMAINS, SENSITIVE_PATH_PATTERNS, JAILBREAK_PATTERNS, SUSPICIOUS_PATTERNS, SOCIAL_ENGINEERING_PATTERNS, PROMPT_LEAKING_PATTERNS, DATA_EXFILTRATION_PATTERNS, PRIVILEGE_ESCALATION_PATTERNS, AI_DEFENCE_PATTERNS, SSRF_INSTRUCTION_PATTERNS, PII_PATTERNS, CODE_EXECUTION_PATTERNS, SCANNER_RULESET_VERSION, } from './patterns.js';
7
+ export { DEFAULT_ALLOWED_DOMAINS, SENSITIVE_PATH_PATTERNS, JAILBREAK_PATTERNS, SUSPICIOUS_PATTERNS, SOCIAL_ENGINEERING_PATTERNS, PROMPT_LEAKING_PATTERNS, DATA_EXFILTRATION_PATTERNS, PRIVILEGE_ESCALATION_PATTERNS, AI_DEFENCE_PATTERNS, SSRF_INSTRUCTION_PATTERNS, PII_PATTERNS, CODE_EXECUTION_PATTERNS, IMPERATIVE_FETCH_EXEC_PROSE, SCANNER_RULESET_VERSION, } from './patterns.js';
8
8
  export { SEVERITY_WEIGHTS, CATEGORY_WEIGHTS } from './weights.js';
9
9
  export { MAX_LINE_LENGTH_FOR_REGEX, MAX_CONTENT_LENGTH_FOR_REGEX, safeRegexTest, safeRegexCheck, } from './regex-utils.js';
10
10
  export { stripInvisible } from './SecurityScanner.exec.js';
11
11
  export { CONFUSABLES, isFullwidthLatin, isMathAlphanumeric, confusableSkeleton, } from './confusables.js';
12
- export { BRAND_ALIASES, levenshteinDistance, scanTyposquat, detectTyposquat, applyTyposquatEnforcementMode, resolveTyposquatEnforcementMode, DEFAULT_TYPOSQUAT_ENFORCEMENT_MODE, } from './typosquat.js';
12
+ export { BRAND_ALIASES, AUTHORITY_CLAIMING_AFFIXES, levenshteinDistance, scanTyposquat, detectTyposquat, applyTyposquatEnforcementMode, resolveTyposquatEnforcementMode, DEFAULT_TYPOSQUAT_ENFORCEMENT_MODE, } from './typosquat.js';
13
13
  export { buildTyposquatReferenceList, DEFAULT_TOP_INSTALLED_LIMIT, } from './typosquat-reference-list.js';
14
14
  export type { ReferenceSkillEntry, InstalledSkillEntry, BuildTyposquatReferenceListOptions, } from './typosquat-reference-list.js';
15
15
  export { describeSignals } from './SecurityScanner.formatters.js';