File tree Expand file tree Collapse file tree 3 files changed +62
-4
lines changed Expand file tree Collapse file tree 3 files changed +62
-4
lines changed Original file line number Diff line number Diff line change @@ -8,6 +8,8 @@ import {SmartLink} from 'sentry-docs/components/smartLink';
88import { extractPlatforms , getDocsRootNode , nodeForPath } from 'sentry-docs/docTree' ;
99import { setServerContext } from 'sentry-docs/serverContext' ;
1010
11+ import { sanitizeNext } from './utils' ;
12+
1113export const metadata : Metadata = {
1214 robots : 'noindex' ,
1315 title : 'Platform Specific Content' ,
@@ -27,8 +29,7 @@ export default async function Page(props: {
2729 next = next [ 0 ] ;
2830 }
2931
30- // discard the hash
31- const [ pathname , _ ] = next . split ( '#' ) ;
32+ const pathname = sanitizeNext ( next ) ;
3233 const rootNode = await getDocsRootNode ( ) ;
3334 const defaultTitle = 'Platform Specific Content' ;
3435 let description = '' ;
@@ -64,7 +65,7 @@ export default async function Page(props: {
6465 p => p . key === requestedPlatform ?. toLowerCase ( )
6566 ) ;
6667 if ( isValidPlatform ) {
67- return redirect ( `/platforms/${ requestedPlatform } ${ next } ` ) ;
68+ return redirect ( `/platforms/${ requestedPlatform } ${ pathname } ` ) ;
6869 }
6970 }
7071
@@ -83,7 +84,7 @@ export default async function Page(props: {
8384 < ul >
8485 { platformList . map ( p => (
8586 < li key = { p . key } >
86- < SmartLink to = { `/platforms/${ p . key } ${ next } ` } >
87+ < SmartLink to = { `/platforms/${ p . key } ${ pathname } ` } >
8788 < PlatformIcon
8889 size = { 16 }
8990 platform = { p . icon ?? p . key }
Original file line number Diff line number Diff line change 1+ import { describe , expect , it } from 'vitest' ;
2+
3+ import { sanitizeNext } from './utils' ;
4+
5+ describe ( 'sanitizeNext' , ( ) => {
6+ it ( 'should return an empty string for external URLs' , ( ) => {
7+ expect ( sanitizeNext ( 'http://example.com' ) ) . toBe ( '' ) ;
8+ expect ( sanitizeNext ( 'https://example.com' ) ) . toBe ( '' ) ;
9+ expect ( sanitizeNext ( '//example.com' ) ) . toBe ( '' ) ;
10+ } ) ;
11+
12+ it ( 'should prepend a slash if missing' , ( ) => {
13+ expect ( sanitizeNext ( 'path/to/resource' ) ) . toBe ( '/path/to/resource' ) ;
14+ } ) ;
15+
16+ it ( 'should not modify a valid internal path' , ( ) => {
17+ expect ( sanitizeNext ( '/path/to/resource' ) ) . toBe ( '/path/to/resource' ) ;
18+ } ) ;
19+
20+ it ( 'should remove unsafe characters' , ( ) => {
21+ expect ( sanitizeNext ( '/path/to/resource?query=1' ) ) . toBe ( '/path/to/resource' ) ;
22+ expect ( sanitizeNext ( '/path/to/resource#hash' ) ) . toBe ( '/path/to/resource' ) ;
23+ } ) ;
24+
25+ it ( 'should allow alphanumeric and hyphens' , ( ) => {
26+ expect ( sanitizeNext ( '/path-to/resource123' ) ) . toBe ( '/path-to/resource123' ) ;
27+ } ) ;
28+
29+ it ( 'should return an empty string for paths with colons' , ( ) => {
30+ expect ( sanitizeNext ( '/path:to/resource' ) ) . toBe ( '' ) ;
31+ } ) ;
32+ } ) ;
Original file line number Diff line number Diff line change 1+ export const sanitizeNext = ( next : string ) => {
2+ let sanitizedNext = next ;
3+ // Validate that next is an internal path
4+ if (
5+ sanitizedNext . startsWith ( '//' ) ||
6+ sanitizedNext . startsWith ( 'http' ) ||
7+ sanitizedNext . includes ( ':' )
8+ ) {
9+ // Reject potentially malicious redirects
10+ sanitizedNext = '' ;
11+ }
12+
13+ // Ensure next starts with a forward slash and only contains safe characters
14+ if ( sanitizedNext && ! sanitizedNext . startsWith ( '/' ) ) {
15+ sanitizedNext = '/' + sanitizedNext ;
16+ }
17+
18+ // Discard hash and path parameters
19+ const [ pathname ] = sanitizedNext . split ( '#' ) [ 0 ] . split ( '?' ) ;
20+
21+ // Only allow alphanumeric, hyphens
22+ sanitizedNext = pathname . replace ( / [ ^ \w \- \/ ] / g, '' ) ;
23+
24+ return sanitizedNext ;
25+ } ;
You can’t perform that action at this time.
0 commit comments