@@ -28,16 +28,45 @@ export default function ParamFerry(): null {
2828 return key === pattern ;
2929 } ) ;
3030
31- if ( shouldSync ) {
32- params [ key ] = value ;
31+ if ( shouldSync && typeof value === 'string' && typeof key === 'string' ) {
32+ // Sanitize parameter key and value during collection
33+ const sanitizedKey = key . replace ( / [ ^ \w \- . _ ~ ] / g, '' ) ;
34+ const sanitizedValue = value . replace ( / [ \r \n \t ] / g, '' ) . substring ( 0 , 500 ) ;
35+
36+ if ( sanitizedKey && sanitizedValue ) {
37+ params [ sanitizedKey ] = sanitizedValue ;
38+ }
3339 }
3440 } ) ;
3541
3642 return params ;
3743 } ;
3844
45+ // Validate URL is safe to process
46+ const isSafeUrl = ( url : string ) : boolean => {
47+ // Block dangerous schemes
48+ const dangerousSchemes = [ 'javascript:' , 'data:' , 'vbscript:' , 'file:' , 'about:' ] ;
49+ const lowerUrl = url . toLowerCase ( ) . trim ( ) ;
50+
51+ if ( dangerousSchemes . some ( scheme => lowerUrl . startsWith ( scheme ) ) ) {
52+ return false ;
53+ }
54+
55+ // Only allow http, https, and relative URLs
56+ if ( lowerUrl . startsWith ( 'http://' ) || lowerUrl . startsWith ( 'https://' ) || lowerUrl . startsWith ( '/' ) || ! lowerUrl . includes ( ':' ) ) {
57+ return true ;
58+ }
59+
60+ return false ;
61+ } ;
62+
3963 // Ferry parameters to a URL
4064 const ferryParams = ( targetUrl : string ) => {
65+ // Validate URL safety first
66+ if ( ! isSafeUrl ( targetUrl ) ) {
67+ return targetUrl ;
68+ }
69+
4170 const params = getCurrentParams ( ) ;
4271
4372 if ( Object . keys ( params ) . length === 0 ) {
@@ -47,14 +76,32 @@ export default function ParamFerry(): null {
4776 try {
4877 const url = new URL ( targetUrl , window . location . origin ) ;
4978
50- // Add parameters to the URL
79+ // Double-check the constructed URL is safe
80+ if ( ! isSafeUrl ( url . toString ( ) ) ) {
81+ return targetUrl ;
82+ }
83+
84+ // Add parameters to the URL with validation
5185 Object . entries ( params ) . forEach ( ( [ key , value ] ) => {
52- if ( value ) {
53- url . searchParams . set ( key , value ) ;
86+ if ( value && typeof value === 'string' ) {
87+ // Sanitize parameter key and value
88+ const sanitizedKey = key . replace ( / [ ^ \w \- . _ ~ ] / g, '' ) ;
89+ const sanitizedValue = value . replace ( / [ \r \n \t ] / g, '' ) . substring ( 0 , 500 ) ; // Limit length and remove control characters
90+
91+ if ( sanitizedKey && sanitizedValue ) {
92+ url . searchParams . set ( sanitizedKey , sanitizedValue ) ;
93+ }
5494 }
5595 } ) ;
5696
57- return url . toString ( ) ;
97+ const result = url . toString ( ) ;
98+
99+ // Final safety check
100+ if ( ! isSafeUrl ( result ) ) {
101+ return targetUrl ;
102+ }
103+
104+ return result ;
58105 } catch ( error ) {
59106 console . warn ( 'Error ferrying parameters:' , error ) ;
60107 return targetUrl ;
@@ -73,12 +120,13 @@ export default function ParamFerry(): null {
73120 // Skip if already processed
74121 if ( anchor . hasAttribute ( 'data-param-ferried' ) ) return ;
75122
76- // Skip external links, anchors, mailto, and tel links
123+ // Skip external links, anchors, mailto, tel links, and unsafe URLs
77124 if (
78125 ( href . startsWith ( 'http' ) && ! href . includes ( window . location . hostname ) ) ||
79126 href . startsWith ( '#' ) ||
80127 href . startsWith ( 'mailto:' ) ||
81- href . startsWith ( 'tel:' )
128+ href . startsWith ( 'tel:' ) ||
129+ ! isSafeUrl ( href )
82130 ) {
83131 anchor . setAttribute ( 'data-param-ferried' , 'skip' ) ;
84132 return ;
@@ -87,14 +135,20 @@ export default function ParamFerry(): null {
87135 try {
88136 const ferriedUrl = ferryParams ( href ) ;
89137
90- if ( ferriedUrl !== href ) {
138+ if ( ferriedUrl !== href && isSafeUrl ( ferriedUrl ) ) {
91139 // For relative URLs, extract just the path and search params
92140 if ( ! href . startsWith ( 'http' ) ) {
93141 const url = new URL ( ferriedUrl ) ;
94142 const newHref = url . pathname + url . search + url . hash ;
95- anchor . setAttribute ( 'href' , newHref ) ;
143+ // Final validation before setting href
144+ if ( isSafeUrl ( newHref ) ) {
145+ anchor . setAttribute ( 'href' , newHref ) ;
146+ }
96147 } else {
97- anchor . setAttribute ( 'href' , ferriedUrl ) ;
148+ // Final validation before setting href
149+ if ( isSafeUrl ( ferriedUrl ) ) {
150+ anchor . setAttribute ( 'href' , ferriedUrl ) ;
151+ }
98152 }
99153 }
100154
0 commit comments