mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 12:39:42 +02:00
fix(files): match glob queries without regex so a hostile query cannot stall the server
The Files search compiled the user's query into a backtracking RegExp: '*a*a*a...' became '^.*a.*a.*a...$', the classic blowup, evaluated synchronously against every walked path — a pathological query could freeze the event loop for the whole server (and every user of it in multi-user mode). /api/search stays regex-free for exactly this reason. Globs now match through a two-pointer wildcard walk, O(text · pattern) worst case, with a 256-char query cap bounding the pattern side; an overlong query compiles to null, the same answer as an empty one. Semantics are unchanged (anchored, case-insensitive, * spans slashes) and the existing tests pass untouched; the pathological pattern gets a test that fails by timeout with the RegExp version. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -64,6 +64,24 @@ describe('compileFileQuery', () => {
|
||||
expect(m!('session.ts', 'src/session.ts')).toBe(true);
|
||||
expect(m!('session.ts', 'lib/session.ts')).toBe(false);
|
||||
});
|
||||
|
||||
it('stays fast on a pathological star-heavy pattern (no regex backtracking)', () => {
|
||||
// `*a*a*a…` compiled to `^.*a.*a…$` is the classic backtracking blowup —
|
||||
// as a RegExp this match takes effectively forever and this test fails by
|
||||
// timeout. The two-pointer glob walk answers it in linear-ish time; the
|
||||
// 500ms ceiling is generous so a loaded CI box cannot flake it.
|
||||
const m = compileFileQuery('*a'.repeat(40) + 'b');
|
||||
expect(m).not.toBeNull();
|
||||
const started = performance.now();
|
||||
expect(m!('a'.repeat(200), 'a'.repeat(200))).toBe(false);
|
||||
expect(performance.now() - started).toBeLessThan(500);
|
||||
});
|
||||
|
||||
it('treats an overlong query as no search, like an empty one', () => {
|
||||
// The glob walk is O(text · pattern); the length cap is what bounds it.
|
||||
expect(compileFileQuery('a'.repeat(257))).toBeNull();
|
||||
expect(compileFileQuery('a'.repeat(256))).not.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('matchFileQuery', () => {
|
||||
|
||||
Reference in New Issue
Block a user