diff --git a/src/pages/_api/api/faucet.ts b/src/pages/_api/api/faucet.ts index 67056781..18540b60 100644 --- a/src/pages/_api/api/faucet.ts +++ b/src/pages/_api/api/faucet.ts @@ -53,18 +53,36 @@ async function fund(address: `0x${string}`, headers: Record): Pr } } -function cors(origin: string | null): Record { - const allowedOrigins = ['https://tempo.xyz', 'https://docs.tempo.xyz'] - - if (origin?.includes('vercel.app')) allowedOrigins.push(origin) - if (process.env.NODE_ENV === 'development') allowedOrigins.push('http://localhost:5173') +function isAllowedPreviewOrigin(origin: string): boolean { + try { + const { hostname, protocol } = new URL(origin) + if (protocol !== 'https:') return false + // Hostname suffix — not substring — so attacker.example containing + // "vercel.app" in the path/host cannot spoof preview CORS. + return ( + hostname === 'tempo.xyz' || + hostname.endsWith('.tempo.xyz') || + hostname.endsWith('.vercel.app') + ) + } catch { + return false + } +} +function cors(origin: string | null): Record { const headers: Record = { 'Access-Control-Allow-Methods': 'POST, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, x-api-token', } - if (origin && allowedOrigins.includes(origin)) headers['Access-Control-Allow-Origin'] = origin + if (process.env.NODE_ENV === 'development' && origin === 'http://localhost:5173') { + headers['Access-Control-Allow-Origin'] = origin + return headers + } + + if (origin && isAllowedPreviewOrigin(origin)) { + headers['Access-Control-Allow-Origin'] = origin + } return headers } diff --git a/src/pages/_api/api/index-supply.ts b/src/pages/_api/api/index-supply.ts index 2035baf9..1f85464f 100644 --- a/src/pages/_api/api/index-supply.ts +++ b/src/pages/_api/api/index-supply.ts @@ -119,19 +119,36 @@ export async function OPTIONS(request: Request): Promise { return new Response(null, { status: 200, headers: cors(origin) }) } -function cors(origin: string | null): Record { - const allowedOrigins = ['https://tempo.xyz', 'https://mainnet.docs.tempo.xyz'] - - if (origin?.includes('vercel.app')) allowedOrigins.push(origin) - if (process.env.NODE_ENV === 'development') allowedOrigins.push('http://localhost:5173') +function isAllowedPreviewOrigin(origin: string): boolean { + try { + const { hostname, protocol } = new URL(origin) + if (protocol !== 'https:') return false + // Hostname suffix — not substring — so attacker.example containing + // "vercel.app" in the path/host cannot spoof preview CORS. + return ( + hostname === 'tempo.xyz' || + hostname.endsWith('.tempo.xyz') || + hostname.endsWith('.vercel.app') + ) + } catch { + return false + } +} +function cors(origin: string | null): Record { const headers: Record = { 'Access-Control-Allow-Methods': 'POST, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, x-api-token', } - if (origin && allowedOrigins.some((allowed) => origin.startsWith(allowed))) + if (process.env.NODE_ENV === 'development' && origin === 'http://localhost:5173') { + headers['Access-Control-Allow-Origin'] = origin + return headers + } + + if (origin && isAllowedPreviewOrigin(origin)) { headers['Access-Control-Allow-Origin'] = origin + } return headers }