From dd46bbbd6035c57311358ddda2c75dc65273f0c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sat, 29 Aug 2026 19:20:58 +0200 Subject: [PATCH] Make the spacing-scale modifier-chain regexes linear MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both class-string regexes carried (?:[^\s"']*:)* for the optional modifier prefix chain. The inner class includes the colon, so the star could carve the same prefix into chunks many ways, and a long non-matching string made the engine try them all — exponential backtracking (CodeQL alert 44, js/redos). Excluding the colon from the chunk class accepts exactly the same strings with one parse each. The rule's 35 tests pass unchanged. Resolves CodeQL alert 44. --- eslint-plugins/healthlog/spacing-scale.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/eslint-plugins/healthlog/spacing-scale.js b/eslint-plugins/healthlog/spacing-scale.js index 6c5e032fa..cb5f9418c 100644 --- a/eslint-plugins/healthlog/spacing-scale.js +++ b/eslint-plugins/healthlog/spacing-scale.js @@ -61,8 +61,15 @@ const ENFORCED_ROOTS = ["src/components/", "src/app/"]; // A pt-/pb- utility with a numeric step, `px`, or an arbitrary value, // optionally behind modifier prefixes (`md:`, `hover:`, `[.border-b]:`). // `pb-safe` (safe-area helpers) and word-suffixed utilities never match. +// +// The modifier chain excludes `:` from its chunk class so each chunk ends at +// exactly one colon. With `:` inside the class the star was ambiguous — +// `(?:[^\s"']*:)*` could carve the same prefix into chunks many ways — and a +// long non-matching class string made the engine try them all +// (exponential backtracking, CodeQL js/redos). Same strings accepted, +// one parse each, linear time. const SLOT_PADDING_RE = - /(?:^|\s)(?:[^\s"']*:)*(p[tb]-(?:\d+(?:\.\d+)?|px|\[[^\]]+\]))(?=\s|$)/; + /(?:^|\s)(?:[^\s"':]*:)*(p[tb]-(?:\d+(?:\.\d+)?|px|\[[^\]]+\]))(?=\s|$)/; const SLOT_NAMES = new Set(["CardHeader", "CardContent"]); @@ -83,8 +90,10 @@ const SPACING_PREFIX = // `5` or `7`; or a half-step whose integer part is anything but `1` // (`0.5`, `2.5`…`9.5`, `10.5`+), so the sanctioned `1.5` is left alone. const OFF_SCALE_STEP = "5|7|(?:[02-9]|\\d\\d+)\\.5"; +// Modifier chain shaped like SLOT_PADDING_RE's, and linear for the same +// reason: `:` is excluded from the chunk class so the star has one parse. const SHELL_OFFSCALE_RE = new RegExp( - `(?:^|\\s)(?:[^\\s"']*:)*((?:${SPACING_PREFIX})-(?:${OFF_SCALE_STEP}))(?=\\s|$)`, + `(?:^|\\s)(?:[^\\s"':]*:)*((?:${SPACING_PREFIX})-(?:${OFF_SCALE_STEP}))(?=\\s|$)`, ); function toPosix(filename) {