fix: don't split query URL params in validatePath (#111296)

This commit is contained in:
Kristian Bremberg 2025-09-30 15:39:14 +02:00 committed by GitHub
parent cfbf64c3fd
commit c37bb1d0a6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 12 additions and 14 deletions

View file

@ -167,9 +167,9 @@ describe('validatePath', () => {
expect(validatePath(urlWithDots)).toBe(urlWithDots);
});
it('should allow query parameters that contain dots', () => {
const urlWithDotsInQuery = 'https://api.example.com/search?version=1.2.3&file=../config';
expect(validatePath(urlWithDotsInQuery)).toBe(urlWithDotsInQuery);
it('should block query parameters that contain path traversal', () => {
const urlWithTraversalInQuery = 'https://api.example.com/search?version=1.2.3&file=../config';
expect(() => validatePath(urlWithTraversalInQuery)).toThrow(PathValidationError);
});
it('should handle malformed URLs gracefully', () => {

View file

@ -146,27 +146,25 @@ export class PathValidationError extends Error {
*/
export function validatePath<OriginalPath extends string>(path: OriginalPath): OriginalPath {
try {
let originalDecoded: string = path; // down-cast to a string to indicate this can't be returned
let decoded: string = path;
while (true) {
const nextDecode = decodeURIComponent(originalDecoded);
if (nextDecode === originalDecoded) {
const nextDecode = decodeURIComponent(decoded);
if (nextDecode === decoded) {
break; // String is fully decoded.
}
originalDecoded = nextDecode;
decoded = nextDecode;
}
// Remove query params and fragments to check only the path portion
const cleaned = originalDecoded.split(/[\?#]/)[0];
originalDecoded = cleaned;
// If the original string contains traversal attempts, block it
if (/\.\.|\/\\|[\t\n\r]/.test(originalDecoded)) {
// Validate the entire decoded string for traversal attempts
// This prevents attacks that use query separators to hide traversal payloads
if (/\.\.|\/\\|[\t\n\r]/.test(decoded)) {
throw new PathValidationError();
}
// Return the original path (not the decoded version) to preserve the full URL
return path;
} catch (err) {
// Rethrow the original InvalidPathError to preserve the stack trace
// Rethrow the original PathValidationError to preserve the stack trace
if (err instanceof PathValidationError) {
throw err;
}