From 677cadd50630e0026574f0843f2a6b2475b05cdb Mon Sep 17 00:00:00 2001 From: zhongliang02 Date: Thu, 7 Nov 2024 11:07:04 +0800 Subject: [PATCH] fix: dynamic routes bypass (#26) * fix dynamic routes bypass * bump package version to 1.2.6 --- packages/validators/package.json | 2 +- packages/validators/src/__tests__/url.test.ts | 12 +++++++++++ packages/validators/src/url/utils.ts | 20 +++++-------------- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/packages/validators/package.json b/packages/validators/package.json index ca6896c..4d90700 100644 --- a/packages/validators/package.json +++ b/packages/validators/package.json @@ -1,6 +1,6 @@ { "name": "@opengovsg/starter-kitty-validators", - "version": "1.2.5", + "version": "1.2.6", "main": "./dist/index.js", "types": "./dist/index.d.ts", "exports": { diff --git a/packages/validators/src/__tests__/url.test.ts b/packages/validators/src/__tests__/url.test.ts index b11ef3f..87bb612 100644 --- a/packages/validators/src/__tests__/url.test.ts +++ b/packages/validators/src/__tests__/url.test.ts @@ -143,6 +143,18 @@ describe('UrlValidator with base URL', () => { it('should not allow Next.js dynamic routes', () => { expect(() => validator.parse('/[[x]]javascript:alert(1337)/[y]/[z]?x&y&z')).toThrow(UrlValidationError) }) + + it('should not allow Next.js dynamic routes', () => { + expect(() => + validator.parse('/[[x]x]]https://[y]y]//example.com/[[/[[x]x]]/y?x=[[x]&x]x&%2F[[x=[[/[[x]&y=[y]&y]y='), + ).toThrow(UrlValidationError) + }) + + it('should not allow Next.js dynamic routes', () => { + expect(() => validator.parse('/[[x]x]]javascript:alert(1)%2F%2F/[[/[[x]x]]/y?x=[[x]&x]x&%2F[[x=[[/[[x]')).toThrow( + UrlValidationError, + ) + }) }) describe('UrlValidator with invalid options', () => { diff --git a/packages/validators/src/url/utils.ts b/packages/validators/src/url/utils.ts index 4541eac..73a141c 100644 --- a/packages/validators/src/url/utils.ts +++ b/packages/validators/src/url/utils.ts @@ -1,7 +1,8 @@ import { UrlValidationError } from '@/url/errors' import { UrlValidatorWhitelist } from '@/url/options' -const DYNAMIC_ROUTE_SEGMENT_REGEX = /\[\[?([^\]]+)\]?\]/g +// regex from https://github.com/vercel/next.js/blob/8cb8edb686ec8ddf7e24c69545d11175fcb9df02/packages/next/src/shared/lib/router/utils/is-dynamic.ts#L7 +const DYNAMIC_ROUTE_SEGMENT_REGEX = /\/\[[^/]+?\](?=\/|$)/ const IS_NOT_HOSTNAME_REGEX = /[^.]+\.[^.]+/g export const resolveRelativeUrl = (url: string, baseOrigin?: URL): URL => { @@ -26,19 +27,8 @@ export const resolveRelativeUrl = (url: string, baseOrigin?: URL): URL => { return normalizedUrl } -/* As of Next.js 14.2.5, router.push() resolves dynamic routes using query parameters. */ -const resolveNextDynamicRoute = (url: URL): URL => { - const pathname = url.pathname - const query = new URLSearchParams(url.search) - const resolvedPathname = pathname.replace(DYNAMIC_ROUTE_SEGMENT_REGEX, (_, name: string) => { - const value = query.get(name) || '' - query.delete(name) - return value - }) - - const result = new URL(url.href) - result.pathname = resolvedPathname - return result +export const isDynamicRoute = (url: URL): boolean => { + return DYNAMIC_ROUTE_SEGMENT_REGEX.test(url.pathname) } export const isSafeUrl = (url: URL, whitelist: UrlValidatorWhitelist) => { @@ -59,7 +49,7 @@ export const isSafeUrl = (url: URL, whitelist: UrlValidatorWhitelist) => { } // don't allow dynamic routes - if (resolveNextDynamicRoute(url).href !== url.href) { + if (isDynamicRoute(url)) { return false } return true