@trailofbits/vsix-audit 0.1.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 (197) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +281 -0
  3. package/dist/cli.d.ts +3 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +703 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/index.d.ts +3 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +4 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/scanner/batch.d.ts +12 -0
  12. package/dist/scanner/batch.d.ts.map +1 -0
  13. package/dist/scanner/batch.js +104 -0
  14. package/dist/scanner/batch.js.map +1 -0
  15. package/dist/scanner/bundler.d.ts +35 -0
  16. package/dist/scanner/bundler.d.ts.map +1 -0
  17. package/dist/scanner/bundler.js +120 -0
  18. package/dist/scanner/bundler.js.map +1 -0
  19. package/dist/scanner/cache.d.ts +45 -0
  20. package/dist/scanner/cache.d.ts.map +1 -0
  21. package/dist/scanner/cache.js +153 -0
  22. package/dist/scanner/cache.js.map +1 -0
  23. package/dist/scanner/cache.test.d.ts +2 -0
  24. package/dist/scanner/cache.test.d.ts.map +1 -0
  25. package/dist/scanner/cache.test.js +149 -0
  26. package/dist/scanner/cache.test.js.map +1 -0
  27. package/dist/scanner/capabilities.d.ts +29 -0
  28. package/dist/scanner/capabilities.d.ts.map +1 -0
  29. package/dist/scanner/capabilities.js +217 -0
  30. package/dist/scanner/capabilities.js.map +1 -0
  31. package/dist/scanner/checks/ast.d.ts +3 -0
  32. package/dist/scanner/checks/ast.d.ts.map +1 -0
  33. package/dist/scanner/checks/ast.js +469 -0
  34. package/dist/scanner/checks/ast.js.map +1 -0
  35. package/dist/scanner/checks/ast.test.d.ts +2 -0
  36. package/dist/scanner/checks/ast.test.d.ts.map +1 -0
  37. package/dist/scanner/checks/ast.test.js +389 -0
  38. package/dist/scanner/checks/ast.test.js.map +1 -0
  39. package/dist/scanner/checks/behavioral.d.ts +3 -0
  40. package/dist/scanner/checks/behavioral.d.ts.map +1 -0
  41. package/dist/scanner/checks/behavioral.js +367 -0
  42. package/dist/scanner/checks/behavioral.js.map +1 -0
  43. package/dist/scanner/checks/blocklist.d.ts +3 -0
  44. package/dist/scanner/checks/blocklist.d.ts.map +1 -0
  45. package/dist/scanner/checks/blocklist.js +32 -0
  46. package/dist/scanner/checks/blocklist.js.map +1 -0
  47. package/dist/scanner/checks/blocklist.test.d.ts +2 -0
  48. package/dist/scanner/checks/blocklist.test.d.ts.map +1 -0
  49. package/dist/scanner/checks/blocklist.test.js +74 -0
  50. package/dist/scanner/checks/blocklist.test.js.map +1 -0
  51. package/dist/scanner/checks/chains.d.ts +35 -0
  52. package/dist/scanner/checks/chains.d.ts.map +1 -0
  53. package/dist/scanner/checks/chains.js +505 -0
  54. package/dist/scanner/checks/chains.js.map +1 -0
  55. package/dist/scanner/checks/chains.test.d.ts +2 -0
  56. package/dist/scanner/checks/chains.test.d.ts.map +1 -0
  57. package/dist/scanner/checks/chains.test.js +250 -0
  58. package/dist/scanner/checks/chains.test.js.map +1 -0
  59. package/dist/scanner/checks/dataflow.d.ts +3 -0
  60. package/dist/scanner/checks/dataflow.d.ts.map +1 -0
  61. package/dist/scanner/checks/dataflow.js +316 -0
  62. package/dist/scanner/checks/dataflow.js.map +1 -0
  63. package/dist/scanner/checks/dependencies.d.ts +13 -0
  64. package/dist/scanner/checks/dependencies.d.ts.map +1 -0
  65. package/dist/scanner/checks/dependencies.js +225 -0
  66. package/dist/scanner/checks/dependencies.js.map +1 -0
  67. package/dist/scanner/checks/dependencies.test.d.ts +2 -0
  68. package/dist/scanner/checks/dependencies.test.d.ts.map +1 -0
  69. package/dist/scanner/checks/dependencies.test.js +248 -0
  70. package/dist/scanner/checks/dependencies.test.js.map +1 -0
  71. package/dist/scanner/checks/finding-quality.test.d.ts +8 -0
  72. package/dist/scanner/checks/finding-quality.test.d.ts.map +1 -0
  73. package/dist/scanner/checks/finding-quality.test.js +164 -0
  74. package/dist/scanner/checks/finding-quality.test.js.map +1 -0
  75. package/dist/scanner/checks/ioc.d.ts +20 -0
  76. package/dist/scanner/checks/ioc.d.ts.map +1 -0
  77. package/dist/scanner/checks/ioc.js +234 -0
  78. package/dist/scanner/checks/ioc.js.map +1 -0
  79. package/dist/scanner/checks/ioc.test.d.ts +2 -0
  80. package/dist/scanner/checks/ioc.test.d.ts.map +1 -0
  81. package/dist/scanner/checks/ioc.test.js +298 -0
  82. package/dist/scanner/checks/ioc.test.js.map +1 -0
  83. package/dist/scanner/checks/manifest.d.ts +6 -0
  84. package/dist/scanner/checks/manifest.d.ts.map +1 -0
  85. package/dist/scanner/checks/manifest.js +123 -0
  86. package/dist/scanner/checks/manifest.js.map +1 -0
  87. package/dist/scanner/checks/manifest.test.d.ts +2 -0
  88. package/dist/scanner/checks/manifest.test.d.ts.map +1 -0
  89. package/dist/scanner/checks/manifest.test.js +108 -0
  90. package/dist/scanner/checks/manifest.test.js.map +1 -0
  91. package/dist/scanner/checks/obfuscation.d.ts +3 -0
  92. package/dist/scanner/checks/obfuscation.d.ts.map +1 -0
  93. package/dist/scanner/checks/obfuscation.js +432 -0
  94. package/dist/scanner/checks/obfuscation.js.map +1 -0
  95. package/dist/scanner/checks/obfuscation.test.d.ts +2 -0
  96. package/dist/scanner/checks/obfuscation.test.d.ts.map +1 -0
  97. package/dist/scanner/checks/obfuscation.test.js +399 -0
  98. package/dist/scanner/checks/obfuscation.test.js.map +1 -0
  99. package/dist/scanner/checks/package.d.ts +17 -0
  100. package/dist/scanner/checks/package.d.ts.map +1 -0
  101. package/dist/scanner/checks/package.js +422 -0
  102. package/dist/scanner/checks/package.js.map +1 -0
  103. package/dist/scanner/checks/package.test.d.ts +2 -0
  104. package/dist/scanner/checks/package.test.d.ts.map +1 -0
  105. package/dist/scanner/checks/package.test.js +518 -0
  106. package/dist/scanner/checks/package.test.js.map +1 -0
  107. package/dist/scanner/checks/patterns.d.ts +5 -0
  108. package/dist/scanner/checks/patterns.d.ts.map +1 -0
  109. package/dist/scanner/checks/patterns.js +251 -0
  110. package/dist/scanner/checks/patterns.js.map +1 -0
  111. package/dist/scanner/checks/patterns.test.d.ts +2 -0
  112. package/dist/scanner/checks/patterns.test.d.ts.map +1 -0
  113. package/dist/scanner/checks/patterns.test.js +147 -0
  114. package/dist/scanner/checks/patterns.test.js.map +1 -0
  115. package/dist/scanner/checks/unicode.d.ts +3 -0
  116. package/dist/scanner/checks/unicode.d.ts.map +1 -0
  117. package/dist/scanner/checks/unicode.js +247 -0
  118. package/dist/scanner/checks/unicode.js.map +1 -0
  119. package/dist/scanner/checks/unicode.test.d.ts +2 -0
  120. package/dist/scanner/checks/unicode.test.d.ts.map +1 -0
  121. package/dist/scanner/checks/unicode.test.js +202 -0
  122. package/dist/scanner/checks/unicode.test.js.map +1 -0
  123. package/dist/scanner/checks/yara.d.ts +23 -0
  124. package/dist/scanner/checks/yara.d.ts.map +1 -0
  125. package/dist/scanner/checks/yara.js +349 -0
  126. package/dist/scanner/checks/yara.js.map +1 -0
  127. package/dist/scanner/checks/yara.test.d.ts +2 -0
  128. package/dist/scanner/checks/yara.test.d.ts.map +1 -0
  129. package/dist/scanner/checks/yara.test.js +126 -0
  130. package/dist/scanner/checks/yara.test.js.map +1 -0
  131. package/dist/scanner/constants.d.ts +18 -0
  132. package/dist/scanner/constants.d.ts.map +1 -0
  133. package/dist/scanner/constants.js +37 -0
  134. package/dist/scanner/constants.js.map +1 -0
  135. package/dist/scanner/detection-coverage.test.d.ts +2 -0
  136. package/dist/scanner/detection-coverage.test.d.ts.map +1 -0
  137. package/dist/scanner/detection-coverage.test.js +216 -0
  138. package/dist/scanner/detection-coverage.test.js.map +1 -0
  139. package/dist/scanner/download.d.ts +76 -0
  140. package/dist/scanner/download.d.ts.map +1 -0
  141. package/dist/scanner/download.js +339 -0
  142. package/dist/scanner/download.js.map +1 -0
  143. package/dist/scanner/download.test.d.ts +2 -0
  144. package/dist/scanner/download.test.d.ts.map +1 -0
  145. package/dist/scanner/download.test.js +149 -0
  146. package/dist/scanner/download.test.js.map +1 -0
  147. package/dist/scanner/index.d.ts +8 -0
  148. package/dist/scanner/index.d.ts.map +1 -0
  149. package/dist/scanner/index.js +167 -0
  150. package/dist/scanner/index.js.map +1 -0
  151. package/dist/scanner/index.test.d.ts +2 -0
  152. package/dist/scanner/index.test.d.ts.map +1 -0
  153. package/dist/scanner/index.test.js +71 -0
  154. package/dist/scanner/index.test.js.map +1 -0
  155. package/dist/scanner/loaders/zoo.d.ts +3 -0
  156. package/dist/scanner/loaders/zoo.d.ts.map +1 -0
  157. package/dist/scanner/loaders/zoo.js +112 -0
  158. package/dist/scanner/loaders/zoo.js.map +1 -0
  159. package/dist/scanner/types.d.ts +118 -0
  160. package/dist/scanner/types.d.ts.map +1 -0
  161. package/dist/scanner/types.js +2 -0
  162. package/dist/scanner/types.js.map +1 -0
  163. package/dist/scanner/utils.d.ts +14 -0
  164. package/dist/scanner/utils.d.ts.map +1 -0
  165. package/dist/scanner/utils.js +25 -0
  166. package/dist/scanner/utils.js.map +1 -0
  167. package/dist/scanner/vsix.d.ts +6 -0
  168. package/dist/scanner/vsix.d.ts.map +1 -0
  169. package/dist/scanner/vsix.js +213 -0
  170. package/dist/scanner/vsix.js.map +1 -0
  171. package/dist/scanner/vsix.test.d.ts +2 -0
  172. package/dist/scanner/vsix.test.d.ts.map +1 -0
  173. package/dist/scanner/vsix.test.js +355 -0
  174. package/dist/scanner/vsix.test.js.map +1 -0
  175. package/package.json +60 -0
  176. package/zoo/blocklist/extensions.json +201 -0
  177. package/zoo/iocs/blockchain-extensions.txt +21 -0
  178. package/zoo/iocs/c2-domains.txt +50 -0
  179. package/zoo/iocs/c2-ips.txt +24 -0
  180. package/zoo/iocs/hashes.txt +47 -0
  181. package/zoo/iocs/malicious-npm.txt +85 -0
  182. package/zoo/iocs/wallets.txt +18 -0
  183. package/zoo/signatures/yara/README.md +46 -0
  184. package/zoo/signatures/yara/blockchain_c2.yar +48 -0
  185. package/zoo/signatures/yara/code_execution.yar +165 -0
  186. package/zoo/signatures/yara/credential_harvesting.yar +116 -0
  187. package/zoo/signatures/yara/crypto_wallet_targeting.yar +92 -0
  188. package/zoo/signatures/yara/data_exfiltration.yar +207 -0
  189. package/zoo/signatures/yara/google_calendar_c2.yar +187 -0
  190. package/zoo/signatures/yara/messaging_c2.yar +103 -0
  191. package/zoo/signatures/yara/multi_stage_attacks.yar +331 -0
  192. package/zoo/signatures/yara/obfuscation_patterns.yar +208 -0
  193. package/zoo/signatures/yara/powershell_attacks.yar +116 -0
  194. package/zoo/signatures/yara/rat_capabilities.yar +243 -0
  195. package/zoo/signatures/yara/self_propagation.yar +239 -0
  196. package/zoo/signatures/yara/unicode_stealth.yar +48 -0
  197. package/zoo/signatures/yara/websocket_c2.yar +83 -0
@@ -0,0 +1,247 @@
1
+ import { detectBundler } from "../bundler.js";
2
+ import { isScannable, SCANNABLE_EXTENSIONS_UNICODE } from "../constants.js";
3
+ function findLineAndColumn(content, index) {
4
+ const beforeMatch = content.slice(0, index);
5
+ const lines = beforeMatch.split("\n");
6
+ const line = lines.length;
7
+ const column = (lines.at(-1)?.length ?? 0) + 1;
8
+ return { line, column };
9
+ }
10
+ function getContext(content, index, length) {
11
+ const start = Math.max(0, index - 20);
12
+ const end = Math.min(content.length, index + length + 20);
13
+ let ctx = content.slice(start, end);
14
+ if (start > 0)
15
+ ctx = "..." + ctx;
16
+ if (end < content.length)
17
+ ctx = ctx + "...";
18
+ return ctx.replace(/[\n\r]/g, "\\n").slice(0, 80);
19
+ }
20
+ // Zero-width characters: U+200B-200D, U+FEFF
21
+ const ZERO_WIDTH_REGEX = /[\u200B-\u200D\uFEFF]/g;
22
+ // Variation selectors: U+FE00-FE0F (GlassWorm technique)
23
+ const VARIATION_SELECTOR_REGEX = /[\uFE00-\uFE0F]/g;
24
+ // Bidirectional overrides: U+202A-202E (Trojan Source attack)
25
+ const BIDI_OVERRIDE_REGEX = /[\u202A-\u202E]/g;
26
+ // Unicode escapes for ASCII: \u00XX where XX is 20-7E (printable ASCII)
27
+ // This is obfuscation - using unicode escapes for normal characters
28
+ const UNICODE_ASCII_ESCAPE_REGEX = /\\u00[2-7][0-9a-fA-F]/g;
29
+ // Cyrillic homoglyphs that look like Latin letters
30
+ // а(U+0430)/a, с(U+0441)/c, е(U+0435)/e, о(U+043E)/o, р(U+0440)/p, х(U+0445)/x, у(U+0443)/y
31
+ // Also uppercase: А(U+0410)/A, В(U+0412)/B, С(U+0421)/C, Е(U+0415)/E, Н(U+041D)/H, etc.
32
+ const CYRILLIC_LOOKALIKE_REGEX = /[\u0430\u0441\u0435\u043E\u0440\u0445\u0443\u0410\u0412\u0421\u0415\u041D\u041A\u041C\u041E\u0420\u0422\u0425]/g;
33
+ // Additional invisible/confusable characters
34
+ // U+00AD Soft hyphen, U+034F Combining grapheme joiner, U+115F-1160 Hangul fillers
35
+ // U+17B4-17B5 Khmer vowels, U+180E Mongolian vowel separator
36
+ const OTHER_INVISIBLE_REGEX = /[\u00AD\u034F\u115F\u1160\u17B4\u17B5\u180E\u2060-\u2064\u206A-\u206F]/g;
37
+ function detectPattern(content, regex, minMatches = 1) {
38
+ const matches = [];
39
+ const r = new RegExp(regex.source, regex.flags);
40
+ let match;
41
+ while ((match = r.exec(content)) !== null) {
42
+ const { line, column } = findLineAndColumn(content, match.index);
43
+ matches.push({
44
+ line,
45
+ column,
46
+ matched: match[0],
47
+ context: getContext(content, match.index, match[0].length),
48
+ });
49
+ }
50
+ return matches.length >= minMatches ? matches : [];
51
+ }
52
+ /**
53
+ * Check if the file appears to be primarily i18n/localization content.
54
+ * These files naturally have high Unicode diversity and should be treated differently.
55
+ */
56
+ function isI18nFile(filename, content) {
57
+ // Check filename patterns
58
+ const i18nPatterns = [
59
+ /locales?\//i,
60
+ /i18n\//i,
61
+ /translations?\//i,
62
+ /lang\//i,
63
+ /messages/i,
64
+ /\.l10n\./i,
65
+ /nls\./i,
66
+ ];
67
+ if (i18nPatterns.some((p) => p.test(filename))) {
68
+ return true;
69
+ }
70
+ // Check if content looks like localization JSON
71
+ // (has many string keys with translated values)
72
+ if (filename.endsWith(".json")) {
73
+ const keyValuePairs = (content.match(/"[^"]+"\s*:\s*"[^"]+"/g) ?? []).length;
74
+ const lines = content.split("\n").length;
75
+ // If more than 50% of lines are key-value pairs, likely i18n
76
+ if (keyValuePairs / lines > 0.5 && keyValuePairs > 10) {
77
+ return true;
78
+ }
79
+ }
80
+ return false;
81
+ }
82
+ /**
83
+ * Check if invisible characters are in proximity to execution patterns.
84
+ * This helps distinguish malicious steganography from benign Unicode use.
85
+ */
86
+ function hasExecutionInProximity(content, invisibleIndex, proximityChars = 200) {
87
+ const start = Math.max(0, invisibleIndex - proximityChars);
88
+ const end = Math.min(content.length, invisibleIndex + proximityChars);
89
+ const region = content.slice(start, end);
90
+ const execPatterns = [
91
+ /eval\s*\(/i,
92
+ /Function\s*\(/i,
93
+ /exec\s*\(/i,
94
+ /spawn\s*\(/i,
95
+ /execSync\s*\(/i,
96
+ /child_process/i,
97
+ /\.\s*call\s*\(/,
98
+ /\.\s*apply\s*\(/,
99
+ /new\s+Function/i,
100
+ /atob\s*\(/i,
101
+ /Buffer\.from/i,
102
+ ];
103
+ return execPatterns.some((p) => p.test(region));
104
+ }
105
+ // Rules that should be skipped for bundled code (they're noisy on minified i18n)
106
+ const SKIP_FOR_BUNDLED = new Set([
107
+ "ZERO_WIDTH_CHARS",
108
+ "CYRILLIC_HOMOGLYPH",
109
+ "OTHER_INVISIBLE_CHARS",
110
+ "UNICODE_ASCII_ESCAPE",
111
+ ]);
112
+ const UNICODE_RULES = [
113
+ {
114
+ id: "ZERO_WIDTH_CHARS",
115
+ title: "Zero-width characters detected",
116
+ description: "File contains zero-width Unicode characters (U+200B-200D, U+FEFF). These invisible characters can hide malicious code or be used for steganography.",
117
+ severity: "high",
118
+ detect: (content) => detectPattern(content, ZERO_WIDTH_REGEX, 3),
119
+ },
120
+ {
121
+ id: "VARIATION_SELECTOR",
122
+ title: "Unicode variation selectors detected (GlassWorm technique)",
123
+ description: "File contains many Unicode variation selectors (U+FE00-FE0F). GlassWorm malware uses these to hide executable code. A few variation selectors are normal (emoji formatting), but many indicates hidden data.",
124
+ severity: "critical",
125
+ // Require at least 10 variation selectors - a few are normal for emojis
126
+ // GlassWorm attack uses hundreds to encode hidden payloads
127
+ detect: (content) => detectPattern(content, VARIATION_SELECTOR_REGEX, 10),
128
+ },
129
+ {
130
+ id: "BIDI_OVERRIDE",
131
+ title: "Bidirectional text override detected (Trojan Source)",
132
+ description: "File contains Unicode bidirectional override characters (U+202A-202E). This is the Trojan Source attack technique that can make malicious code appear benign by reordering how text is displayed.",
133
+ severity: "critical",
134
+ detect: (content) => detectPattern(content, BIDI_OVERRIDE_REGEX, 1),
135
+ },
136
+ {
137
+ id: "UNICODE_ASCII_ESCAPE",
138
+ title: "Unicode escape sequences for ASCII characters",
139
+ description: "File uses Unicode escape sequences (\\u00XX) for normal ASCII characters. This is a common obfuscation technique to hide string contents from static analysis.",
140
+ severity: "medium",
141
+ detect: (content) => detectPattern(content, UNICODE_ASCII_ESCAPE_REGEX, 5),
142
+ },
143
+ {
144
+ id: "CYRILLIC_HOMOGLYPH",
145
+ title: "Cyrillic homoglyph characters detected",
146
+ description: "File contains Cyrillic characters that visually resemble Latin letters (e.g., Cyrillic 'а' vs Latin 'a'). This can be used for homoglyph attacks to disguise malicious identifiers.",
147
+ severity: "high",
148
+ detect: (content, filename) => {
149
+ // Only flag in code files, not documentation
150
+ if (filename.endsWith(".md") || filename.endsWith(".txt")) {
151
+ return [];
152
+ }
153
+ // Skip i18n files which legitimately contain Cyrillic
154
+ if (isI18nFile(filename, content)) {
155
+ return [];
156
+ }
157
+ return detectPattern(content, CYRILLIC_LOOKALIKE_REGEX, 1);
158
+ },
159
+ },
160
+ {
161
+ id: "OTHER_INVISIBLE_CHARS",
162
+ title: "Other invisible Unicode characters detected",
163
+ description: "File contains invisible Unicode characters (soft hyphens, combining marks, format controls). These can be used to hide malicious content.",
164
+ severity: "medium",
165
+ detect: (content) => detectPattern(content, OTHER_INVISIBLE_REGEX, 3),
166
+ },
167
+ {
168
+ id: "INVISIBLE_CODE_EXECUTION",
169
+ title: "Invisible characters near code execution",
170
+ description: "File contains many invisible Unicode characters in proximity to code execution functions (eval, Function, exec). This is a strong indicator of hidden malicious code.",
171
+ severity: "critical",
172
+ detect: (content, filename) => {
173
+ // Skip i18n files - these legitimately contain RTL and combining chars
174
+ if (isI18nFile(filename, content)) {
175
+ return [];
176
+ }
177
+ // Check for truly suspicious invisible chars (exclude ZWNJ which is common in RTL text)
178
+ // Only flag: variation selectors (GlassWorm), bidi overrides (Trojan Source)
179
+ // Note: Single ZWSPs are common in UI libraries as constants, so exclude them
180
+ const suspiciousInvisible = new RegExp(`${VARIATION_SELECTOR_REGEX.source}|${BIDI_OVERRIDE_REGEX.source}`, "g");
181
+ // Require at least 5 invisible chars - single chars are often legitimate
182
+ const matches = detectPattern(content, suspiciousInvisible, 5);
183
+ // Only return matches that are actually near execution patterns
184
+ // (within 200 chars of eval, Function, exec, etc.)
185
+ return matches.filter((m) => {
186
+ const matchIndex = content.slice(0, m.line).split("\n").length > 1
187
+ ? content
188
+ .split("\n")
189
+ .slice(0, m.line - 1)
190
+ .join("\n").length + m.column
191
+ : m.column;
192
+ return hasExecutionInProximity(content, matchIndex);
193
+ });
194
+ },
195
+ },
196
+ ];
197
+ export function checkUnicode(contents) {
198
+ const findings = [];
199
+ const seenFindings = new Set();
200
+ for (const [filename, buffer] of contents.files) {
201
+ if (!isScannable(filename, SCANNABLE_EXTENSIONS_UNICODE))
202
+ continue;
203
+ const content = buffer.toString("utf8");
204
+ // Detect bundled code
205
+ const bundlerInfo = detectBundler(content, filename);
206
+ for (const rule of UNICODE_RULES) {
207
+ // Skip noisy rules for bundled code
208
+ if (bundlerInfo.isBundled && SKIP_FOR_BUNDLED.has(rule.id)) {
209
+ continue;
210
+ }
211
+ const matches = rule.detect(content, filename);
212
+ if (matches.length === 0)
213
+ continue;
214
+ // Create one finding per rule per file, with all matches in metadata
215
+ const key = `${rule.id}:${filename}`;
216
+ if (seenFindings.has(key))
217
+ continue;
218
+ seenFindings.add(key);
219
+ const firstMatch = matches[0];
220
+ if (!firstMatch)
221
+ continue;
222
+ findings.push({
223
+ id: rule.id,
224
+ title: rule.title,
225
+ description: rule.description,
226
+ severity: rule.severity,
227
+ category: "unicode",
228
+ location: {
229
+ file: filename,
230
+ line: firstMatch.line,
231
+ column: firstMatch.column,
232
+ },
233
+ metadata: {
234
+ matchCount: matches.length,
235
+ firstMatch: firstMatch.context,
236
+ codePoints: matches
237
+ .slice(0, 5)
238
+ .map((m) => [...m.matched]
239
+ .map((c) => `U+${c.codePointAt(0)?.toString(16).toUpperCase().padStart(4, "0")}`)
240
+ .join(", ")),
241
+ },
242
+ });
243
+ }
244
+ }
245
+ return findings;
246
+ }
247
+ //# sourceMappingURL=unicode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unicode.js","sourceRoot":"","sources":["../../../src/scanner/checks/unicode.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=unicode.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unicode.test.d.ts","sourceRoot":"","sources":["../../../src/scanner/checks/unicode.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,202 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { checkUnicode } from "./unicode.js";
3
+ function makeContents(files) {
4
+ const manifest = {
5
+ name: "test-extension",
6
+ publisher: "test",
7
+ version: "1.0.0",
8
+ };
9
+ const fileMap = new Map();
10
+ for (const [name, content] of Object.entries(files)) {
11
+ fileMap.set(name, Buffer.from(content, "utf8"));
12
+ }
13
+ return { manifest, files: fileMap, basePath: "/test" };
14
+ }
15
+ describe("checkUnicode", () => {
16
+ describe("zero-width characters", () => {
17
+ it("detects zero-width space characters (U+200B)", () => {
18
+ // 3+ occurrences needed
19
+ const content = "const x\u200B = 'a\u200Bb\u200Bc';";
20
+ const contents = makeContents({ "extension.js": content });
21
+ const findings = checkUnicode(contents);
22
+ expect(findings).toHaveLength(1);
23
+ expect(findings.some((f) => f.id === "ZERO_WIDTH_CHARS")).toBe(true);
24
+ expect(findings.some((f) => f.severity === "high")).toBe(true);
25
+ });
26
+ it("detects zero-width joiner (U+200D)", () => {
27
+ const content = "const x\u200D = 'a\u200Db\u200Dc';";
28
+ const contents = makeContents({ "extension.js": content });
29
+ const findings = checkUnicode(contents);
30
+ expect(findings).toHaveLength(1);
31
+ expect(findings.some((f) => f.id === "ZERO_WIDTH_CHARS")).toBe(true);
32
+ });
33
+ it("ignores files with only 1-2 zero-width chars", () => {
34
+ const content = "const x\u200B = 'a\u200Bb';";
35
+ const contents = makeContents({ "extension.js": content });
36
+ const findings = checkUnicode(contents);
37
+ const zeroWidthFinding = findings.find((f) => f.id === "ZERO_WIDTH_CHARS");
38
+ expect(zeroWidthFinding).toBeUndefined();
39
+ });
40
+ });
41
+ describe("variation selectors (GlassWorm technique)", () => {
42
+ it("detects variation selectors (U+FE00-FE0F) when >= 10 present", () => {
43
+ // Implementation requires 10+ variation selectors (GlassWorm uses hundreds)
44
+ // A few are normal for emoji formatting
45
+ const content = "a\uFE00b\uFE01c\uFE02d\uFE03e\uFE04f\uFE05g\uFE06h\uFE07i\uFE08j\uFE09k\uFE0A";
46
+ const contents = makeContents({ "extension.js": content });
47
+ const findings = checkUnicode(contents);
48
+ expect(findings).toHaveLength(1);
49
+ expect(findings.some((f) => f.id === "VARIATION_SELECTOR")).toBe(true);
50
+ expect(findings.some((f) => f.severity === "critical")).toBe(true);
51
+ });
52
+ it("ignores few variation selectors (normal for emoji)", () => {
53
+ // Less than 10 variation selectors should be ignored (normal emoji use)
54
+ const content = "a\uFE00b\uFE01c\uFE02d\uFE0F";
55
+ const contents = makeContents({ "extension.js": content });
56
+ const findings = checkUnicode(contents);
57
+ expect(findings.find((f) => f.id === "VARIATION_SELECTOR")).toBeUndefined();
58
+ });
59
+ });
60
+ describe("bidirectional overrides (Trojan Source)", () => {
61
+ it("detects left-to-right override (U+202D)", () => {
62
+ const content = "const admin\u202D = true;";
63
+ const contents = makeContents({ "extension.js": content });
64
+ const findings = checkUnicode(contents);
65
+ expect(findings).toHaveLength(1);
66
+ expect(findings.some((f) => f.id === "BIDI_OVERRIDE")).toBe(true);
67
+ expect(findings.some((f) => f.severity === "critical")).toBe(true);
68
+ });
69
+ it("detects right-to-left override (U+202E)", () => {
70
+ const content = "const admin\u202E = true;";
71
+ const contents = makeContents({ "extension.js": content });
72
+ const findings = checkUnicode(contents);
73
+ expect(findings).toHaveLength(1);
74
+ expect(findings.some((f) => f.id === "BIDI_OVERRIDE")).toBe(true);
75
+ });
76
+ });
77
+ describe("Unicode ASCII escapes", () => {
78
+ it("detects excessive Unicode escapes for ASCII", () => {
79
+ // Using \\u00XX for normal printable ASCII is suspicious
80
+ const content = "const x = '\\u0068\\u0065\\u006c\\u006c\\u006f\\u0077';"; // "hellow"
81
+ const contents = makeContents({ "extension.js": content });
82
+ const findings = checkUnicode(contents);
83
+ expect(findings).toHaveLength(1);
84
+ expect(findings.some((f) => f.id === "UNICODE_ASCII_ESCAPE")).toBe(true);
85
+ expect(findings.some((f) => f.severity === "medium")).toBe(true);
86
+ });
87
+ it("ignores files with few Unicode escapes", () => {
88
+ const content = "const x = '\\u0068\\u0065';"; // Only 2 escapes
89
+ const contents = makeContents({ "extension.js": content });
90
+ const findings = checkUnicode(contents);
91
+ const escapeFinding = findings.find((f) => f.id === "UNICODE_ASCII_ESCAPE");
92
+ expect(escapeFinding).toBeUndefined();
93
+ });
94
+ });
95
+ describe("Cyrillic homoglyphs", () => {
96
+ it("detects Cyrillic 'а' (U+0430) that looks like Latin 'a'", () => {
97
+ const content = "const \u0430dmin = true;"; // Cyrillic а
98
+ const contents = makeContents({ "extension.js": content });
99
+ const findings = checkUnicode(contents);
100
+ expect(findings).toHaveLength(1);
101
+ expect(findings.some((f) => f.id === "CYRILLIC_HOMOGLYPH")).toBe(true);
102
+ expect(findings.some((f) => f.severity === "high")).toBe(true);
103
+ });
104
+ it("detects Cyrillic 'е' (U+0435) that looks like Latin 'e'", () => {
105
+ const content = "const s\u0435cret = 'password';"; // Cyrillic е
106
+ const contents = makeContents({ "extension.js": content });
107
+ const findings = checkUnicode(contents);
108
+ expect(findings).toHaveLength(1);
109
+ expect(findings.some((f) => f.id === "CYRILLIC_HOMOGLYPH")).toBe(true);
110
+ });
111
+ it("ignores Cyrillic in markdown files", () => {
112
+ const content = "# Hello \u0430nd welcome"; // Cyrillic а in markdown
113
+ const contents = makeContents({ "README.md": content });
114
+ const findings = checkUnicode(contents);
115
+ const cyrillicFinding = findings.find((f) => f.id === "CYRILLIC_HOMOGLYPH");
116
+ expect(cyrillicFinding).toBeUndefined();
117
+ });
118
+ });
119
+ describe("invisible characters near code execution", () => {
120
+ it("flags many invisible chars in file with eval()", () => {
121
+ // Implementation requires 5+ invisible chars near execution patterns
122
+ const content = "const payload\uFE01\uFE02\uFE03\uFE04\uFE05 = 'data';\neval(payload);";
123
+ const contents = makeContents({ "extension.js": content });
124
+ const findings = checkUnicode(contents);
125
+ expect(findings.some((f) => f.id === "INVISIBLE_CODE_EXECUTION")).toBe(true);
126
+ });
127
+ it("flags many invisible chars in file with Function()", () => {
128
+ const content = "const x\uFE01\uFE02\uFE03\uFE04\uFE05 = 1;\nnew Function('return x')();";
129
+ const contents = makeContents({ "extension.js": content });
130
+ const findings = checkUnicode(contents);
131
+ expect(findings.some((f) => f.id === "INVISIBLE_CODE_EXECUTION")).toBe(true);
132
+ });
133
+ it("flags many invisible chars in file with child_process", () => {
134
+ const content = "require('child_process').exec('ls');\nconst hidden\uFE01\uFE02\uFE03\uFE04\uFE05 = 1;";
135
+ const contents = makeContents({ "extension.js": content });
136
+ const findings = checkUnicode(contents);
137
+ expect(findings.some((f) => f.id === "INVISIBLE_CODE_EXECUTION")).toBe(true);
138
+ });
139
+ it("does NOT flag few invisible chars even with execution context", () => {
140
+ // Single invisible char isn't enough - needs 5+
141
+ const content = "const x\uFE01 = 1;\neval(x);";
142
+ const contents = makeContents({ "extension.js": content });
143
+ const findings = checkUnicode(contents);
144
+ expect(findings.some((f) => f.id === "INVISIBLE_CODE_EXECUTION")).toBe(false);
145
+ });
146
+ });
147
+ describe("file filtering", () => {
148
+ it("scans JavaScript files", () => {
149
+ // Use bidi override which triggers with just 1 occurrence
150
+ const content = "const admin\u202E = true;";
151
+ const contents = makeContents({ "test.js": content });
152
+ const findings = checkUnicode(contents);
153
+ expect(findings.length).toBeGreaterThan(0);
154
+ });
155
+ it("scans TypeScript files", () => {
156
+ const content = "const admin\u202E: boolean = true;";
157
+ const contents = makeContents({ "test.ts": content });
158
+ const findings = checkUnicode(contents);
159
+ expect(findings.length).toBeGreaterThan(0);
160
+ });
161
+ it("scans JSON files", () => {
162
+ const content = '{"key\u202E": "value"}';
163
+ const contents = makeContents({ "test.json": content });
164
+ const findings = checkUnicode(contents);
165
+ expect(findings.length).toBeGreaterThan(0);
166
+ });
167
+ it("ignores binary files", () => {
168
+ // Even critical patterns should be ignored in binary files
169
+ const content = "\u202E\u202D\u202C";
170
+ const contents = makeContents({ "test.png": content });
171
+ const findings = checkUnicode(contents);
172
+ expect(findings).toHaveLength(0);
173
+ });
174
+ });
175
+ describe("metadata", () => {
176
+ it("includes match count in metadata", () => {
177
+ // Use BIDI_OVERRIDE which triggers with 1+ occurrences
178
+ const content = "a\u202Db\u202Ec\u202D";
179
+ const contents = makeContents({ "test.js": content });
180
+ const findings = checkUnicode(contents);
181
+ const finding = findings.find((f) => f.id === "BIDI_OVERRIDE");
182
+ expect(finding?.metadata?.["matchCount"]).toBe(3);
183
+ });
184
+ it("includes code points in metadata", () => {
185
+ const content = "const admin\u202E = true;";
186
+ const contents = makeContents({ "test.js": content });
187
+ const findings = checkUnicode(contents);
188
+ const finding = findings.find((f) => f.id === "BIDI_OVERRIDE");
189
+ const codePoints = finding?.metadata?.["codePoints"];
190
+ expect(codePoints).toBeDefined();
191
+ expect(codePoints?.some((cp) => cp.includes("202E"))).toBe(true);
192
+ });
193
+ it("includes line number in location", () => {
194
+ const content = "line1\nline2\nconst admin\u202E = true;";
195
+ const contents = makeContents({ "test.js": content });
196
+ const findings = checkUnicode(contents);
197
+ const finding = findings.find((f) => f.id === "BIDI_OVERRIDE");
198
+ expect(finding?.location?.line).toBe(3);
199
+ });
200
+ });
201
+ });
202
+ //# sourceMappingURL=unicode.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unicode.test.js","sourceRoot":"","sources":["../../../src/scanner/checks/unicode.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import type { Finding, VsixContents } from "../types.js";
2
+ /**
3
+ * Get the default YARA rules directory (cached)
4
+ */
5
+ export declare function getDefaultYaraRulesDir(): Promise<string>;
6
+ export declare const DEFAULT_YARA_RULES_DIR: string;
7
+ /**
8
+ * Check if YARA-X is installed and available
9
+ */
10
+ export declare function isYaraAvailable(): Promise<boolean>;
11
+ /**
12
+ * Get YARA-X version string
13
+ */
14
+ export declare function getYaraVersion(): Promise<string | null>;
15
+ /**
16
+ * List all YARA rule files in a directory
17
+ */
18
+ export declare function listYaraRules(rulesDir: string): Promise<string[]>;
19
+ /**
20
+ * Run YARA rules against extension contents
21
+ */
22
+ export declare function checkYara(contents: VsixContents, rulesDir?: string): Promise<Finding[]>;
23
+ //# sourceMappingURL=yara.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"yara.d.ts","sourceRoot":"","sources":["../../../src/scanner/checks/yara.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AA+GzD;;GAEG;AACH,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,MAAM,CAAC,CAK9D;AAGD,eAAO,MAAM,sBAAsB,QAQlC,CAAC;AASF;;GAEG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,OAAO,CAAC,CAOxD;AAED;;GAEG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAO7D;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAOvE;AAqHD;;GAEG;AACH,wBAAsB,SAAS,CAAC,QAAQ,EAAE,YAAY,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CA+G7F"}