diff --git a/packages/shared/src/cognito-auth.test.ts b/packages/shared/src/cognito-auth.test.ts index 9a2889c..15fa654 100644 --- a/packages/shared/src/cognito-auth.test.ts +++ b/packages/shared/src/cognito-auth.test.ts @@ -260,4 +260,14 @@ describe('extractBearerToken', () => { it('throws AuthError on a non-Bearer header', () => { expect(() => extractBearerToken('Basic abc')).toThrow(AuthError); }); + + // ReDoS guard (CodeQL js/polynomial-redos): the matcher is /\s+(\S.*)/, not + // the ambiguous /\s+(.+)/. These pin the behavior that the `\S` fix preserves. + it('still captures a token that follows multiple separating spaces', () => { + expect(extractBearerToken('Bearer abc.def')).toBe('abc.def'); + }); + + it('rejects a "Bearer" header with no token after the whitespace', () => { + expect(() => extractBearerToken(`Bearer ${' '.repeat(5_000)}`)).toThrow(AuthError); + }); }); diff --git a/packages/shared/src/cognito-auth.ts b/packages/shared/src/cognito-auth.ts index a4b0c5c..292c9e9 100644 --- a/packages/shared/src/cognito-auth.ts +++ b/packages/shared/src/cognito-auth.ts @@ -189,7 +189,11 @@ export function extractBearerToken(req: unknown): string { if (!header) { throw new AuthError('missing_token', 'No Authorization header present.'); } - const match = /^Bearer\s+(.+)$/i.exec(header.trim()); + // `\s+(\S.*)` — NOT `\s+(.+)`: requiring the capture to start with a + // non-whitespace char removes the quantifier overlap (both `\s` and `.` + // match a space), which otherwise allows polynomial backtracking on a + // crafted all-whitespace header (CodeQL js/polynomial-redos). Linear now. + const match = /^Bearer\s+(\S.*)$/i.exec(header.trim()); const token = match?.[1]?.trim(); if (!token) { throw new AuthError('missing_token', 'Authorization header is not a Bearer token.');