@@ -3,7 +3,7 @@ import type { RenderResponse } from 'nitro/types'
33import type { Link , SerializableHead } from '@unhead/vue/types'
44import { destr } from 'destr'
55import type { H3Event } from 'nitro/h3'
6- import { defineEventHandler , getQuery , readBody } from 'nitro/h3'
6+ import { defineEventHandler , getQuery , HTTPError , readBody } from 'nitro/h3'
77import { resolveUnrefHeadInput } from '@unhead/vue'
88import { getRequestDependencies } from 'vue-bundle-renderer/runtime'
99import { getQuery as getURLQuery } from 'ufo'
@@ -107,29 +107,37 @@ const handler: ReturnType<typeof defineEventHandler> = defineEventHandler(async
107107
108108export default handler
109109
110+ const ISLAND_PATH_PREFIX = '/__nuxt_island/'
111+ const VALID_COMPONENT_NAME_RE = / ^ [ a - z ] [ \w . - ] * $ / i
112+
110113async function getIslandContext ( event : H3Event ) : Promise < NuxtIslandContext > {
111- // TODO: Strict validation for url
112114 let url = event . url . pathname + event . url . search + event . url . hash
113115 if ( import . meta. prerender && url && await islandPropCache ! . hasItem ( url ) ) {
114116 // rehydrate props from cache so we can rerender island if cache does not have it any more
115117 url = await islandPropCache ! . getItem ( url ) as string
116118 }
117- const componentParts = url . substring ( '/__nuxt_island' . length + 1 ) . replace ( ISLAND_SUFFIX_RE , '' ) . split ( '_' )
119+
120+ if ( ! url . startsWith ( ISLAND_PATH_PREFIX ) ) {
121+ throw new HTTPError ( { status : 400 , statusText : 'Invalid island request path' } )
122+ }
123+
124+ const componentParts = url . substring ( ISLAND_PATH_PREFIX . length ) . replace ( ISLAND_SUFFIX_RE , '' ) . split ( '_' )
118125 const hashId = componentParts . length > 1 ? componentParts . pop ( ) : undefined
119126 const componentName = componentParts . join ( '_' )
120127
121- // TODO: Validate context
128+ if ( ! componentName || ! VALID_COMPONENT_NAME_RE . test ( componentName ) ) {
129+ throw new HTTPError ( { status : 400 , statusText : 'Invalid island component name' } )
130+ }
131+
122132 const context = event . req . method === 'GET' ? getQuery < NuxtIslandContext > ( event ) : await readBody < NuxtIslandContext > ( event )
123133
124- const ctx : NuxtIslandContext = {
125- url : '/' ,
126- ... context ,
134+ // Only extract known context fields to prevent arbitrary data injection
135+ return {
136+ url : typeof context ?. url === 'string' ? context . url : '/' ,
127137 id : hashId ,
128138 name : componentName ,
129139 props : destr ( context ?. props ) || { } ,
130140 slots : { } ,
131141 components : { } ,
132142 }
133-
134- return ctx
135143}
0 commit comments