Skip to content

Commit

Permalink
fix: dynamic routes bypass (#26)
Browse files Browse the repository at this point in the history
* fix dynamic routes bypass

* bump package version to 1.2.6
  • Loading branch information
zhongliang02 authored Nov 7, 2024
1 parent 031f6a3 commit 677cadd
Show file tree
Hide file tree
Showing 3 changed files with 18 additions and 16 deletions.
2 changes: 1 addition & 1 deletion packages/validators/package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
12 changes: 12 additions & 0 deletions packages/validators/src/__tests__/url.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
20 changes: 5 additions & 15 deletions packages/validators/src/url/utils.ts
Original file line number Diff line number Diff line change
@@ -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 => {
Expand All @@ -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) => {
Expand All @@ -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
Expand Down

0 comments on commit 677cadd

Please sign in to comment.