Skip to content

Commit ed81226

Browse files
NullVoxPopuliclaude
andcommitted
Fix polynomial regex (ReDoS) flagged by CodeQL
Split the single regex with overlapping quantifiers into two separate patterns for mustache comments and mustache block comments. Each uses a specific character class ([\w\s,/-]*) that cannot cause polynomial backtracking. Also removes the -+$ cleanup regex since the block comment pattern now captures only the rules content without trailing dashes. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
1 parent 176831f commit ed81226

1 file changed

Lines changed: 24 additions & 19 deletions

File tree

lib/processors/template-lint-disable.js

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@
33
const noop = require('ember-eslint-parser/noop');
44

55
/**
6-
* Regex pattern for template-lint-disable mustache comments:
6+
* Regex patterns for template-lint-disable mustache comments.
7+
* Two separate patterns to avoid polynomial backtracking (ReDoS):
78
* {{! template-lint-disable rule1 rule2 }}
89
* {{!-- template-lint-disable rule1 rule2 --}}
910
*/
10-
const TEMPLATE_LINT_DISABLE_REGEX =
11-
/{{!-*\s*template-lint-disable\s*([\s\S]*?)-*}}/g;
11+
const MUSTACHE_COMMENT_REGEX =
12+
/{{!\s+template-lint-disable([\w\s,/-]*)}}/g;
13+
const MUSTACHE_BLOCK_COMMENT_REGEX =
14+
/{{!--\s*template-lint-disable([\w\s,/-]*)--}}/g;
1215

1316
// Store disable directives per file
1417
const fileDisableDirectives = new Map();
@@ -19,35 +22,37 @@ const fileDisableDirectives = new Map();
1922
*
2023
* template-lint-disable means "disable next line" (like eslint-disable-next-line).
2124
*/
22-
function parseDisableDirectives(text) {
23-
const directives = [];
24-
const lines = text.split('\n');
25-
26-
for (let i = 0; i < lines.length; i++) {
27-
const line = lines[i];
28-
TEMPLATE_LINT_DISABLE_REGEX.lastIndex = 0;
25+
function collectMatches(line, lineIndex, directives) {
26+
for (const regex of [MUSTACHE_COMMENT_REGEX, MUSTACHE_BLOCK_COMMENT_REGEX]) {
27+
regex.lastIndex = 0;
2928
let match;
3029

31-
while ((match = TEMPLATE_LINT_DISABLE_REGEX.exec(line)) !== null) {
30+
while ((match = regex.exec(line)) !== null) {
3231
const rulesPart = (match[1] || '').trim();
33-
// Strip trailing -- from mustache block comments
34-
const cleaned = rulesPart.replace(/-+$/, '').trim();
35-
36-
const rules = cleaned
37-
? cleaned.split(/[\s,]+/).filter(Boolean)
32+
const rules = rulesPart
33+
? rulesPart.split(/[\s,]+/).filter(Boolean)
3834
: []; // empty = disable all
3935

40-
// Comment is on line i+1 (1-indexed), next line is i+2.
36+
// Comment is on line lineIndex+1 (1-indexed), next line is lineIndex+2.
4137
// In template ASTs, TextNodes can start on the same line as the comment
4238
// (e.g. the newline after {{! template-lint-disable }} is part of the
4339
// following TextNode), so we suppress both the comment line and the next.
4440
directives.push({
45-
commentLine: i + 1,
46-
nextLine: i + 2,
41+
commentLine: lineIndex + 1,
42+
nextLine: lineIndex + 2,
4743
rules,
4844
});
4945
}
5046
}
47+
}
48+
49+
function parseDisableDirectives(text) {
50+
const directives = [];
51+
const lines = text.split('\n');
52+
53+
for (let i = 0; i < lines.length; i++) {
54+
collectMatches(lines[i], i, directives);
55+
}
5156

5257
return directives;
5358
}

0 commit comments

Comments
 (0)