@cyberhub/shieldpm 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.
- package/LICENSE +21 -0
- package/README.md +239 -0
- package/dist/analyzer/static.d.ts +35 -0
- package/dist/analyzer/static.d.ts.map +1 -0
- package/dist/analyzer/static.js +416 -0
- package/dist/analyzer/static.js.map +1 -0
- package/dist/analyzer/typosquat.d.ts +30 -0
- package/dist/analyzer/typosquat.d.ts.map +1 -0
- package/dist/analyzer/typosquat.js +211 -0
- package/dist/analyzer/typosquat.js.map +1 -0
- package/dist/cli.d.ts +10 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +621 -0
- package/dist/cli.js.map +1 -0
- package/dist/diff/dependency.d.ts +51 -0
- package/dist/diff/dependency.d.ts.map +1 -0
- package/dist/diff/dependency.js +222 -0
- package/dist/diff/dependency.js.map +1 -0
- package/dist/fingerprint/profile.d.ts +68 -0
- package/dist/fingerprint/profile.d.ts.map +1 -0
- package/dist/fingerprint/profile.js +233 -0
- package/dist/fingerprint/profile.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/monitor/permissions.d.ts +45 -0
- package/dist/monitor/permissions.d.ts.map +1 -0
- package/dist/monitor/permissions.js +265 -0
- package/dist/monitor/permissions.js.map +1 -0
- package/dist/sandbox/runner.d.ts +46 -0
- package/dist/sandbox/runner.d.ts.map +1 -0
- package/dist/sandbox/runner.js +216 -0
- package/dist/sandbox/runner.js.map +1 -0
- package/dist/utils/colors.d.ts +31 -0
- package/dist/utils/colors.d.ts.map +1 -0
- package/dist/utils/colors.js +54 -0
- package/dist/utils/colors.js.map +1 -0
- package/dist/utils/logger.d.ts +26 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +77 -0
- package/dist/utils/logger.js.map +1 -0
- package/package.json +24 -0
- package/src/analyzer/static.ts +483 -0
- package/src/analyzer/typosquat.ts +272 -0
- package/src/cli.ts +700 -0
- package/src/diff/dependency.ts +297 -0
- package/src/fingerprint/profile.ts +333 -0
- package/src/index.ts +34 -0
- package/src/monitor/permissions.ts +330 -0
- package/src/sandbox/runner.ts +302 -0
- package/src/utils/colors.ts +58 -0
- package/src/utils/logger.ts +87 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ShieldPM — Static Analysis Engine
|
|
3
|
+
* Scans package source code for suspicious patterns, network calls,
|
|
4
|
+
* filesystem access, obfuscation, and dynamic code execution.
|
|
5
|
+
*/
|
|
6
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
7
|
+
import { join, extname, relative } from 'node:path';
|
|
8
|
+
const PATTERNS = [
|
|
9
|
+
// ── Dynamic code execution ──
|
|
10
|
+
{
|
|
11
|
+
rule: 'no-eval',
|
|
12
|
+
pattern: /\beval\s*\(/g,
|
|
13
|
+
severity: 'critical',
|
|
14
|
+
category: 'code-execution',
|
|
15
|
+
message: 'eval() can execute arbitrary code',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
rule: 'no-function-constructor',
|
|
19
|
+
pattern: /\bnew\s+Function\s*\(/g,
|
|
20
|
+
severity: 'critical',
|
|
21
|
+
category: 'code-execution',
|
|
22
|
+
message: 'Function constructor can execute arbitrary code',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
rule: 'no-vm-runInContext',
|
|
26
|
+
pattern: /\bvm\s*\.\s*(runInNewContext|runInThisContext|runInContext|compileFunction)\s*\(/g,
|
|
27
|
+
severity: 'high',
|
|
28
|
+
category: 'code-execution',
|
|
29
|
+
message: 'vm module can execute arbitrary code',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
rule: 'no-buffer-eval',
|
|
33
|
+
pattern: /Buffer\.from\s*\([^)]+\)\s*\.\s*toString\s*\([^)]*\)[\s\S]{0,50}eval/g,
|
|
34
|
+
severity: 'critical',
|
|
35
|
+
category: 'code-execution',
|
|
36
|
+
message: 'Buffer decode + eval pattern — likely obfuscated code execution',
|
|
37
|
+
},
|
|
38
|
+
// ── Child process / shell ──
|
|
39
|
+
{
|
|
40
|
+
rule: 'no-child-process',
|
|
41
|
+
pattern: /require\s*\(\s*['"`]child_process['"`]\s*\)/g,
|
|
42
|
+
severity: 'high',
|
|
43
|
+
category: 'process',
|
|
44
|
+
message: 'child_process can spawn arbitrary system commands',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
rule: 'no-child-process-import',
|
|
48
|
+
pattern: /from\s+['"`]child_process['"`]/g,
|
|
49
|
+
severity: 'high',
|
|
50
|
+
category: 'process',
|
|
51
|
+
message: 'child_process import — can spawn arbitrary system commands',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
rule: 'no-exec-sync',
|
|
55
|
+
pattern: /\b(execSync|exec|spawn|spawnSync|fork|execFile|execFileSync)\s*\(/g,
|
|
56
|
+
severity: 'high',
|
|
57
|
+
category: 'process',
|
|
58
|
+
message: 'Process execution function detected',
|
|
59
|
+
},
|
|
60
|
+
// ── Network access ──
|
|
61
|
+
{
|
|
62
|
+
rule: 'no-http-require',
|
|
63
|
+
pattern: /require\s*\(\s*['"`](https?|net|tls|dgram)['"`]\s*\)/g,
|
|
64
|
+
severity: 'medium',
|
|
65
|
+
category: 'network',
|
|
66
|
+
message: 'Network module require',
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
rule: 'no-http-import',
|
|
70
|
+
pattern: /from\s+['"`](https?|net|tls|dgram)['"`]/g,
|
|
71
|
+
severity: 'medium',
|
|
72
|
+
category: 'network',
|
|
73
|
+
message: 'Network module import',
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
rule: 'no-http-request',
|
|
77
|
+
pattern: /\b(https?)\s*\.\s*(request|get)\s*\(/g,
|
|
78
|
+
severity: 'medium',
|
|
79
|
+
category: 'network',
|
|
80
|
+
message: 'HTTP request detected',
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
rule: 'no-fetch',
|
|
84
|
+
pattern: /\bfetch\s*\(\s*['"`]https?:/g,
|
|
85
|
+
severity: 'medium',
|
|
86
|
+
category: 'network',
|
|
87
|
+
message: 'fetch() call to external URL',
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
rule: 'no-fetch-dynamic',
|
|
91
|
+
pattern: /\bfetch\s*\(\s*[^'"`\s]/g,
|
|
92
|
+
severity: 'high',
|
|
93
|
+
category: 'network',
|
|
94
|
+
message: 'fetch() with dynamic URL — destination unknown',
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
rule: 'no-dns-lookup',
|
|
98
|
+
pattern: /\bdns\s*\.\s*(lookup|resolve|resolve4|resolve6)\s*\(/g,
|
|
99
|
+
severity: 'low',
|
|
100
|
+
category: 'network',
|
|
101
|
+
message: 'DNS lookup detected',
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
rule: 'no-xmlhttprequest',
|
|
105
|
+
pattern: /\bnew\s+XMLHttpRequest\s*\(/g,
|
|
106
|
+
severity: 'medium',
|
|
107
|
+
category: 'network',
|
|
108
|
+
message: 'XMLHttpRequest detected',
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
rule: 'no-websocket',
|
|
112
|
+
pattern: /\bnew\s+WebSocket\s*\(/g,
|
|
113
|
+
severity: 'medium',
|
|
114
|
+
category: 'network',
|
|
115
|
+
message: 'WebSocket connection detected',
|
|
116
|
+
},
|
|
117
|
+
// ── File system access ──
|
|
118
|
+
{
|
|
119
|
+
rule: 'no-fs-require',
|
|
120
|
+
pattern: /require\s*\(\s*['"`]fs['"`]\s*\)/g,
|
|
121
|
+
severity: 'low',
|
|
122
|
+
category: 'filesystem',
|
|
123
|
+
message: 'fs module require',
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
rule: 'no-fs-import',
|
|
127
|
+
pattern: /from\s+['"`](fs|node:fs|fs\/promises|node:fs\/promises)['"`]/g,
|
|
128
|
+
severity: 'low',
|
|
129
|
+
category: 'filesystem',
|
|
130
|
+
message: 'fs module import',
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
rule: 'no-sensitive-path-read',
|
|
134
|
+
pattern: /\b(readFile|readFileSync|createReadStream)\s*\(\s*['"`](\/etc\/passwd|\/etc\/shadow|~\/\.ssh|~\/\.aws|~\/\.npmrc|~\/\.env|\/proc\/)/g,
|
|
135
|
+
severity: 'critical',
|
|
136
|
+
category: 'filesystem',
|
|
137
|
+
message: 'Reading sensitive system file',
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
rule: 'no-sensitive-path-write',
|
|
141
|
+
pattern: /\b(writeFile|writeFileSync|appendFile|appendFileSync)\s*\(\s*['"`](\/etc\/|\/usr\/|\/bin\/|~\/\.bashrc|~\/\.profile)/g,
|
|
142
|
+
severity: 'critical',
|
|
143
|
+
category: 'filesystem',
|
|
144
|
+
message: 'Writing to sensitive system path',
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
rule: 'no-fs-unlink',
|
|
148
|
+
pattern: /\b(unlink|unlinkSync|rmdir|rmdirSync|rm)\s*\(/g,
|
|
149
|
+
severity: 'medium',
|
|
150
|
+
category: 'filesystem',
|
|
151
|
+
message: 'File/directory deletion detected',
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
rule: 'no-home-dir-readdir',
|
|
155
|
+
pattern: /\b(readdir|readdirSync)\s*\(\s*['"`](~|\/home\/|\/Users\/)/g,
|
|
156
|
+
severity: 'high',
|
|
157
|
+
category: 'filesystem',
|
|
158
|
+
message: 'Listing home directory contents',
|
|
159
|
+
},
|
|
160
|
+
// ── Environment variable access ──
|
|
161
|
+
{
|
|
162
|
+
rule: 'no-env-access',
|
|
163
|
+
pattern: /process\.env\b/g,
|
|
164
|
+
severity: 'low',
|
|
165
|
+
category: 'environment',
|
|
166
|
+
message: 'Accesses environment variables',
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
rule: 'no-env-sensitive',
|
|
170
|
+
pattern: /process\.env\s*\[\s*['"`](API_KEY|SECRET|TOKEN|PASSWORD|AWS_|GITHUB_TOKEN|NPM_TOKEN|DATABASE_URL|PRIVATE_KEY)/g,
|
|
171
|
+
severity: 'high',
|
|
172
|
+
category: 'environment',
|
|
173
|
+
message: 'Accesses sensitive environment variable',
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
rule: 'no-env-exfiltrate',
|
|
177
|
+
pattern: /JSON\.stringify\s*\(\s*process\.env\s*\)/g,
|
|
178
|
+
severity: 'critical',
|
|
179
|
+
category: 'environment',
|
|
180
|
+
message: 'Serializing entire process.env — possible credential exfiltration',
|
|
181
|
+
},
|
|
182
|
+
// ── Dynamic require / import ──
|
|
183
|
+
{
|
|
184
|
+
rule: 'no-dynamic-require',
|
|
185
|
+
pattern: /require\s*\(\s*[^'"`\s)][^)]*\)/g,
|
|
186
|
+
severity: 'medium',
|
|
187
|
+
category: 'code-execution',
|
|
188
|
+
message: 'Dynamic require with non-literal argument',
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
rule: 'no-dynamic-import',
|
|
192
|
+
pattern: /import\s*\(\s*[^'"`\s)][^)]*\)/g,
|
|
193
|
+
severity: 'medium',
|
|
194
|
+
category: 'code-execution',
|
|
195
|
+
message: 'Dynamic import with non-literal argument',
|
|
196
|
+
},
|
|
197
|
+
// ── Obfuscation ──
|
|
198
|
+
{
|
|
199
|
+
rule: 'no-charcode-build',
|
|
200
|
+
pattern: /String\.fromCharCode\s*\(/g,
|
|
201
|
+
severity: 'high',
|
|
202
|
+
category: 'obfuscation',
|
|
203
|
+
message: 'String.fromCharCode — common obfuscation technique',
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
rule: 'no-hex-string',
|
|
207
|
+
pattern: /\\x[0-9a-fA-F]{2}(\\x[0-9a-fA-F]{2}){3,}/g,
|
|
208
|
+
severity: 'high',
|
|
209
|
+
category: 'obfuscation',
|
|
210
|
+
message: 'Long hex escape sequence — possible obfuscated string',
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
rule: 'no-unicode-escape',
|
|
214
|
+
pattern: /\\u[0-9a-fA-F]{4}(\\u[0-9a-fA-F]{4}){5,}/g,
|
|
215
|
+
severity: 'high',
|
|
216
|
+
category: 'obfuscation',
|
|
217
|
+
message: 'Long unicode escape sequence — possible obfuscated string',
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
rule: 'no-atob',
|
|
221
|
+
pattern: /\batob\s*\(/g,
|
|
222
|
+
severity: 'medium',
|
|
223
|
+
category: 'obfuscation',
|
|
224
|
+
message: 'Base64 decode — may hide malicious strings',
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
rule: 'no-base64-decode',
|
|
228
|
+
pattern: /Buffer\.from\s*\([^,]+,\s*['"`]base64['"`]\s*\)/g,
|
|
229
|
+
severity: 'medium',
|
|
230
|
+
category: 'obfuscation',
|
|
231
|
+
message: 'Base64 decode via Buffer — may hide malicious strings',
|
|
232
|
+
},
|
|
233
|
+
// ── Prototype pollution ──
|
|
234
|
+
{
|
|
235
|
+
rule: 'no-proto-access',
|
|
236
|
+
pattern: /\[['"`]__proto__['"`]\]/g,
|
|
237
|
+
severity: 'high',
|
|
238
|
+
category: 'prototype-pollution',
|
|
239
|
+
message: '__proto__ access — possible prototype pollution',
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
rule: 'no-constructor-prototype',
|
|
243
|
+
pattern: /\bconstructor\s*\[\s*['"`]prototype['"`]\s*\]/g,
|
|
244
|
+
severity: 'high',
|
|
245
|
+
category: 'prototype-pollution',
|
|
246
|
+
message: 'constructor.prototype access — possible prototype pollution',
|
|
247
|
+
},
|
|
248
|
+
// ── Install scripts ──
|
|
249
|
+
{
|
|
250
|
+
rule: 'no-preinstall-script',
|
|
251
|
+
pattern: /"preinstall"\s*:\s*"/g,
|
|
252
|
+
severity: 'high',
|
|
253
|
+
category: 'install-script',
|
|
254
|
+
message: 'preinstall script can execute code before user reviews package',
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
rule: 'no-postinstall-script',
|
|
258
|
+
pattern: /"postinstall"\s*:\s*"/g,
|
|
259
|
+
severity: 'medium',
|
|
260
|
+
category: 'install-script',
|
|
261
|
+
message: 'postinstall script detected',
|
|
262
|
+
},
|
|
263
|
+
];
|
|
264
|
+
// ── Severity weights for scoring ─────────────────────────────────────────
|
|
265
|
+
const SEVERITY_WEIGHT = {
|
|
266
|
+
critical: 3.0,
|
|
267
|
+
high: 2.0,
|
|
268
|
+
medium: 1.0,
|
|
269
|
+
low: 0.3,
|
|
270
|
+
info: 0.0,
|
|
271
|
+
};
|
|
272
|
+
// ── File collection ──────────────────────────────────────────────────────
|
|
273
|
+
const SCANNABLE_EXTENSIONS = new Set(['.js', '.mjs', '.cjs', '.ts', '.mts', '.cts', '.json']);
|
|
274
|
+
const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'build', 'coverage', '.shieldpm']);
|
|
275
|
+
async function collectFiles(dir) {
|
|
276
|
+
const files = [];
|
|
277
|
+
async function walk(d) {
|
|
278
|
+
let entries;
|
|
279
|
+
try {
|
|
280
|
+
entries = await readdir(d, { withFileTypes: true });
|
|
281
|
+
}
|
|
282
|
+
catch {
|
|
283
|
+
return; // skip unreadable dirs
|
|
284
|
+
}
|
|
285
|
+
for (const entry of entries) {
|
|
286
|
+
const full = join(d, entry.name);
|
|
287
|
+
if (entry.isDirectory()) {
|
|
288
|
+
if (!SKIP_DIRS.has(entry.name)) {
|
|
289
|
+
await walk(full);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
else if (entry.isFile() && SCANNABLE_EXTENSIONS.has(extname(entry.name))) {
|
|
293
|
+
files.push(full);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
await walk(dir);
|
|
298
|
+
return files;
|
|
299
|
+
}
|
|
300
|
+
// ── Core scan logic ──────────────────────────────────────────────────────
|
|
301
|
+
function scanContent(content, filePath, rules) {
|
|
302
|
+
const findings = [];
|
|
303
|
+
const lines = content.split('\n');
|
|
304
|
+
for (const rule of rules) {
|
|
305
|
+
// Reset lastIndex for global regexes
|
|
306
|
+
rule.pattern.lastIndex = 0;
|
|
307
|
+
let match;
|
|
308
|
+
while ((match = rule.pattern.exec(content)) !== null) {
|
|
309
|
+
// Calculate line and column
|
|
310
|
+
const beforeMatch = content.slice(0, match.index);
|
|
311
|
+
const line = (beforeMatch.match(/\n/g) || []).length + 1;
|
|
312
|
+
const lastNewline = beforeMatch.lastIndexOf('\n');
|
|
313
|
+
const column = match.index - lastNewline;
|
|
314
|
+
// Grab the snippet (the matched line)
|
|
315
|
+
const snippet = lines[line - 1]?.trim() ?? '';
|
|
316
|
+
findings.push({
|
|
317
|
+
severity: rule.severity,
|
|
318
|
+
category: rule.category,
|
|
319
|
+
message: rule.message,
|
|
320
|
+
file: filePath,
|
|
321
|
+
line,
|
|
322
|
+
column,
|
|
323
|
+
snippet: snippet.length > 120 ? snippet.slice(0, 117) + '...' : snippet,
|
|
324
|
+
rule: rule.rule,
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return findings;
|
|
329
|
+
}
|
|
330
|
+
// ── Risk score calculation ───────────────────────────────────────────────
|
|
331
|
+
function calculateScore(findings) {
|
|
332
|
+
if (findings.length === 0)
|
|
333
|
+
return 0;
|
|
334
|
+
let raw = 0;
|
|
335
|
+
for (const f of findings) {
|
|
336
|
+
raw += SEVERITY_WEIGHT[f.severity];
|
|
337
|
+
}
|
|
338
|
+
// Diminishing returns — many low findings shouldn't max the score
|
|
339
|
+
// Score = 10 * (1 - e^(-raw/8))
|
|
340
|
+
const score = 10 * (1 - Math.exp(-raw / 8));
|
|
341
|
+
return Math.round(score * 10) / 10; // one decimal
|
|
342
|
+
}
|
|
343
|
+
function buildSummary(findings, score) {
|
|
344
|
+
if (findings.length === 0)
|
|
345
|
+
return 'No suspicious patterns found.';
|
|
346
|
+
const critical = findings.filter((f) => f.severity === 'critical').length;
|
|
347
|
+
const high = findings.filter((f) => f.severity === 'high').length;
|
|
348
|
+
const medium = findings.filter((f) => f.severity === 'medium').length;
|
|
349
|
+
const low = findings.filter((f) => f.severity === 'low').length;
|
|
350
|
+
const parts = [];
|
|
351
|
+
if (critical > 0)
|
|
352
|
+
parts.push(`${critical} critical`);
|
|
353
|
+
if (high > 0)
|
|
354
|
+
parts.push(`${high} high`);
|
|
355
|
+
if (medium > 0)
|
|
356
|
+
parts.push(`${medium} medium`);
|
|
357
|
+
if (low > 0)
|
|
358
|
+
parts.push(`${low} low`);
|
|
359
|
+
const riskLabel = score >= 7 ? 'DANGEROUS' : score >= 4 ? 'SUSPICIOUS' : score >= 2 ? 'CAUTION' : 'LOW RISK';
|
|
360
|
+
return `Risk: ${riskLabel} (${score}/10) — ${findings.length} findings: ${parts.join(', ')}`;
|
|
361
|
+
}
|
|
362
|
+
// ── Public API ───────────────────────────────────────────────────────────
|
|
363
|
+
/**
|
|
364
|
+
* Analyze a package directory for security risks via static pattern matching.
|
|
365
|
+
*/
|
|
366
|
+
export async function analyzePackage(packageDir) {
|
|
367
|
+
const files = await collectFiles(packageDir);
|
|
368
|
+
const allFindings = [];
|
|
369
|
+
for (const file of files) {
|
|
370
|
+
let content;
|
|
371
|
+
try {
|
|
372
|
+
content = await readFile(file, 'utf-8');
|
|
373
|
+
}
|
|
374
|
+
catch {
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
const relPath = relative(packageDir, file);
|
|
378
|
+
const findings = scanContent(content, relPath, PATTERNS);
|
|
379
|
+
allFindings.push(...findings);
|
|
380
|
+
}
|
|
381
|
+
// Deduplicate same rule+file+line
|
|
382
|
+
const seen = new Set();
|
|
383
|
+
const deduped = allFindings.filter((f) => {
|
|
384
|
+
const key = `${f.rule}:${f.file}:${f.line}`;
|
|
385
|
+
if (seen.has(key))
|
|
386
|
+
return false;
|
|
387
|
+
seen.add(key);
|
|
388
|
+
return true;
|
|
389
|
+
});
|
|
390
|
+
// Sort: critical first, then by file/line
|
|
391
|
+
deduped.sort((a, b) => {
|
|
392
|
+
const sw = SEVERITY_WEIGHT[b.severity] - SEVERITY_WEIGHT[a.severity];
|
|
393
|
+
if (sw !== 0)
|
|
394
|
+
return sw;
|
|
395
|
+
return a.file.localeCompare(b.file) || a.line - b.line;
|
|
396
|
+
});
|
|
397
|
+
const score = calculateScore(deduped);
|
|
398
|
+
const categoryCounts = {};
|
|
399
|
+
for (const f of deduped) {
|
|
400
|
+
categoryCounts[f.category] = (categoryCounts[f.category] ?? 0) + 1;
|
|
401
|
+
}
|
|
402
|
+
return {
|
|
403
|
+
score,
|
|
404
|
+
findings: deduped,
|
|
405
|
+
summary: buildSummary(deduped, score),
|
|
406
|
+
categoryCounts,
|
|
407
|
+
filesScanned: files.length,
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Analyze a single source string (useful for quick checks).
|
|
412
|
+
*/
|
|
413
|
+
export function analyzeSource(source, filename = '<input>') {
|
|
414
|
+
return scanContent(source, filename, PATTERNS);
|
|
415
|
+
}
|
|
416
|
+
//# sourceMappingURL=static.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"static.js","sourceRoot":"","sources":["../../src/analyzer/static.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ShieldPM — Typosquatting Detector
|
|
3
|
+
* Checks package names against popular npm packages to detect
|
|
4
|
+
* typosquatting attempts using multiple heuristics.
|
|
5
|
+
*/
|
|
6
|
+
export declare const POPULAR_PACKAGES: string[];
|
|
7
|
+
export type DetectionMethod = 'levenshtein' | 'character-swap' | 'hyphen-underscore' | 'scope-confusion' | 'repeated-character' | 'missing-character' | 'extra-character';
|
|
8
|
+
export interface TyposquatResult {
|
|
9
|
+
/** Whether the package name is suspicious */
|
|
10
|
+
isSuspicious: boolean;
|
|
11
|
+
/** The popular package it resembles */
|
|
12
|
+
similarTo: string;
|
|
13
|
+
/** Edit distance or similarity metric */
|
|
14
|
+
distance: number;
|
|
15
|
+
/** How the similarity was detected */
|
|
16
|
+
method: DetectionMethod;
|
|
17
|
+
/** Human-readable explanation */
|
|
18
|
+
reason: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function levenshtein(a: string, b: string): number;
|
|
21
|
+
/**
|
|
22
|
+
* Check a package name for typosquatting against known popular packages.
|
|
23
|
+
* Returns null if the name appears safe.
|
|
24
|
+
*/
|
|
25
|
+
export declare function checkTyposquatting(packageName: string, knownPackages?: string[]): TyposquatResult | null;
|
|
26
|
+
/**
|
|
27
|
+
* Batch-check multiple package names.
|
|
28
|
+
*/
|
|
29
|
+
export declare function checkMultiple(packageNames: string[], knownPackages?: string[]): Map<string, TyposquatResult>;
|
|
30
|
+
//# sourceMappingURL=typosquat.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typosquat.d.ts","sourceRoot":"","sources":["../../src/analyzer/typosquat.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,eAAO,MAAM,gBAAgB,EAAE,MAAM,EASpC,CAAC;AAIF,MAAM,MAAM,eAAe,GACvB,aAAa,GACb,gBAAgB,GAChB,mBAAmB,GACnB,iBAAiB,GACjB,oBAAoB,GACpB,mBAAmB,GACnB,iBAAiB,CAAC;AAEtB,MAAM,WAAW,eAAe;IAC9B,6CAA6C;IAC7C,YAAY,EAAE,OAAO,CAAC;IACtB,uCAAuC;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,yCAAyC;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,sCAAsC;IACtC,MAAM,EAAE,eAAe,CAAC;IACxB,iCAAiC;IACjC,MAAM,EAAE,MAAM,CAAC;CAChB;AAID,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CA2BxD;AA4FD;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,WAAW,EAAE,MAAM,EACnB,aAAa,GAAE,MAAM,EAAqB,GACzC,eAAe,GAAG,IAAI,CAiFxB;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,YAAY,EAAE,MAAM,EAAE,EACtB,aAAa,GAAE,MAAM,EAAqB,GACzC,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAW9B"}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ShieldPM — Typosquatting Detector
|
|
3
|
+
* Checks package names against popular npm packages to detect
|
|
4
|
+
* typosquatting attempts using multiple heuristics.
|
|
5
|
+
*/
|
|
6
|
+
// ── Top 50 popular npm packages ──────────────────────────────────────────
|
|
7
|
+
export const POPULAR_PACKAGES = [
|
|
8
|
+
'express', 'lodash', 'react', 'axios', 'chalk', 'commander', 'moment',
|
|
9
|
+
'debug', 'uuid', 'dotenv', 'typescript', 'webpack', 'eslint', 'prettier',
|
|
10
|
+
'jest', 'mocha', 'bluebird', 'underscore', 'async', 'request', 'yargs',
|
|
11
|
+
'inquirer', 'glob', 'minimist', 'semver', 'mkdirp', 'rimraf', 'cheerio',
|
|
12
|
+
'socket.io', 'mongoose', 'sequelize', 'passport', 'nodemon', 'pm2',
|
|
13
|
+
'next', 'nuxt', 'vue', 'angular', 'svelte', 'fastify', 'koa', 'hapi',
|
|
14
|
+
'body-parser', 'cors', 'helmet', 'jsonwebtoken', 'bcrypt', 'sharp',
|
|
15
|
+
'puppeteer', 'redis',
|
|
16
|
+
];
|
|
17
|
+
// ── Levenshtein distance ─────────────────────────────────────────────────
|
|
18
|
+
export function levenshtein(a, b) {
|
|
19
|
+
const m = a.length;
|
|
20
|
+
const n = b.length;
|
|
21
|
+
if (m === 0)
|
|
22
|
+
return n;
|
|
23
|
+
if (n === 0)
|
|
24
|
+
return m;
|
|
25
|
+
// Use two rows instead of full matrix for memory efficiency
|
|
26
|
+
let prev = new Array(n + 1);
|
|
27
|
+
let curr = new Array(n + 1);
|
|
28
|
+
for (let j = 0; j <= n; j++)
|
|
29
|
+
prev[j] = j;
|
|
30
|
+
for (let i = 1; i <= m; i++) {
|
|
31
|
+
curr[0] = i;
|
|
32
|
+
for (let j = 1; j <= n; j++) {
|
|
33
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
34
|
+
curr[j] = Math.min(prev[j] + 1, // deletion
|
|
35
|
+
curr[j - 1] + 1, // insertion
|
|
36
|
+
prev[j - 1] + cost // substitution
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
[prev, curr] = [curr, prev];
|
|
40
|
+
}
|
|
41
|
+
return prev[n];
|
|
42
|
+
}
|
|
43
|
+
// ── Detection functions ──────────────────────────────────────────────────
|
|
44
|
+
function normalizePackageName(name) {
|
|
45
|
+
const match = name.match(/^(@[^/]+)\/(.+)$/);
|
|
46
|
+
if (match) {
|
|
47
|
+
return { scope: match[1], base: match[2] };
|
|
48
|
+
}
|
|
49
|
+
return { scope: '', base: name };
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Check for character swaps (transpositions) — e.g. "exprses" vs "express"
|
|
53
|
+
*/
|
|
54
|
+
function detectCharacterSwap(input, target) {
|
|
55
|
+
if (input.length !== target.length)
|
|
56
|
+
return false;
|
|
57
|
+
if (input === target)
|
|
58
|
+
return false;
|
|
59
|
+
let diffs = 0;
|
|
60
|
+
const diffPositions = [];
|
|
61
|
+
for (let i = 0; i < input.length; i++) {
|
|
62
|
+
if (input[i] !== target[i]) {
|
|
63
|
+
diffs++;
|
|
64
|
+
diffPositions.push(i);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// Exactly two adjacent characters swapped
|
|
68
|
+
if (diffs === 2) {
|
|
69
|
+
const [p1, p2] = diffPositions;
|
|
70
|
+
return (p2 - p1 === 1 &&
|
|
71
|
+
input[p1] === target[p2] &&
|
|
72
|
+
input[p2] === target[p1]);
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Check for hyphen/underscore confusion — e.g. "lodash" vs "lo-dash", "lo_dash"
|
|
78
|
+
*/
|
|
79
|
+
function detectHyphenUnderscore(input, target) {
|
|
80
|
+
const normalize = (s) => s.replace(/[-_.]/g, '');
|
|
81
|
+
return normalize(input) === normalize(target) && input !== target;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Check for scope confusion — e.g. "@tyeps/react" vs "@types/react"
|
|
85
|
+
*/
|
|
86
|
+
function detectScopeConfusion(input, knownPackages) {
|
|
87
|
+
const { scope, base } = normalizePackageName(input);
|
|
88
|
+
if (!scope)
|
|
89
|
+
return null;
|
|
90
|
+
// Common scope typos
|
|
91
|
+
const knownScopes = ['@types', '@babel', '@angular', '@vue', '@react-native'];
|
|
92
|
+
for (const known of knownScopes) {
|
|
93
|
+
if (scope !== known && levenshtein(scope, known) <= 2) {
|
|
94
|
+
const fullKnown = `${known}/${base}`;
|
|
95
|
+
return {
|
|
96
|
+
isSuspicious: true,
|
|
97
|
+
similarTo: fullKnown,
|
|
98
|
+
distance: levenshtein(scope, known),
|
|
99
|
+
method: 'scope-confusion',
|
|
100
|
+
reason: `Scope "${scope}" looks like a typo of "${known}"`,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Check for repeated characters — e.g. "expresss" vs "express"
|
|
108
|
+
*/
|
|
109
|
+
function detectRepeatedChar(input, target) {
|
|
110
|
+
if (input.length !== target.length + 1)
|
|
111
|
+
return false;
|
|
112
|
+
// Try removing each character from input and see if it matches target
|
|
113
|
+
for (let i = 0; i < input.length; i++) {
|
|
114
|
+
const reduced = input.slice(0, i) + input.slice(i + 1);
|
|
115
|
+
if (reduced === target)
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
// ── Main detection ───────────────────────────────────────────────────────
|
|
121
|
+
/**
|
|
122
|
+
* Check a package name for typosquatting against known popular packages.
|
|
123
|
+
* Returns null if the name appears safe.
|
|
124
|
+
*/
|
|
125
|
+
export function checkTyposquatting(packageName, knownPackages = POPULAR_PACKAGES) {
|
|
126
|
+
const { base: inputBase } = normalizePackageName(packageName);
|
|
127
|
+
// Exact match — it's a real popular package, not suspicious
|
|
128
|
+
if (knownPackages.includes(packageName) || knownPackages.includes(inputBase)) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
// Check scope confusion first
|
|
132
|
+
const scopeResult = detectScopeConfusion(packageName, knownPackages);
|
|
133
|
+
if (scopeResult)
|
|
134
|
+
return scopeResult;
|
|
135
|
+
// Check against each known package
|
|
136
|
+
let bestMatch = null;
|
|
137
|
+
let bestDistance = Infinity;
|
|
138
|
+
for (const known of knownPackages) {
|
|
139
|
+
const { base: knownBase } = normalizePackageName(known);
|
|
140
|
+
// Skip if the lengths are too different for meaningful comparison
|
|
141
|
+
if (Math.abs(inputBase.length - knownBase.length) > 2)
|
|
142
|
+
continue;
|
|
143
|
+
// Hyphen/underscore confusion
|
|
144
|
+
if (detectHyphenUnderscore(inputBase, knownBase)) {
|
|
145
|
+
return {
|
|
146
|
+
isSuspicious: true,
|
|
147
|
+
similarTo: known,
|
|
148
|
+
distance: 0,
|
|
149
|
+
method: 'hyphen-underscore',
|
|
150
|
+
reason: `"${packageName}" differs from "${known}" only by hyphen/underscore/dot`,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
// Character swap
|
|
154
|
+
if (detectCharacterSwap(inputBase, knownBase)) {
|
|
155
|
+
return {
|
|
156
|
+
isSuspicious: true,
|
|
157
|
+
similarTo: known,
|
|
158
|
+
distance: 1,
|
|
159
|
+
method: 'character-swap',
|
|
160
|
+
reason: `"${packageName}" has swapped characters compared to "${known}"`,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
// Repeated character
|
|
164
|
+
if (detectRepeatedChar(inputBase, knownBase)) {
|
|
165
|
+
return {
|
|
166
|
+
isSuspicious: true,
|
|
167
|
+
similarTo: known,
|
|
168
|
+
distance: 1,
|
|
169
|
+
method: 'repeated-character',
|
|
170
|
+
reason: `"${packageName}" has a repeated character compared to "${known}"`,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
// Missing character
|
|
174
|
+
if (detectRepeatedChar(knownBase, inputBase)) {
|
|
175
|
+
return {
|
|
176
|
+
isSuspicious: true,
|
|
177
|
+
similarTo: known,
|
|
178
|
+
distance: 1,
|
|
179
|
+
method: 'missing-character',
|
|
180
|
+
reason: `"${packageName}" is missing a character compared to "${known}"`,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
// General Levenshtein distance
|
|
184
|
+
const dist = levenshtein(inputBase, knownBase);
|
|
185
|
+
if (dist <= 2 && dist < bestDistance) {
|
|
186
|
+
bestDistance = dist;
|
|
187
|
+
bestMatch = {
|
|
188
|
+
isSuspicious: true,
|
|
189
|
+
similarTo: known,
|
|
190
|
+
distance: dist,
|
|
191
|
+
method: 'levenshtein',
|
|
192
|
+
reason: `"${packageName}" is ${dist} edit(s) away from "${known}"`,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return bestMatch;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Batch-check multiple package names.
|
|
200
|
+
*/
|
|
201
|
+
export function checkMultiple(packageNames, knownPackages = POPULAR_PACKAGES) {
|
|
202
|
+
const results = new Map();
|
|
203
|
+
for (const name of packageNames) {
|
|
204
|
+
const result = checkTyposquatting(name, knownPackages);
|
|
205
|
+
if (result) {
|
|
206
|
+
results.set(name, result);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return results;
|
|
210
|
+
}
|
|
211
|
+
//# sourceMappingURL=typosquat.js.map
|