@archpublicwebsite/eslint-config 1.0.15 → 1.0.16

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/README.md CHANGED
@@ -66,6 +66,8 @@ On install or when you run the setup script manually, the package prepares the c
66
66
  - `.hooks/prepare-commit-msg`
67
67
  - `.hooks/commit-msg`
68
68
  - `.hooks/post-commit`
69
+ - `safe-reinstall.sh` (supports `--check-only` for pre-commit safety checks)
70
+ - `scan-global.sh` (global IOC scanner)
69
71
  - `eslint.config.mjs` when one does not already exist
70
72
  - `.prettierrc` plugin entry for `prettier-plugin-tailwindcss`
71
73
  - `.vscode/settings.json` with ESLint flat-config settings
@@ -181,7 +183,10 @@ Make sure the tarball contains only the intended public files: `eslint.config.mj
181
183
  "scripts": {
182
184
  "lint": "pnpm lint:fix",
183
185
  "lint:check": "turbo run lint",
184
- "lint:fix": "((pnpm format || true) && turbo run lint --continue=always -- --fix) || true"
186
+ "lint:fix": "((pnpm format || true) && turbo run lint --continue=always -- --fix) || true",
187
+ "precommit": "node node_modules/@archpublicwebsite/eslint-config/tools/git-hooks/pre-commit.mjs",
188
+ "security:global-scan": "bash ./node_modules/@archpublicwebsite/eslint-config/tools/security/scan-global.sh",
189
+ "security:safe-check": "bash ./node_modules/@archpublicwebsite/eslint-config/tools/security/safe-reinstall.sh --check-only"
185
190
  },
186
191
  "lint-staged": {
187
192
  "*.{js,ts,tsx,vue}": ["eslint --fix"]
@@ -189,6 +194,19 @@ Make sure the tarball contains only the intended public files: `eslint.config.mj
189
194
  }
190
195
  ```
191
196
 
197
+ Pre-commit now runs in this order:
198
+
199
+ 1. `node tools/security/scan.mjs`
200
+ 2. `bash ./safe-reinstall.sh --check-only` (if present)
201
+ 3. `bash ./scan-global.sh` (if present)
202
+ 4. `pnpm lint-staged`
203
+
204
+ If you need to skip the global machine scan in CI or emergency situations, set:
205
+
206
+ ```bash
207
+ SKIP_GLOBAL_SCAN=1
208
+ ```
209
+
192
210
  ## Verification
193
211
 
194
212
  After making config changes, validate the package by checking the installed consumer workflow or by running the setup script in a test project.
package/eslint.config.mjs CHANGED
@@ -36,6 +36,63 @@ export function createArchipelagoConfig(...overrides) {
36
36
  // ── Tailwind ──────────────────────────────────────────────────────────────
37
37
  ...tailwind.configs['flat/recommended'],
38
38
 
39
+ // ── Security — OWASP / supply-chain hardening ─────────────────────────────
40
+ // These rules catch patterns that appear in real-world npm supply-chain
41
+ // attacks (eval payloads, implied eval, script-URL injection, etc.).
42
+ // They apply to ALL project files.
43
+ {
44
+ name: 'archipelago/security',
45
+ rules: {
46
+ // Eval family — arbitrary code execution
47
+ 'no-eval': 'error',
48
+ 'no-new-func': 'error',
49
+ 'no-implied-eval': 'error',
50
+ // javascript: URI as href/src — XSS vector
51
+ 'no-script-url': 'error',
52
+ // Dangerous globals that accept strings as code
53
+ 'no-restricted-globals': [
54
+ 'error',
55
+ { name: 'eval', message: 'eval() is a security risk — use a safe alternative' },
56
+ ],
57
+ },
58
+ },
59
+
60
+ // ── Extra security for build / config files ───────────────────────────────
61
+ // Importing child_process, http, https, net, or dns inside a Tailwind /
62
+ // Vite / PostCSS / ESLint config is always a red flag (supply-chain attack
63
+ // pattern). The same code is legitimate inside scripts/ — this rule only
64
+ // targets config file globs.
65
+ {
66
+ name: 'archipelago/security-config-files',
67
+ files: [
68
+ '**/tailwind.config.*',
69
+ '**/vite.config.*',
70
+ '**/nuxt.config.*',
71
+ '**/webpack.config.*',
72
+ '**/rollup.config.*',
73
+ '**/postcss.config.*',
74
+ '**/babel.config.*',
75
+ '**/jest.config.*',
76
+ '**/vitest.config.*',
77
+ '**/eslint.config.*',
78
+ ],
79
+ rules: {
80
+ 'no-restricted-imports': [
81
+ 'error',
82
+ { name: 'child_process', message: 'child_process is not allowed in build config files — supply-chain risk' },
83
+ { name: 'node:child_process', message: 'child_process is not allowed in build config files — supply-chain risk' },
84
+ { name: 'http', message: 'http is not allowed in build config files — exfiltration risk' },
85
+ { name: 'node:http', message: 'http is not allowed in build config files — exfiltration risk' },
86
+ { name: 'https', message: 'https is not allowed in build config files — exfiltration risk' },
87
+ { name: 'node:https', message: 'https is not allowed in build config files — exfiltration risk' },
88
+ { name: 'dns', message: 'dns is not allowed in build config files — DNS tunnelling risk' },
89
+ { name: 'node:dns', message: 'dns is not allowed in build config files — DNS tunnelling risk' },
90
+ { name: 'net', message: 'net is not allowed in build config files — reverse-shell risk' },
91
+ { name: 'node:net', message: 'net is not allowed in build config files — reverse-shell risk' },
92
+ ],
93
+ },
94
+ },
95
+
39
96
  // ── Canonical rules ───────────────────────────────────────────────────────
40
97
  {
41
98
  name: 'archipelago/rules',
@@ -90,10 +147,9 @@ export function createArchipelagoConfig(...overrides) {
90
147
  'ts/no-empty-object-type': 'off',
91
148
  'ts/no-explicit-any': 'warn',
92
149
  'ts/no-var-requires': 'warn',
93
- 'ts/consistent-type-imports': ['error', {
94
- prefer: 'type-imports',
95
- fixStyle: 'inline-type-imports',
96
- }],
150
+ // Keep disabled globally to avoid parserServices errors on plain JS
151
+ // config files (tailwind/postcss/vite) during lint-staged runs.
152
+ 'ts/consistent-type-imports': 'off',
97
153
 
98
154
  // ── Import ordering ──────────────────────────────────────────────────
99
155
  'import/order': 'off',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archpublicwebsite/eslint-config",
3
- "version": "1.0.15",
3
+ "version": "1.0.16",
4
4
  "author": "Archipelago Hotels",
5
5
  "description": "Reusable ESLint flat config and git-hook toolkit for Archipelago projects",
6
6
  "type": "module",
@@ -29,6 +29,8 @@
29
29
  "flat-config",
30
30
  "vue",
31
31
  "git-hooks",
32
+ "security",
33
+ "supply-chain",
32
34
  "vscode"
33
35
  ],
34
36
  "private": false,
@@ -38,6 +40,10 @@
38
40
  "scripts": {
39
41
  "postinstall": "node ./tools/setup/install.mjs",
40
42
  "setup": "node ./tools/setup/install.mjs",
43
+ "security-scan": "node ./tools/security/scan.mjs",
44
+ "security:global-scan": "bash ./tools/security/scan-global.sh",
45
+ "security:safe-check": "bash ./tools/security/safe-reinstall.sh --check-only",
46
+ "security-test": "node ./tools/security/test-patterns.mjs",
41
47
  "prepublishOnly": "npm pack --dry-run",
42
48
  "version:patch": "node ../../scripts/bump-version.mjs patch",
43
49
  "version:minor": "node ../../scripts/bump-version.mjs minor",
@@ -1,10 +1,40 @@
1
+ import { dirname, join } from 'node:path'
2
+ import { fileURLToPath } from 'node:url'
3
+ import { existsSync } from 'node:fs'
1
4
  import { hasCommand, run } from './shared.mjs'
2
5
 
6
+ const __dirname = dirname(fileURLToPath(import.meta.url))
7
+ const SCAN_SCRIPT = join(__dirname, '../security/scan.mjs')
8
+
9
+ function runIfExists(scriptPath, command) {
10
+ if (!existsSync(scriptPath)) {
11
+ return
12
+ }
13
+
14
+ console.log(`\nRunning ${scriptPath}...`)
15
+ run(command, { stdio: 'inherit' })
16
+ }
17
+
3
18
  if (!hasCommand('pnpm')) {
4
19
  console.error('\nCOMMIT FAILED: pnpm is required but not found.\n')
5
20
  process.exit(1)
6
21
  }
7
22
 
23
+ // ── 1. Security scan — blocks on critical/high findings ───────────────────────
24
+ run(`node "${SCAN_SCRIPT}"`, { stdio: 'inherit' })
25
+
26
+ // ── 2. Optional shell guards at repo root ─────────────────────────────────────
27
+ // These scripts are auto-generated by setup when missing.
28
+ runIfExists('./safe-reinstall.sh', 'bash ./safe-reinstall.sh --check-only')
29
+ if (process.env.SKIP_GLOBAL_SCAN === '1') {
30
+ console.log('\nSkipping global IOC scan (SKIP_GLOBAL_SCAN=1).')
31
+ }
32
+ else {
33
+ runIfExists('./scan-global.sh', 'bash ./scan-global.sh')
34
+ }
35
+
36
+ // ── 3. Lint-staged — auto-fix & format staged files ───────────────────────────
8
37
  console.log('\nRunning lint-staged (auto-fix staged files)...')
9
38
  run('pnpm lint-staged', { stdio: 'inherit' })
39
+
10
40
  console.log('\nCOMMIT CHECKS PASSED\n')
@@ -0,0 +1,352 @@
1
+ /**
2
+ * @archipelago/security-scan — pattern definitions
3
+ *
4
+ * Inspired by socket.dev. Every pattern here represents a real-world attack
5
+ * vector observed in npm supply-chain incidents (event-stream, ua-parser-js,
6
+ * node-ipc, polyfill.io, XZ Utils, etc.).
7
+ *
8
+ * Patterns are kept as non-global RegExp so they can safely be re-tested
9
+ * across lines without needing to reset `lastIndex`.
10
+ */
11
+
12
+ // ─── Source-file patterns ──────────────────────────────────────────────────────
13
+
14
+ /**
15
+ * Patterns scanned in every staged .js/.ts/.vue file.
16
+ *
17
+ * @type {Array<{
18
+ * id: string,
19
+ * severity: 'critical'|'high'|'medium',
20
+ * message: string,
21
+ * regex: RegExp,
22
+ * configOnly?: boolean,
23
+ * }>}
24
+ */
25
+ export const FILE_PATTERNS = [
26
+ // ── Arbitrary code execution ───────────────────────────────────────────────
27
+ {
28
+ id: 'no-eval',
29
+ severity: 'critical',
30
+ message: 'eval() executes arbitrary strings — primary vector for malware delivery',
31
+ regex: /\beval\s*\(/,
32
+ },
33
+ {
34
+ id: 'no-new-func',
35
+ severity: 'critical',
36
+ message: 'new Function() executes arbitrary strings — equivalent to eval()',
37
+ regex: /\bnew\s+Function\s*\(/,
38
+ },
39
+ {
40
+ id: 'no-implied-eval',
41
+ severity: 'high',
42
+ message: 'setTimeout/setInterval with a string argument executes code dynamically',
43
+ regex: /\b(?:setTimeout|setInterval)\s*\(\s*['"]/,
44
+ },
45
+
46
+ // ── Payload delivery & obfuscation ────────────────────────────────────────
47
+ {
48
+ id: 'base64-decode',
49
+ severity: 'high',
50
+ message: 'Buffer.from(…, "base64") — frequently used to hide malicious payloads',
51
+ regex: /Buffer\.from\s*\([^)]*,\s*['"]base64['"]\)/,
52
+ },
53
+ {
54
+ id: 'atob-usage',
55
+ severity: 'medium',
56
+ message: 'atob() decodes base64 — verify it is not decoding a hidden payload',
57
+ regex: /\batob\s*\(/,
58
+ },
59
+ {
60
+ id: 'charcode-obfuscation',
61
+ severity: 'high',
62
+ message: 'String.fromCharCode sequence — common technique to obfuscate malicious strings',
63
+ regex: /String\.fromCharCode\s*\(\s*(?:\d+\s*,\s*){4,}\d+\s*\)/,
64
+ },
65
+ {
66
+ id: 'unicode-bidi-control',
67
+ severity: 'critical',
68
+ message: 'Unicode bidi control characters detected — hidden code/trick rendering attack (Trojan Source)',
69
+ regex: /[\u202A-\u202E\u2066-\u2069]/,
70
+ },
71
+ {
72
+ id: 'zero-width-hidden-char',
73
+ severity: 'high',
74
+ message: 'Zero-width/invisible Unicode characters detected — possible hidden payload marker',
75
+ regex: /[\u200B-\u200D\uFEFF]/,
76
+ },
77
+ {
78
+ id: 'hex-string-obfuscation',
79
+ severity: 'high',
80
+ message: 'Dense hex-encoded string — possible obfuscated payload',
81
+ regex: /['"](?:\\x[0-9a-fA-F]{2}){20,}['"]/,
82
+ },
83
+
84
+ // ── Dangerous imports in build / config context ───────────────────────────
85
+ // These are legitimate in scripts/ but not in Tailwind/Vite/PostCSS configs.
86
+ {
87
+ id: 'child-process-in-config',
88
+ severity: 'critical',
89
+ message: 'child_process in build config — potential command-execution backdoor',
90
+ regex: /require\s*\(\s*['"](?:node:)?child_process['"]\s*\)|from\s+['"](?:node:)?child_process['"]/,
91
+ configOnly: true,
92
+ },
93
+ {
94
+ id: 'http-in-config-require',
95
+ severity: 'critical',
96
+ message: 'HTTP/HTTPS module in build config — potential data-exfiltration vector',
97
+ regex: /require\s*\(\s*['"](?:node:)?https?['"]\s*\)/,
98
+ configOnly: true,
99
+ },
100
+ {
101
+ id: 'http-in-config-import',
102
+ severity: 'critical',
103
+ message: 'HTTP/HTTPS module in build config — potential data-exfiltration vector',
104
+ regex: /from\s+['"](?:node:)?https?['"]/,
105
+ configOnly: true,
106
+ },
107
+ {
108
+ id: 'dns-in-config',
109
+ severity: 'critical',
110
+ message: 'DNS module in build config — potential exfiltration via DNS tunnelling',
111
+ regex: /require\s*\(\s*['"](?:node:)?dns['"]\s*\)|from\s+['"](?:node:)?dns['"]/,
112
+ configOnly: true,
113
+ },
114
+ {
115
+ id: 'net-in-config',
116
+ severity: 'critical',
117
+ message: 'net/socket module in build config — potential reverse-shell vector',
118
+ regex: /require\s*\(\s*['"](?:node:)?net['"]\s*\)|from\s+['"](?:node:)?net['"]/,
119
+ configOnly: true,
120
+ },
121
+
122
+ // ── Prototype pollution ────────────────────────────────────────────────────
123
+ {
124
+ id: 'prototype-pollution',
125
+ severity: 'high',
126
+ message: 'Prototype pollution pattern — can escalate to RCE in some server runtimes',
127
+ regex: /\.__proto__\s*=|Object\.prototype\s*\[|constructor\s*\.\s*prototype\s*\[/,
128
+ },
129
+
130
+ // ── Suspicious dynamic import ──────────────────────────────────────────────
131
+ {
132
+ id: 'dynamic-require',
133
+ severity: 'medium',
134
+ message: 'require() with a computed/variable path — can load arbitrary modules at runtime',
135
+ // Only flag when the argument is not a plain string literal
136
+ regex: /\brequire\s*\(\s*(?!['"` ])[^'"`)]/,
137
+ },
138
+
139
+ // ── Dropper / malware-loader patterns ─────────────────────────────────────
140
+ // The patterns below are the exact fingerprints of the supply-chain dropper
141
+ // observed in the wild (event-stream variant, ua-parser-js, polyfill.io, etc.)
142
+ // where a shuffled-string decoder bootstraps a dynamic Function() call.
143
+
144
+ {
145
+ id: 'require-global-hijack',
146
+ severity: 'critical',
147
+ message: 'global[...] = require — dropper technique to expose require() as a global for later payload use',
148
+ // Matches both: global['x'] = require AND global[arr[0]] = require
149
+ regex: /global\s*\[.*?\]\s*=\s*require\b/,
150
+ },
151
+ {
152
+ id: 'shuffle-cipher-iife',
153
+ severity: 'critical',
154
+ message: 'Shuffle-cipher IIFE — RC4-variant string decoder used to hide payload strings (seen in event-stream attack)',
155
+ // Matches: (function(l,e){ ... })("...", number) — the typical 2-arg encoded-string bootstrapper
156
+ // [\s\S] instead of [^}] so the body can contain nested {}
157
+ regex: /\(function\s*\(\w\s*,\s*\w\)[\s\S]{20,}?\}\)\s*\(\s*["'][^"']{4,}["']\s*,\s*\d{4,}\s*\)/,
158
+ },
159
+ {
160
+ id: 'obfuscated-variable-names',
161
+ severity: 'high',
162
+ message: 'Obfuscator-generated variable names (_$_XXXX pattern) — strong indicator of minified malware',
163
+ // Matches: _$_XXXX where XXXX is 3+ hex/alphanumeric chars
164
+ regex: /\b_\$_[0-9a-fA-F]{3,}\b/,
165
+ },
166
+ {
167
+ id: 'long-encoded-string',
168
+ severity: 'high',
169
+ message: 'Extremely long encoded string literal (>400 chars) — characteristic of embedded payload delivery',
170
+ // This is checked by the line-length scanner, not regex; placeholder regex below
171
+ // triggers on strings containing many mixed chars that indicate base64/shuffled encoding
172
+ regex: /['"][A-Za-z0-9+/=\\]{400,}['"]/,
173
+ },
174
+ {
175
+ id: 'array-fn-call-chain',
176
+ severity: 'critical',
177
+ message: 'Decoded-array Function invocation chain — dropper pattern: fn(a,b); result(number)',
178
+ // Matches the dropper execution tail: jFD(LQI, pYd); Tgw(2509)
179
+ // i.e. a 2-arg call followed immediately by a 1-numeric-arg call on the result variable
180
+ regex: /\w+\s*\(\s*\w+\s*,\s*\w+\s*\)\s*;\s*\w+\s*\(\s*\d{3,}\s*\)/,
181
+ },
182
+ {
183
+ id: 'function-constructor-via-array',
184
+ severity: 'critical',
185
+ message: 'Function constructor accessed via decoded array — used to run hidden payload without calling new Function() directly',
186
+ // Generalised: identifier assigned from decoded array accessor, then invoked.
187
+ regex: /=\s*\w+\s*\[\s*\w+\s*\]\s*;.{0,80}\w+\s*\(\s*\w+\s*,\s*\w+\s*\(\s*\w+\s*\)\s*\)/,
188
+ },
189
+ {
190
+ id: 'global-bracket-assignment-index',
191
+ severity: 'high',
192
+ message: 'global[variable] assignment — indirect global mutation used to smuggle require/module references',
193
+ regex: /global\s*\[\s*[\w$]+\s*\[\s*\d+\s*\]\s*\]\s*=/,
194
+ },
195
+ {
196
+ id: 'global-bracket-assignment-string',
197
+ severity: 'high',
198
+ message: 'global[variable] assignment — indirect global mutation used to smuggle require/module references',
199
+ regex: /global\s*\[\s*['"][^'"]{1,40}['"]\s*\]\s*=/,
200
+ },
201
+ {
202
+ id: 'global-bracket-assignment-var',
203
+ severity: 'high',
204
+ message: 'global[variable] assignment — indirect global mutation used to smuggle require/module references',
205
+ regex: /global\s*\[\s*[\w$]+\s*\]\s*=/,
206
+ },
207
+ {
208
+ id: 'module-global-hijack',
209
+ severity: 'critical',
210
+ message: 'global[...] = module — dropper technique to expose Node module reference as a global',
211
+ regex: /global\s*\[.*?\]\s*=\s*module\b/,
212
+ },
213
+ {
214
+ id: 'define-property-backdoor-getter',
215
+ severity: 'critical',
216
+ message: 'Object.defineProperty used to define hidden getter/setter/value with dynamic execution — potential backdoor',
217
+ regex: /Object\.defineProperty\s*\([^)]*\b(?:get|set)\s*:\s*(?:function|\([^)]*\)\s*=>)/,
218
+ },
219
+ {
220
+ id: 'define-property-backdoor-value',
221
+ severity: 'critical',
222
+ message: 'Object.defineProperty used to define hidden getter/setter/value with dynamic execution — potential backdoor',
223
+ regex: /Object\.defineProperty\s*\([^)]*\bvalue\s*:\s*(?:eval\s*\(|new\s+Function\s*\(|require\s*\()/,
224
+ },
225
+ ]
226
+
227
+ // ─── Install-script patterns ───────────────────────────────────────────────────
228
+
229
+ /**
230
+ * Patterns scanned in node_modules package install scripts
231
+ * (preinstall / install / postinstall).
232
+ *
233
+ * @type {Array<{id: string, severity: 'critical'|'high'|'medium', message: string, regex: RegExp}>}
234
+ */
235
+ export const INSTALL_SCRIPT_PATTERNS = [
236
+ {
237
+ id: 'install-curl-wget',
238
+ severity: 'critical',
239
+ message: 'Install script downloads files from the internet via curl/wget',
240
+ regex: /\bcurl\b|\bwget\b/,
241
+ },
242
+ {
243
+ id: 'install-net-request',
244
+ severity: 'critical',
245
+ message: 'Install script makes HTTP requests at install time',
246
+ regex: /require\s*\(\s*['"]https?['"]\s*\)|\.get\s*\(\s*['"]https?:\/\//,
247
+ },
248
+ {
249
+ id: 'install-base64-exec',
250
+ severity: 'critical',
251
+ message: 'Install script decodes base64 content — hidden payload delivery pattern',
252
+ regex: /Buffer\.from[^)]*base64|atob\s*\(/,
253
+ },
254
+ {
255
+ id: 'install-eval',
256
+ severity: 'critical',
257
+ message: 'Install script calls eval() — arbitrary code execution at install time',
258
+ regex: /\beval\s*\(/,
259
+ },
260
+ {
261
+ id: 'install-env-credential',
262
+ severity: 'high',
263
+ message: 'Install script reads credential/secret environment variables',
264
+ regex: /process\.env\.(?:AWS_|GITHUB_TOKEN|NPM_TOKEN|SECRET|PASSWORD|API_KEY|PRIVATE_KEY|TOKEN)/i,
265
+ },
266
+ {
267
+ id: 'install-system-path',
268
+ severity: 'critical',
269
+ message: 'Install script writes to system paths — possible persistence mechanism',
270
+ regex: /\/etc\/|\/usr\/(?:bin|local)|C:\\Windows\\|\.ssh\//,
271
+ },
272
+ {
273
+ id: 'install-exec-shell',
274
+ severity: 'high',
275
+ message: 'Install script spawns child processes via child_process',
276
+ regex: /\bexecSync\s*\(|\bspawnSync\s*\(|\bexec\s*\(\s*['"]/,
277
+ },
278
+ {
279
+ id: 'install-hex-obfuscation',
280
+ severity: 'high',
281
+ message: 'Install script contains dense hex-encoded strings — possible obfuscated payload',
282
+ regex: /(?:\\x[0-9a-fA-F]{2}){20,}/,
283
+ },
284
+ ]
285
+
286
+ // ─── Config file name detection ────────────────────────────────────────────────
287
+
288
+ /**
289
+ * File paths that match these patterns receive stricter scanning
290
+ * (configOnly patterns apply).
291
+ */
292
+ export const CONFIG_FILE_PATTERNS = [
293
+ /(?:^|\/)tailwind\.config\.[cm]?[jt]s$/,
294
+ /(?:^|\/)vite\.config\.[cm]?[jt]s$/,
295
+ /(?:^|\/)nuxt\.config\.[cm]?[jt]s$/,
296
+ /(?:^|\/)webpack\.config\.[cm]?[jt]s$/,
297
+ /(?:^|\/)rollup\.config\.[cm]?[jt]s$/,
298
+ /(?:^|\/)postcss\.config\.[cm]?[jt]s$/,
299
+ /(?:^|\/)babel\.config\.[cm]?[jt]s$/,
300
+ /(?:^|\/)jest\.config\.[cm]?[jt]s$/,
301
+ /(?:^|\/)vitest\.config\.[cm]?[jt]s$/,
302
+ /(?:^|\/)eslint\.config\.[cm]?[jt]s$/,
303
+ /(?:^|\/)\.[a-z]+rc\.[cm]?[jt]s$/,
304
+ ]
305
+
306
+ // ─── Extension allow-list ──────────────────────────────────────────────────────
307
+
308
+ /** Source file extensions to include in the scan. */
309
+ export const SCAN_EXTENSIONS = new Set([
310
+ '.js',
311
+ '.mjs',
312
+ '.cjs',
313
+ '.ts',
314
+ '.mts',
315
+ '.cts',
316
+ '.vue',
317
+ ])
318
+
319
+ // ─── Typosquatting reference list ──────────────────────────────────────────────
320
+
321
+ /**
322
+ * Popular npm packages commonly targeted by typosquatting attacks.
323
+ * A newly added package within Levenshtein distance 1 of any entry will be flagged.
324
+ */
325
+ export const POPULAR_PACKAGES = [
326
+ // Frameworks & meta-frameworks
327
+ 'react', 'vue', 'svelte', 'angular', 'next', 'nuxt', 'remix', 'astro',
328
+ // Build tools
329
+ 'vite', 'webpack', 'rollup', 'parcel', 'esbuild', 'turbopack',
330
+ // Styling
331
+ 'tailwindcss', 'postcss', 'autoprefixer', 'sass', 'less',
332
+ // Utility libraries
333
+ 'lodash', 'axios', 'dayjs', 'moment', 'date-fns', 'zod', 'yup',
334
+ // State management
335
+ 'pinia', 'vuex', 'redux', 'mobx', 'zustand', 'jotai',
336
+ // Routing
337
+ 'react-router', 'vue-router',
338
+ // Testing
339
+ 'vitest', 'jest', 'mocha', 'chai', 'playwright', 'cypress',
340
+ // TypeScript
341
+ 'typescript', 'ts-node', 'tsx',
342
+ // Linting
343
+ 'eslint', 'prettier', 'stylelint',
344
+ // Security / auth
345
+ 'bcrypt', 'jsonwebtoken', 'passport', 'helmet', 'cors',
346
+ // Database
347
+ 'prisma', 'sequelize', 'mongoose', 'typeorm', 'knex', 'drizzle-orm',
348
+ // Node utilities
349
+ 'express', 'fastify', 'koa', 'hapi', 'socket.io', 'ws', 'nodemailer',
350
+ // Package tooling
351
+ 'pnpm', 'yarn', 'npm',
352
+ ]