File size: 2,090 Bytes
39d98a0
 
 
 
 
db56627
39d98a0
 
 
 
 
 
 
 
db56627
 
 
 
 
 
 
 
39d98a0
 
db56627
39d98a0
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 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 */ }
};