mirror of
https://github.com/supabase/supabase.git
synced 2026-10-11 04:15:04 +03:00
The Next build sets X-Frame-Options / X-Content-Type-Options / HSTS / Content-Security-Policy / Referrer-Policy via next.config.ts `headers()`. The TanStack build never carried these over — vercel.ts only set cache-control, so the deployed dashboard shipped with no CSP. The TanStack deploy serves a static shell (no server to attach headers), so the headers go in the Vercel config. Add security-headers.ts as the shared source of truth (reuses getCSP(), env-gated exactly like next.config) and: - vercel.ts: apply to every response (source '/(.*)'), all base-path prefixes — full getCSP() + HSTS on platform, the deployed surface that matters. - scripts/serve.js: apply the non-platform set (frame-ancestors 'none') for the self-hosted server, matching next.config's non-platform branch. Note: font-src in the CSP is 'self' + cloudflare + supabase-assets (no fonts.gstatic.com), so vendoring the fonts locally in the previous commit is what keeps them CSP-compliant. X-Content-Type-Options keeps next.config's 'no-sniff' value verbatim for parity.
38 lines
1.5 KiB
TypeScript
38 lines
1.5 KiB
TypeScript
import { getCSP } from './csp'
|
|
|
|
// Security response headers for the app. On the Next build these are applied via
|
|
// `next.config.ts` `headers()`; the TanStack build has no such hook and (on
|
|
// Vercel) serves a static shell with no server to attach them, so they're
|
|
// applied through `vercel.ts` (deploy) and `scripts/serve.js` (self-hosted)
|
|
// instead. Keep this in sync with the `/(.*?)` header block in next.config.ts.
|
|
//
|
|
// Env-gated exactly like next.config:
|
|
// - CSP: full `getCSP()` on platform, else just `frame-ancestors 'none'`.
|
|
// - HSTS: only on platform + Vercel (never for self-hosted / previews on a
|
|
// bare IP), so it's omitted rather than sent empty.
|
|
export function getSecurityHeaders(): Array<{ key: string; value: string }> {
|
|
const isPlatform = process.env.NEXT_PUBLIC_IS_PLATFORM === 'true'
|
|
const isVercel = process.env.VERCEL === '1'
|
|
|
|
const headers = [
|
|
{ key: 'X-Frame-Options', value: 'DENY' },
|
|
// Mirrors next.config.ts (note: the effective value browsers honour is
|
|
// `nosniff`; kept identical to the Next build to avoid a behaviour split).
|
|
{ key: 'X-Content-Type-Options', value: 'no-sniff' },
|
|
{
|
|
key: 'Content-Security-Policy',
|
|
value: isPlatform ? getCSP() : "frame-ancestors 'none';",
|
|
},
|
|
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
|
|
]
|
|
|
|
if (isPlatform && isVercel) {
|
|
headers.push({
|
|
key: 'Strict-Transport-Security',
|
|
value: 'max-age=31536000; includeSubDomains; preload',
|
|
})
|
|
}
|
|
|
|
return headers
|
|
}
|