File tree Expand file tree Collapse file tree
apps/console/src/components Expand file tree Collapse file tree Original file line number Diff line number Diff line change @@ -65,7 +65,17 @@ function useNavOrder(appName: string) {
6565 const [ orderMap , setOrderMap ] = React . useState < Record < string , string [ ] > > ( ( ) => {
6666 try {
6767 const raw = localStorage . getItem ( storageKey ) ;
68- return raw ? JSON . parse ( raw ) : { } ;
68+ if ( ! raw ) return { } ;
69+ const parsed : unknown = JSON . parse ( raw ) ;
70+ // Validate shape: must be a plain object with string[] values
71+ if ( typeof parsed !== 'object' || parsed === null || Array . isArray ( parsed ) ) return { } ;
72+ const result : Record < string , string [ ] > = { } ;
73+ for ( const [ k , v ] of Object . entries ( parsed as Record < string , unknown > ) ) {
74+ if ( Array . isArray ( v ) && v . every ( ( i : unknown ) => typeof i === 'string' ) ) {
75+ result [ k ] = v as string [ ] ;
76+ }
77+ }
78+ return result ;
6979 } catch {
7080 return { } ;
7181 }
Original file line number Diff line number Diff line change @@ -118,6 +118,20 @@ export function useRealtimeSubscription<T = unknown>(config: RealtimeConfig): Re
118118 return ;
119119 }
120120
121+ // Validate WebSocket URL protocol (only ws: or wss: allowed)
122+ try {
123+ const parsed = new URL ( url ) ;
124+ if ( parsed . protocol !== 'ws:' && parsed . protocol !== 'wss:' ) {
125+ setError ( new Error ( 'WebSocket URL must use ws: or wss: protocol' ) ) ;
126+ setConnectionState ( 'error' ) ;
127+ return ;
128+ }
129+ } catch {
130+ setError ( new Error ( 'Invalid WebSocket URL' ) ) ;
131+ setConnectionState ( 'error' ) ;
132+ return ;
133+ }
134+
121135 // Close existing connection
122136 if ( wsRef . current ) {
123137 intentionalCloseRef . current = true ;
Original file line number Diff line number Diff line change @@ -70,7 +70,14 @@ export function CollaborationProvider({
7070 if ( ! canUseWS ) return ;
7171
7272 function connect ( ) {
73- const url = new URL ( config . serverUrl ! ) ;
73+ // Validate WebSocket URL protocol (only ws: or wss: allowed)
74+ let url : URL ;
75+ try {
76+ url = new URL ( config . serverUrl ! ) ;
77+ if ( url . protocol !== 'ws:' && url . protocol !== 'wss:' ) return ;
78+ } catch {
79+ return ;
80+ }
7481 if ( config . roomId ) url . searchParams . set ( 'room' , config . roomId ) ;
7582 url . searchParams . set ( 'userId' , user ! . id ) ;
7683
Original file line number Diff line number Diff line change @@ -287,6 +287,19 @@ export function useETagCache(userConfig: ETagCacheConfig = {}): ETagCacheResult
287287 url : string ,
288288 options ?: RequestInit ,
289289 ) : Promise < { data : T ; fromCache : boolean ; etag ?: string } > => {
290+ // Validate URL: only allow http(s) or relative URLs
291+ if ( url . includes ( '://' ) ) {
292+ try {
293+ const parsed = new URL ( url ) ;
294+ if ( parsed . protocol !== 'http:' && parsed . protocol !== 'https:' ) {
295+ throw new Error ( `Unsupported URL protocol: ${ parsed . protocol } ` ) ;
296+ }
297+ } catch ( e ) {
298+ if ( e instanceof Error && e . message . startsWith ( 'Unsupported' ) ) throw e ;
299+ throw new Error ( 'Invalid URL' ) ;
300+ }
301+ }
302+
290303 if ( ! configRef . current . enabled ) {
291304 const res = await fetch ( url , options ) ;
292305 const data = ( await res . json ( ) ) as T ;
You can’t perform that action at this time.
0 commit comments