@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
@@ -10,6 +10,7 @@
10
10
  */
11
11
  import { analyzeMarkdownContext, isDocumentationContext, isWithinInlineCode, } from './SecurityScanner.helpers.js';
12
12
  import { safeRegexTest } from './regex-utils.js';
13
+ import { FETCH_COMMAND_PATTERN, correlationTargetBasename, isCorrelatedWithFetchDestination, } from './SecurityScanner.fetch-correlation.js';
13
14
  /**
14
15
  * SMI-5424 PR2: owner-permission chmod is a COMPOUND signal, not standalone —
15
16
  * `chmod 755 ./bin/cli` / `chmod +x build.sh` previously false-fired
@@ -34,49 +35,19 @@ import { safeRegexTest } from './regex-utils.js';
34
35
  // that bare `chmod +x foo` (no u/g/o/a prefix — the most common make-executable form
35
36
  // in install scripts and malicious droppers) still matches.
36
37
  const OWNER_PERM_CHMOD = /\bchmod\s+(?:-[A-Za-z]+\s+)?(?:[0-7]{3,4}|[ugoa]*(?:[+\-=][rwxXstugo]+(?:,[ugoa]*[+\-=][rwxXstugo]*)*)+)/i;
37
- // FIX-1: actual fetch COMMANDS only — bare prose tokens (`# downloaded`, `See https://…`,
38
- // `npx tool init`) false-fired next to a chmod. curl/wget/git-clone, and npx only with a URL.
39
- const CHMOD_FETCH_CONTEXT = /\b(?:curl|wget)\b|\bgit\s+clone\b|\bnpx\b[^\n]{0,80}https?:\/\//i;
40
38
  // FIX-2: capture the chmod target path so a fetch command anywhere correlates by basename.
41
39
  // SMI-5433: prefix widened to match the same extended forms as OWNER_PERM_CHMOD; capture
42
40
  // group (\S+) (the target path) is unchanged.
43
41
  const CHMOD_TARGET = /\bchmod\s+(?:-[A-Za-z]+\s+)?(?:[0-7]{3,4}|[ugoa]*(?:[+\-=][rwxXstugo]+(?:,[ugoa]*[+\-=][rwxXstugo]*)*)+)\s+(\S+)/i;
44
- function escapeRegExp(s) {
45
- return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
46
- }
47
- // SMI-5431: the IMPLICIT download destination of a fetch command — the file written with
48
- // NO explicit -o/-O/--output<space>/> redirect: `wget <url>` (no -O/-o) → URL last segment;
49
- // `git clone <url>` → repo dir (minus `.git`); `curl --output=<file>` (equals form, missed by
50
- // the explicit regex). A bare `curl <url>` GET writes to STDOUT → '' (never correlates). ReDoS-safe.
51
- function implicitDownloadBasename(line) {
52
- const lastSegment = (urlAfterScheme) => {
53
- const noFrag = urlAfterScheme.split(/[?#]/)[0];
54
- const slash = noFrag.indexOf('/'); // first slash = end of host
55
- if (slash < 0)
56
- return ''; // host only -> wget writes index.html
57
- const path = noFrag.slice(slash + 1).replace(/\/+$/, '');
58
- return path === '' ? '' : (path.split('/').pop() ?? '');
59
- };
60
- const wget = line.match(/\bwget\b(?![^\n]{0,200}\s-[oO]\b)[^\n]{0,200}?https?:\/\/(\S{1,400})/i);
61
- if (wget)
62
- return lastSegment(wget[1]);
63
- const clone = line.match(/\bgit\s+clone\b[^\n]{0,200}?https?:\/\/(\S{1,400})/i);
64
- if (clone)
65
- return lastSegment(clone[1]).replace(/\.git$/i, '');
66
- const curlEq = line.match(/\bcurl\b[^\n]{0,200}?--output=['"]?(\S{1,400})/i);
67
- if (curlEq)
68
- return curlEq[1].replace(/['"]/g, '').split('/').pop() ?? '';
69
- return '';
70
- }
71
42
  export function scanChmodFetchCompound(content, alreadyFlaggedLines, lineContexts) {
72
43
  const findings = [];
73
44
  const lines = content.split('\n');
74
45
  const contexts = lineContexts ?? analyzeMarkdownContext(content);
75
46
  // FIX-2: lines carrying a fetch command, for distance-independent correlation.
76
47
  // H-6 (SMI-5433): route through safeRegexTest so the 10,000-char cap applies uniformly
77
- // to the per-line filter too (CHMOD_FETCH_CONTEXT is provably linear, so this is
48
+ // to the per-line filter too (FETCH_COMMAND_PATTERN is provably linear, so this is
78
49
  // defense-in-depth consistency, not a ReDoS fix).
79
- const fetchLines = lines.filter((l) => safeRegexTest(CHMOD_FETCH_CONTEXT, l) !== null);
50
+ const fetchLines = lines.filter((l) => safeRegexTest(FETCH_COMMAND_PATTERN, l) !== null);
80
51
  lines.forEach((line, index) => {
81
52
  const lineNumber = index + 1;
82
53
  // World-writable / setuid already emitted critical for this line — skip.
@@ -88,20 +59,21 @@ export function scanChmodFetchCompound(content, alreadyFlaggedLines, lineContext
88
59
  // Bounded ±1-line window for the download-then-chmod adjacency.
89
60
  const window = [lines[index - 1] ?? '', line, lines[index + 1] ?? ''].join('\n');
90
61
  // H-6 (SMI-5433): route through safeRegexTest for the 10,000-char cap.
91
- const adjacentFetch = safeRegexTest(CHMOD_FETCH_CONTEXT, window) !== null;
92
- // FIX-2 + SMI-5431: correlate the chmod target basename (≥3 chars) against a fetch
62
+ const adjacentFetch = safeRegexTest(FETCH_COMMAND_PATTERN, window) !== null;
63
+ // FIX-2 + SMI-5431: correlate the chmod target (final segment ≥3 chars) against a fetch
93
64
  // command's DOWNLOAD DESTINATION anywhere — explicit (-o/-O/--output<space>/>, with an
94
65
  // optional leading path) via regex, OR implicit (wget/git-clone/curl --output=) via
95
66
  // exact-token equality. Anchored on the destination, NOT basename-anywhere, so a URL
96
67
  // path / query / header value (governance FP class) and a bare curl GET do not correlate.
68
+ // SMI-6033 Wave 3: the FULL captured path is handed to the shared utility (it used to be
69
+ // reduced to a bare basename here, discarding the directory the utility now compares).
97
70
  let correlated = false;
98
71
  // H-6 (SMI-5433): route through safeRegexTest for the 10,000-char cap.
99
72
  const tm = safeRegexTest(CHMOD_TARGET, line);
100
73
  if (tm) {
101
- const base = tm[1].replace(/['"]/g, '').split('/').pop() ?? '';
102
- if (base.length >= 3) {
103
- const re = new RegExp(`(?:-o|-O|--output|>>?)\\s*['"]?(?:[^\\s'"]*/)?${escapeRegExp(base)}(?:[\\s'"?]|$)`);
104
- correlated = fetchLines.some((l) => re.test(l) || implicitDownloadBasename(l) === base);
74
+ const targetPath = tm[1].replace(/['"]/g, '');
75
+ if (correlationTargetBasename(targetPath).length >= 3) {
76
+ correlated = isCorrelatedWithFetchDestination(targetPath, fetchLines);
105
77
  }
106
78
  }
107
79
  if (!adjacentFetch && !correlated)
@@ -124,4 +96,111 @@ export function scanChmodFetchCompound(content, alreadyFlaggedLines, lineContext
124
96
  });
125
97
  return findings;
126
98
  }
99
+ /**
100
+ * SMI-6033 Wave 2/3 (Gap 5, tiered per the plan's "Product decision:
101
+ * Gatekeeper-bypass carve-out", resolved 2026-08-14): xattr Gatekeeper-bypass
102
+ * detector.
103
+ *
104
+ * `xattr -c <file>` (clear ALL extended attributes) or
105
+ * `xattr -d com.apple.quarantine <file>` (delete just the quarantine
106
+ * attribute — with or without a combined `-r` recursive flag) strips
107
+ * macOS's "downloaded from the internet" Gatekeeper warning from an
108
+ * unsigned binary.
109
+ *
110
+ * NOT unconditionally standalone-critical: `critical` requires the xattr
111
+ * target's path to be correlated with a fetch destination elsewhere in
112
+ * the content (the shared `isCorrelatedWithFetchDestination` utility, same
113
+ * as `scanChmodFetchCompound` above and `scanArchiveEvasion`'s inline-literal
114
+ * form) — the "download a payload, strip its quarantine bit" attack shape.
115
+ * Uncorrelated usage (e.g. a general macOS-troubleshooting doc mentioning
116
+ * `xattr -c` with no fetched file in scope) stays `medium`, regardless of
117
+ * `isHighTrustAuthor`.
118
+ *
119
+ * `isHighTrustAuthor` (default `false`) is the trust-tier carve-out: when the
120
+ * correlated (would-be-critical) form fires AND the caller has verified the
121
+ * skill's author is high-trust, severity downgrades to `medium` instead of
122
+ * `critical` — a downgrade, not a full exemption, since even a high-trust
123
+ * account can be compromised (a `medium` finding still counts toward the
124
+ * two-distinct-medium-signal escalation rule). This parameter must be sourced
125
+ * from a VERIFIED author signal — the indexer's own resolved GitHub repo
126
+ * owner, never a self-declared `SKILL.md` frontmatter field, which is
127
+ * trivially spoofable. `skill_validate`'s offline call site has no such
128
+ * verified signal (a network call to check it would break the tool's
129
+ * deliberately-offline design, per Gap 7) and so must never pass this
130
+ * parameter — it stays defaulted `false`, keeping correlated xattr
131
+ * always-critical there, full stop. Checksum/signature-verification prose
132
+ * near the command does NOT downgrade a non-high-trust correlated form: it's
133
+ * attacker-authored text and proves nothing about the artifact's safety, so
134
+ * no such logic exists here.
135
+ *
136
+ * Two bounded, ReDoS-safe TRIGGER patterns rather than one combined
137
+ * alternation (mirrors CHMOD_TARGET's capture-then-inspect style):
138
+ * XATTR_CLEAR_ALL matches any `-c`-bearing flag cluster within 40 chars of
139
+ * `xattr` (`-c`, `-cr`, `-rc`, ...); XATTR_DELETE_QUARANTINE matches a
140
+ * `-d`-bearing flag cluster immediately followed by the literal
141
+ * `com.apple.quarantine` attribute name (covers both a combined `-dr`/`-rd`
142
+ * cluster and two separate `-r -d com.apple.quarantine` tokens, since the
143
+ * attribute name always immediately follows whichever token carries `-d`).
144
+ * Reading or writing a DIFFERENT attribute (`-l`, `-p <name>`, `-w <name>
145
+ * <value>`) is not a bypass and does not match either pattern. A second,
146
+ * TARGET-capturing sibling of each pattern (below) exists ONLY to extract the
147
+ * xattr'd file's basename for the correlation check — it never changes
148
+ * whether a line trips the detector.
149
+ */
150
+ const XATTR_CLEAR_ALL = /\bxattr\b[^\n]{0,40}-[a-zA-Z]*c[a-zA-Z]*\b/i;
151
+ const XATTR_DELETE_QUARANTINE = /\bxattr\b[^\n]{0,40}-[a-zA-Z]*d[a-zA-Z]*\s+['"]?com\.apple\.quarantine\b/i;
152
+ const XATTR_CLEAR_ALL_TARGET = /\bxattr\b[^\n]{0,40}-[a-zA-Z]*c[a-zA-Z]*\b\s+['"]?(\S+)/i;
153
+ const XATTR_DELETE_QUARANTINE_TARGET = /\bxattr\b[^\n]{0,40}-[a-zA-Z]*d[a-zA-Z]*\s+['"]?com\.apple\.quarantine\b['"]?\s+['"]?(\S+)/i;
154
+ /**
155
+ * The FILE target of an xattr Gatekeeper-bypass command (real xattr CLI
156
+ * syntax is `xattr [-flags] [attr_name] target...`, so the target always
157
+ * comes right after whichever trigger variant matched) — used only for the
158
+ * fetch-correlation trust-tier check. Extraction failure (no trailing token)
159
+ * simply yields '' (never correlates); it does not affect whether the line
160
+ * trips the underlying detector. SMI-6033 Wave 3: returns the FULL path
161
+ * (quotes stripped), not a bare basename — the shared correlation utility is
162
+ * now directory-aware and must not be handed a path-stripped target.
163
+ */
164
+ function extractXattrTargetPath(line) {
165
+ const m = safeRegexTest(XATTR_CLEAR_ALL_TARGET, line) ??
166
+ safeRegexTest(XATTR_DELETE_QUARANTINE_TARGET, line);
167
+ if (!m)
168
+ return '';
169
+ return m[1].replace(/['"]/g, '');
170
+ }
171
+ export function scanGatekeeperBypass(content, lineContexts, isHighTrustAuthor = false) {
172
+ const findings = [];
173
+ const lines = content.split('\n');
174
+ const contexts = lineContexts ?? analyzeMarkdownContext(content);
175
+ // Fetch-carrying lines, for the correlation precondition (mirrors
176
+ // scanChmodFetchCompound/scanArchiveEvasion's own fetchLines computation).
177
+ const fetchLines = lines.filter((l) => safeRegexTest(FETCH_COMMAND_PATTERN, l) !== null);
178
+ lines.forEach((line, index) => {
179
+ const match = safeRegexTest(XATTR_CLEAR_ALL, line) ?? safeRegexTest(XATTR_DELETE_QUARANTINE, line);
180
+ if (!match)
181
+ return;
182
+ const ctx = contexts[index];
183
+ const inInlineCode = ctx?.isInlineCode && isWithinInlineCode(line, match.index ?? 0);
184
+ const inDocContext = ctx ? isDocumentationContext(ctx) || inInlineCode : false;
185
+ const targetPath = extractXattrTargetPath(line);
186
+ const correlated = correlationTargetBasename(targetPath).length >= 3 &&
187
+ isCorrelatedWithFetchDestination(targetPath, fetchLines);
188
+ // Product decision (2026-08-14): correlated form is standalone-critical
189
+ // UNLESS the caller has verified the author is high-trust (indexer path
190
+ // only — see this function's header). Uncorrelated usage never reaches
191
+ // 'critical' regardless of trust tier.
192
+ const critical = !inDocContext && correlated && !isHighTrustAuthor;
193
+ findings.push({
194
+ type: 'gatekeeper_bypass',
195
+ severity: inDocContext ? 'low' : critical ? 'critical' : 'medium',
196
+ message: `xattr command strips the macOS Gatekeeper quarantine attribute: "${match[0].trim().slice(0, 100)}"`,
197
+ location: line.trim().slice(0, 100),
198
+ lineNumber: index + 1,
199
+ category: 'gatekeeper_bypass',
200
+ inDocumentationContext: inDocContext,
201
+ confidence: inDocContext ? 'low' : 'high',
202
+ });
203
+ });
204
+ return findings;
205
+ }
127
206
  //# sourceMappingURL=SecurityScanner.compound.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"SecurityScanner.compound.js","sourceRoot":"","sources":["../../../../src/security/scanner/SecurityScanner.compound.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"SecurityScanner.compound.js","sourceRoot":"","sources":["../../../../src/security/scanner/SecurityScanner.compound.ts"],"names":[],"mappings":"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"}
@@ -5,10 +5,12 @@
5
5
  */
6
6
  import type { ScanReport, ScannerOptions } from './types.js';
7
7
  import type { LineContext } from './SecurityScanner.helpers.js';
8
- import { analyzeMarkdownContext, isDocumentationContext, isWithinInlineCode, calculateRiskScore } from './SecurityScanner.helpers.js';
8
+ import { analyzeMarkdownContext, isDocumentationContext, isWithinInlineCode } from './SecurityScanner.helpers.js';
9
+ import { extractUrls } from './SecurityScanner.urls.js';
10
+ import { calculateRiskScore } from './SecurityScanner.risk-score.js';
9
11
  import { scanSsrfPatterns } from './SecurityScanner.ssrf.js';
10
12
  import { toMinimalRefs, toSARIF, toGitHubAnnotations, toSummary } from './SecurityScanner.formatters.js';
11
- export { LineContext, analyzeMarkdownContext, isDocumentationContext, isWithinInlineCode, calculateRiskScore, };
13
+ export { LineContext, analyzeMarkdownContext, isDocumentationContext, isWithinInlineCode, calculateRiskScore, extractUrls, };
12
14
  export { scanSsrfPatterns };
13
15
  export { toMinimalRefs, toSARIF, toGitHubAnnotations, toSummary };
14
16
  export declare class SecurityScanner {
@@ -17,7 +19,6 @@ export declare class SecurityScanner {
17
19
  private maxContentLength;
18
20
  private riskThreshold;
19
21
  constructor(options?: ScannerOptions);
20
- private extractUrls;
21
22
  private isAllowedDomain;
22
23
  private scanUrls;
23
24
  private scanJailbreakPatterns;
@@ -25,7 +26,43 @@ export declare class SecurityScanner {
25
26
  private scanAIDefenceVulnerabilities;
26
27
  /** @deprecated Use standalone calculateRiskScore function for new code */
27
28
  calculateRiskScore: typeof calculateRiskScore;
28
- scan(skillId: string, content: string): ScanReport;
29
+ /**
30
+ * Run every content-scanning detector against `content` and return the
31
+ * combined findings. Factored out of `scan()` (SMI-6033 Wave 2, Gap 2) so
32
+ * the encoded-payload detector's recursive rescan of DECODED content can
33
+ * reuse the exact same detector suite instead of a parallel, narrower
34
+ * reimplementation.
35
+ *
36
+ * `skipEncodedPayload` is the STRUCTURAL depth-1 recursion guarantee: the
37
+ * recursive callback passed to `scanEncodedPayload` below always calls this
38
+ * method with `skipEncodedPayload: true`, so a base64 blob discovered
39
+ * INSIDE already-decoded content can never itself be decoded — the inner
40
+ * call cannot reach `scanEncodedPayload` again no matter what the decoded
41
+ * text contains. This disables ONLY the encoded-payload detector on the
42
+ * inner call, not the rest of the suite — a decoded `curl|bash` still
43
+ * trips `code_execution`, decoded secrets still trip `sensitive_path`, etc.
44
+ */
45
+ private runDetectors;
46
+ /**
47
+ * SMI-6033 Wave 3 (Gap 5): `isHighTrustAuthor` (default `false`) is the
48
+ * Gatekeeper-bypass trust-tier carve-out — see `scanGatekeeperBypass`'s own
49
+ * header (`SecurityScanner.compound.ts`) for the full policy. No in-repo
50
+ * caller of this method currently has a verified author signal to pass
51
+ * here (the indexer scans via the edge twin, not this core class); the
52
+ * parameter exists so a future verified-author caller can opt in, and so
53
+ * every existing call site (skill_validate, skill_rescan,
54
+ * bundled-sibling-scan, skill-installation.*) defaults closed by
55
+ * construction, not by convention.
56
+ *
57
+ * SMI-6033 Wave 2 (Gap 8) fix (2026-08-17): `isMarkdown` defaults `true`,
58
+ * preserving byte-identical behavior for every existing caller. Pass
59
+ * `false` when `content` is a real source file, not markdown — see
60
+ * `analyzeMarkdownContext`'s own header (SecurityScanner.helpers.ts) for
61
+ * why the markdown-only indented-code-block heuristic must never apply to
62
+ * non-markdown content. `bundled-sibling-scan.ts`'s
63
+ * `collectExecutableCodeFiles` candidates are the first real caller.
64
+ */
65
+ scan(skillId: string, content: string, isHighTrustAuthor?: boolean, isMarkdown?: boolean): ScanReport;
29
66
  quickCheck(content: string): boolean;
30
67
  addAllowedDomain(domain: string): void;
31
68
  addBlockedPattern(pattern: RegExp): void;
@@ -1 +1 @@
1
- {"version":3,"file":"SecurityScanner.d.ts","sourceRoot":"","sources":["../../../../src/security/scanner/SecurityScanner.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAmB,UAAU,EAAE,cAAc,EAAqB,MAAM,YAAY,CAAA;AAUhG,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAA;AAC/D,OAAO,EACL,sBAAsB,EACtB,sBAAsB,EACtB,kBAAkB,EAClB,kBAAkB,EAEnB,MAAM,8BAA8B,CAAA;AAGrC,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAA;AA0B5D,OAAO,EACL,aAAa,EACb,OAAO,EACP,mBAAmB,EACnB,SAAS,EACV,MAAM,iCAAiC,CAAA;AAGxC,OAAO,EACL,WAAW,EACX,sBAAsB,EACtB,sBAAsB,EACtB,kBAAkB,EAClB,kBAAkB,GACnB,CAAA;AACD,OAAO,EAAE,gBAAgB,EAAE,CAAA;AAC3B,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,mBAAmB,EAAE,SAAS,EAAE,CAAA;AAEjE,qBAAa,eAAe;IAC1B,OAAO,CAAC,cAAc,CAAa;IACnC,OAAO,CAAC,eAAe,CAAU;IACjC,OAAO,CAAC,gBAAgB,CAAQ;IAChC,OAAO,CAAC,aAAa,CAAQ;gBAEjB,OAAO,GAAE,cAAmB;IAOxC,OAAO,CAAC,WAAW;IAenB,OAAO,CAAC,eAAe;IAYvB,OAAO,CAAC,QAAQ;IAmBhB,OAAO,CAAC,qBAAqB;IAkB7B,OAAO,CAAC,sBAAsB;IA6D9B,OAAO,CAAC,4BAA4B;IAkBpC,0EAA0E;IAC1E,kBAAkB,4BAAqB;IAEvC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU;IA2FlD,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAOpC,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAItC,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAKxC,MAAM,CAAC,aAAa,uBAAgB;IACpC,MAAM,CAAC,OAAO,iBAAU;IACxB,MAAM,CAAC,mBAAmB,6BAAsB;IAChD,MAAM,CAAC,SAAS,mBAAY;CAC7B;AAED,eAAe,eAAe,CAAA"}
1
+ {"version":3,"file":"SecurityScanner.d.ts","sourceRoot":"","sources":["../../../../src/security/scanner/SecurityScanner.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAmB,UAAU,EAAE,cAAc,EAAqB,MAAM,YAAY,CAAA;AAUhG,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAA;AAC/D,OAAO,EACL,sBAAsB,EACtB,sBAAsB,EACtB,kBAAkB,EAEnB,MAAM,8BAA8B,CAAA;AAErC,OAAO,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAA;AAEvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,iCAAiC,CAAA;AAGpE,OAAO,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAA;AAsC5D,OAAO,EACL,aAAa,EACb,OAAO,EACP,mBAAmB,EACnB,SAAS,EACV,MAAM,iCAAiC,CAAA;AAGxC,OAAO,EACL,WAAW,EACX,sBAAsB,EACtB,sBAAsB,EACtB,kBAAkB,EAClB,kBAAkB,EAClB,WAAW,GACZ,CAAA;AACD,OAAO,EAAE,gBAAgB,EAAE,CAAA;AAC3B,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,mBAAmB,EAAE,SAAS,EAAE,CAAA;AAEjE,qBAAa,eAAe;IAC1B,OAAO,CAAC,cAAc,CAAa;IACnC,OAAO,CAAC,eAAe,CAAU;IACjC,OAAO,CAAC,gBAAgB,CAAQ;IAChC,OAAO,CAAC,aAAa,CAAQ;gBAEjB,OAAO,GAAE,cAAmB;IAOxC,OAAO,CAAC,eAAe;IAYvB,OAAO,CAAC,QAAQ;IAmBhB,OAAO,CAAC,qBAAqB;IAkB7B,OAAO,CAAC,sBAAsB;IA6D9B,OAAO,CAAC,4BAA4B;IAkBpC,0EAA0E;IAC1E,kBAAkB,4BAAqB;IAEvC;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,YAAY;IAwGpB;;;;;;;;;;;;;;;;;;OAkBG;IACH,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,iBAAiB,UAAQ,EAAE,UAAU,UAAO,GAAG,UAAU;IA+ChG,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO;IAOpC,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAItC,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI;IAKxC,MAAM,CAAC,aAAa,uBAAgB;IACpC,MAAM,CAAC,OAAO,iBAAU;IACxB,MAAM,CAAC,mBAAmB,6BAAsB;IAChD,MAAM,CAAC,SAAS,mBAAY;CAC7B;AAED,eAAe,eAAe,CAAA"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Security Scanner — decoy/misdirection URL-target heuristic
3
+ * @module @skillsmith/core/security/scanner/SecurityScanner.decoy
4
+ *
5
+ * SMI-6033 Wave 4 (Gap 6): reuses the typosquat detector's brand data —
6
+ * `BRAND_ALIASES` (typosquat.ts) and `AUTHORITY_CLAIMING_AFFIXES`
7
+ * (typosquat.ts, exported for exactly this reuse as part of Wave 1's Gap 6
8
+ * prerequisite) — to catch a fetch/exec instruction whose surrounding prose
9
+ * claims a specific vendor (a brand token, optionally reinforced by an
10
+ * authority-claiming affix like "official"/"verified") while the fetch
11
+ * target's actual domain does not belong to that vendor and is not in the
12
+ * general `DEFAULT_ALLOWED_DOMAINS` allowlist.
13
+ *
14
+ * Per the plan's §9 reconciliation table: `decoy_misdirection` is "N/A —
15
+ * never standalone... Approximate NL heuristic by construction; co-signal
16
+ * required (medium tier), unchanged." This detector therefore NEVER emits
17
+ * `high`/`critical` — only `medium`, or nothing (see the doc-context
18
+ * downgrade below, which uses `low`, not a third severity choice for the
19
+ * non-doc case). The co-signal mechanism that would let this medium finding
20
+ * contribute toward escalating a separate weak `code_execution` finding is a
21
+ * SEPARATE dispatch's job — `SecurityScanner.exec.ts`'s
22
+ * `CO_SIGNAL_MIN_SEVERITY` replacement — and is NOT implemented in this file.
23
+ *
24
+ * Design note, NOT explicit in the plan text and flagged here for review:
25
+ * the plan says to compare "the claimed vendor's canonical domain" against
26
+ * the fetch target's domain, but `BRAND_ALIASES`'s values are GitHub OWNER
27
+ * SLUGS (`anthropics`, `google-gemini`, ...) — they answer "whose GitHub org
28
+ * publishes this," not "what is this vendor's real website." A small
29
+ * curated `BRAND_CANONICAL_DOMAINS` map below, scoped to the exact same six
30
+ * brand tokens `BRAND_ALIASES` already curates, fills that gap; a brand
31
+ * token with no entry here is not currently possible (the two maps share the
32
+ * same key set by construction) but the lookup fails safe (empty domain
33
+ * list -> never matches -> never suppresses) if that ever drifts.
34
+ *
35
+ * Second design note: the plan text reads "a brand token OR an
36
+ * authority-claiming phrase implying a specific vendor." An authority-
37
+ * claiming affix ALONE ("the official installer") never names a specific
38
+ * vendor and so cannot resolve a canonical domain to compare against — this
39
+ * detector therefore requires a brand-token match to identify "the claimed
40
+ * vendor" at all (necessary for the domain comparison to mean anything), and
41
+ * treats a co-occurring authority-claiming affix as a confidence booster
42
+ * (medium -> high), not an alternate, independently-sufficient trigger. This
43
+ * mirrors typosquat.ts's own rule 3 (`hasBrandToken && hasAuthorityAffix`),
44
+ * which also requires both signals together.
45
+ *
46
+ * Two fixes from adversarial review (2026-08-16 — see
47
+ * docs/internal/code_review/2026-08-15-smi6033-wave4-escalation-model.md
48
+ * for the full account):
49
+ *
50
+ * 1. Fetch-target correlation. The original version treated ANY URL on a
51
+ * line that ALSO matched the generic FETCH_COMMAND_PATTERN as the fetch
52
+ * target — including a URL that was merely mentioned in prose alongside
53
+ * an unrelated fetch-verb usage on the same line (e.g.
54
+ * "curl --version; see mirror documentation at <url>", where curl is
55
+ * checking its own version, not fetching that URL). Replaced with
56
+ * `isActualFetchTarget`, which requires the fetch verb to be
57
+ * IMMEDIATELY followed (only flag-like tokens and whitespace, no
58
+ * command separator, no prose) by the URL — i.e. the URL must actually
59
+ * be the verb's argument.
60
+ *
61
+ * 2. Authority-affix proximity. `hasAuthorityAffix` used to scan the ENTIRE
62
+ * ±5-line window independently of where the brand token itself was
63
+ * found, so an unrelated authority phrase elsewhere in the window (e.g.
64
+ * "for official documentation on Python packaging, see PEP 517") could
65
+ * wrongly boost confidence to 'high' for a brand claim it has nothing to
66
+ * do with. Now scoped to a tight window around the brand token's OWN
67
+ * line (`DECOY_AUTHORITY_AFFIX_PROXIMITY_LINES`).
68
+ */
69
+ import type { SecurityFinding } from './types.js';
70
+ import type { LineContext } from './SecurityScanner.helpers.js';
71
+ export declare function scanDecoyMisdirection(content: string, lineContexts?: LineContext[]): SecurityFinding[];
72
+ //# sourceMappingURL=SecurityScanner.decoy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SecurityScanner.decoy.d.ts","sourceRoot":"","sources":["../../../../src/security/scanner/SecurityScanner.decoy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmEG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAA;AAuK/D,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,EACf,YAAY,CAAC,EAAE,WAAW,EAAE,GAC3B,eAAe,EAAE,CAiDnB"}