11// Proxy Drizzle Studio frontend to avoid CORS/Private Network Access issues
22// Serving from localhost allows connecting to localhost:4983 without browser blocking
33
4- export default defineEventHandler ( async ( event ) => {
4+ import { eventHandler , getQuery , setHeader , createError } from 'h3'
5+
6+ export default eventHandler ( async ( event ) => {
57 const query = getQuery ( event )
6- const port = query . port || 4983
8+
9+ // Validate port to prevent XSS injection
10+ const port = parseInt ( String ( query . port ) ) || 4983
11+ if ( port < 1 || port > 65535 ) {
12+ throw createError ( { statusCode : 400 , message : 'Invalid port' } )
13+ }
14+
715 // Get path without query string
816 const fullPath = event . path ?. split ( '?' ) [ 0 ] || ''
917 const path = fullPath . replace ( '/api/_hub/studio' , '' ) || '/'
1018
19+ // Validate path to prevent SSRF and path traversal
20+ if ( path !== '/' && ( ! / ^ \/ [ a - z A - Z 0 - 9 . _ - ] + $ / . test ( path ) || path . includes ( '..' ) ) ) {
21+ throw createError ( { statusCode : 400 , message : 'Invalid path' } )
22+ }
1123
1224 // Proxy asset requests (JS, CSS, etc.)
1325 if ( path !== '/' && path !== '' ) {
1426 try {
15- // Use text for JS/CSS, blob for binary
1627 const isBinary = path . endsWith ( '.svg' ) || path . endsWith ( '.png' ) || path . endsWith ( '.ico' )
1728 const asset = await $fetch ( `https://local.drizzle.studio${ path } ` , {
1829 responseType : isBinary ? 'blob' : 'text'
@@ -22,17 +33,24 @@ export default defineEventHandler(async (event) => {
2233 if ( path . endsWith ( '.js' ) ) setHeader ( event , 'Content-Type' , 'application/javascript' )
2334 else if ( path . endsWith ( '.css' ) ) setHeader ( event , 'Content-Type' , 'text/css' )
2435 else if ( path . endsWith ( '.svg' ) ) setHeader ( event , 'Content-Type' , 'image/svg+xml' )
36+ else if ( path . endsWith ( '.png' ) ) setHeader ( event , 'Content-Type' , 'image/png' )
37+ else if ( path . endsWith ( '.ico' ) ) setHeader ( event , 'Content-Type' , 'image/x-icon' )
2538
2639 return asset
27- } catch ( error : any ) {
28- throw createError ( { statusCode : 502 , message : ` Failed to fetch studio asset: ${ path } ` } )
40+ } catch {
41+ throw createError ( { statusCode : 502 , message : ' Failed to fetch studio asset' } )
2942 }
3043 }
3144
3245 // Fetch the Drizzle Studio HTML
33- const html = await $fetch < string > ( 'https://local.drizzle.studio/' , {
34- headers : { 'Accept' : 'text/html' }
35- } )
46+ let html : string
47+ try {
48+ html = await $fetch < string > ( 'https://local.drizzle.studio/' , {
49+ headers : { 'Accept' : 'text/html' }
50+ } )
51+ } catch {
52+ throw createError ( { statusCode : 502 , message : 'Failed to fetch Drizzle Studio' } )
53+ }
3654
3755 // Rewrite asset paths to go through our proxy
3856 let modifiedHtml = html
0 commit comments