Spaces:
Running
Running
| // Production hardening: security headers + an audit log. | |
| // Helmet-style headers. The always-on ones are safe for any app. A Content-Security-Policy is powerful | |
| // but easy to break (fonts need allow-listed origins), so it's OPT-IN via ENABLE_CSP=1. | |
| export function securityHeaders() { | |
| const cspParts = process.env.ENABLE_CSP === "1" ? [ | |
| "default-src 'self'", | |
| "script-src 'self' 'unsafe-inline' 'unsafe-eval'", | |
| "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com", | |
| "font-src 'self' https://fonts.gstatic.com data:", | |
| "img-src 'self' data: blob: https:", | |
| "media-src 'self' blob: https:", | |
| "connect-src 'self'", | |
| "worker-src 'self' blob:", | |
| ] : []; | |
| // Allow embedding this app in an <iframe> on specific origins — e.g. a Hugging Face Space renders | |
| // the app inside huggingface.co. Set EMBED_ORIGINS to a space-separated origin list; we then send a | |
| // CSP `frame-ancestors` (the modern, embed-friendly control) and OMIT the blocking X-Frame-Options. | |
| // Unset → the secure default for self-hosting (X-Frame-Options: SAMEORIGIN, no framing). | |
| const embed = (process.env.EMBED_ORIGINS || "").trim(); | |
| if (embed) cspParts.push(`frame-ancestors 'self' ${embed}`); | |
| const csp = cspParts.length ? cspParts.join("; ") : null; | |
| return (req, res, next) => { | |
| res.setHeader("X-Content-Type-Options", "nosniff"); | |
| if (!embed) res.setHeader("X-Frame-Options", "SAMEORIGIN"); | |
| res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin"); | |
| res.setHeader("X-DNS-Prefetch-Control", "off"); | |
| res.setHeader("Permissions-Policy", "geolocation=(), microphone=(self), camera=(self)"); | |
| if (process.env.NODE_ENV === "production") res.setHeader("Strict-Transport-Security", "max-age=31536000; includeSubDomains"); | |
| if (csp) res.setHeader("Content-Security-Policy", csp); | |
| next(); | |
| }; | |
| } | |
| // audit log for sensitive actions (admin actions) — no raw PII | |
| export const auditLog = (event, data = {}) => { | |
| try { console.log(`[audit] ${new Date().toISOString()} ${event} ${JSON.stringify(data)}`); } catch { /* ignore */ } | |
| }; | |