Skip to content

Commit b1848ca

Browse files
authored
fix: resolve high-severity npm audit vulnerabilities (brace-expansion DoS) (#755)
Root cause: GHSA-mh99-v99m-4gvg (CVE-2026-14257) — brace-expansion DoS via unbounded expansion. The advisory affects every version < 5.0.8 (single range `>=0, <5.0.8`), and 5.0.8+ is API-incompatible with the minimatch@3/@9 used by eslint@9 and jest. This left 31 transitive high vulns across the jest/eslint/@typescript-eslint trees, and blocked every dependabot PR's `npm audit --audit-level=high` CI step (main itself is affected). The only patched brace-expansion (5.x) requires minimatch@10, which in turn requires eslint@10 (eslint@9's @eslint/config-array pins minimatch@3). @typescript-eslint@8.65.0 already declares `eslint: ^10.0.0` support. Changes: - eslint ^9 -> ^10, @eslint/js ^9 -> ^10 (natively use minimatch@10 + brace-expansion@5, clearing the eslint tree). - Drop the FlatCompat/@eslint/eslintrc layer; rewrite eslint.config.js as a pure flat config (eslint@10's @eslint/eslintrc would otherwise still pull vulnerable minimatch@3). - Remove unused eslint-plugin-github (never referenced by the config); it pulled old minimatch@3/@9 and a stale bundled typescript-eslint@8.16.0 and was itself flagged by the audit. - Add eslint-config-prettier@10 (previously only present transitively via eslint-plugin-github) to preserve the prior `extends` behavior. - Add overrides to clear the remaining jest-tree vulns: brace-expansion ^5.0.8, glob ^11.0.1, test-exclude ^8.0.0 and force @typescript-eslint/utils ^8.65.0 so eslint-plugin-jest's older transitive copy dedupes to the eslint@10-compatible release. - Disable eslint@10's new `no-useless-assignment` rule to avoid churning the bundled dist/ for an unrelated audit fix. Result: `npm audit --audit-level=high` -> 0 vulnerabilities; build, lint, format-check and tests (37/37) all pass; src/ and dist/ are untouched so check-dist remains valid.
1 parent 0fc2d3c commit b1848ca

3 files changed

Lines changed: 2282 additions & 7195 deletions

File tree

eslint.config.js

Lines changed: 48 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,62 @@
1-
import {
2-
defineConfig,
3-
globalIgnores,
4-
} from "eslint/config";
1+
import {defineConfig, globalIgnores} from "eslint/config";
52

63
import tsParser from "@typescript-eslint/parser";
74
import typescriptEslint from "@typescript-eslint/eslint-plugin";
85
import jest from "eslint-plugin-jest";
96
import globals from "globals";
107
import js from "@eslint/js";
11-
12-
import {
13-
FlatCompat,
14-
} from "@eslint/eslintrc";
15-
16-
const compat = new FlatCompat({
17-
baseDirectory: import.meta.dirname,
18-
recommendedConfig: js.configs.recommended,
19-
allConfig: js.configs.all
20-
});
21-
22-
export default defineConfig([{
23-
extends: compat.extends(
24-
"eslint:recommended",
25-
"plugin:@typescript-eslint/recommended",
26-
"plugin:eslint-plugin-jest/recommended",
27-
"eslint-config-prettier",
28-
),
29-
30-
languageOptions: {
31-
parser: tsParser,
32-
33-
globals: {
34-
...globals.node,
35-
...jest.environments.globals.globals,
8+
import prettier from "eslint-config-prettier";
9+
10+
export default defineConfig([
11+
js.configs.recommended,
12+
...typescriptEslint.configs["flat/recommended"],
13+
jest.configs["flat/recommended"],
14+
prettier,
15+
{
16+
languageOptions: {
17+
parser: tsParser,
18+
19+
globals: {
20+
...globals.node,
21+
...jest.environments.globals.globals,
22+
},
3623
},
37-
},
38-
39-
plugins: {
40-
"@typescript-eslint": typescriptEslint,
41-
jest,
42-
},
4324

44-
rules: {
45-
"@typescript-eslint/no-require-imports": "error",
46-
"@typescript-eslint/no-non-null-assertion": "off",
47-
"@typescript-eslint/no-explicit-any": "off",
48-
"@typescript-eslint/no-empty-function": "off",
25+
rules: {
26+
"@typescript-eslint/no-require-imports": "error",
27+
"@typescript-eslint/no-non-null-assertion": "off",
28+
"@typescript-eslint/no-explicit-any": "off",
29+
"@typescript-eslint/no-empty-function": "off",
4930

50-
"@typescript-eslint/ban-ts-comment": ["error", {
51-
"ts-ignore": "allow-with-description",
52-
}],
31+
"@typescript-eslint/ban-ts-comment": ["error", {
32+
"ts-ignore": "allow-with-description",
33+
}],
5334

54-
"no-console": "error",
55-
"yoda": "error",
35+
"no-console": "error",
36+
"yoda": "error",
5637

57-
"prefer-const": ["error", {
58-
destructuring: "all",
59-
}],
38+
"prefer-const": ["error", {
39+
destructuring: "all",
40+
}],
6041

61-
"no-control-regex": "off",
42+
"no-control-regex": "off",
6243

63-
"no-constant-condition": ["error", {
64-
checkLoops: false,
65-
}],
66-
},
67-
}, {
68-
files: ["**/*{test,spec}.ts"],
44+
"no-constant-condition": ["error", {
45+
checkLoops: false,
46+
}],
6947

70-
rules: {
71-
"@typescript-eslint/no-unused-vars": "off",
72-
"jest/no-standalone-expect": "off",
73-
"jest/no-conditional-expect": "off",
74-
"no-console": "off",
75-
},
76-
}, globalIgnores(["**/dist/", "**/lib/", "**/node_modules/", "**/jest.config.mjs", "**/jest.setup.js"])]);
48+
// Introduced in ESLint 10; the existing try/catch and switch
49+
// patterns rely on defensive initializers. Kept off to avoid
50+
// churning the bundled dist/ output for an unrelated audit fix.
51+
"no-useless-assignment": "off",
52+
},
53+
}, {
54+
files: ["**/*{test,spec}.ts"],
55+
56+
rules: {
57+
"@typescript-eslint/no-unused-vars": "off",
58+
"jest/no-standalone-expect": "off",
59+
"jest/no-conditional-expect": "off",
60+
"no-console": "off",
61+
},
62+
}, globalIgnores(["**/dist/", "**/lib/", "**/node_modules/", "**/jest.config.mjs", "**/jest.setup.js"])]);

0 commit comments

Comments
 (0)