e2bd1d95ed
Audit di sicurezza su tutta l'app. Report in .planning/SECURITY-SCAN.md (codice), .planning/SECURITY-AUDIT-INFRA.md (dipendenze/segreti/deploy) e piano in .planning/SECURITY-REMEDIATION-PLAN.md. CRITICO — l'autorizzazione admin era un unico punto di rottura: nessuna delle 21 pagine /admin controllava la sessione e admin/layout.tsx renderizzava comunque i figli quando mancava. L'unico guard era proxy.ts, su un Next.js affetto da GHSA-6gpp-xcg3-4w24 (proxy bypass). Ora il layout è un secondo gate indipendente; proxy.ts marca il path con un token derivato da NEXTAUTH_SECRET, così il gate non è aggirabile forgiando header e fallisce chiuso se il proxy non gira. ALTO — gli slug cliente avevano 4 caratteri casuali da Math.random() (~20 bit, 1.7M tentativi) e risolvono prima del token: ora 12 caratteri via nanoid (CSPRNG, ~62 bit). Aggiunto rate limit al ramo /client/, che ne era privo. ALTO — src/lib/quote-actions.ts esponeva due server action pubbliche senza autenticazione, una delle quali scriveva su DB. Codice morto, zero chiamanti: rimosso. MEDIO — i quattro dangerouslySetInnerHTML nelle sezioni proposta rendevano output AI come HTML grezzo su pagina pubblica, alimentato da transcript di terzi. Sostituiti con RichText (whitelist di emphasis, nessun HTML al DOM). I transcript ora sono recintati in tag che il system prompt dichiara essere dati, non istruzioni. Inoltre: next 16.2.6 -> 16.2.12 e next-auth 4.24.14 -> 4.24.15 (chiude 9 CVE Next piu GHSA-xmf8-cvqr-rfgj su getToken, raggiungibile dal proxy); HSTS e CSP; potatura della Map di rate-limit.ts, che cresceva senza limite; espunta la password Postgres di produzione dai due 07-01-SUMMARY.md. Verificato: tsc pulito, build OK, smoke test su login/redirect/header forgiati. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
51 lines
1.8 KiB
TypeScript
51 lines
1.8 KiB
TypeScript
import type { NextConfig } from "next";
|
|
|
|
// Enforcing, but deliberately permissive on script-src.
|
|
//
|
|
// A nonce-based script-src would mean reading headers() in the root layout,
|
|
// which opts every route out of static rendering — today `/` is served from
|
|
// cache with s-maxage=31536000. That trade is not worth it while the only
|
|
// inline script is the theme FOUC guard in src/app/layout.tsx and the XSS sink
|
|
// it would defend has already been removed (C-4 in .planning/SECURITY-SCAN.md).
|
|
//
|
|
// The remaining directives cost nothing and still close real avenues: no
|
|
// plugins, no <base> hijacking, no framing, no posting form data off-site, and
|
|
// no exfiltration channel to an arbitrary host.
|
|
const contentSecurityPolicy = [
|
|
"default-src 'self'",
|
|
"script-src 'self' 'unsafe-inline'",
|
|
"style-src 'self' 'unsafe-inline'",
|
|
"img-src 'self' data: https:",
|
|
"font-src 'self' data:",
|
|
"connect-src 'self'",
|
|
"object-src 'none'",
|
|
"base-uri 'self'",
|
|
"form-action 'self'",
|
|
"frame-ancestors 'none'",
|
|
"upgrade-insecure-requests",
|
|
].join("; ");
|
|
|
|
const securityHeaders = [
|
|
{ key: "X-Frame-Options", value: "DENY" },
|
|
{ key: "X-Content-Type-Options", value: "nosniff" },
|
|
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
|
|
{ key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=()" },
|
|
{ key: "X-DNS-Prefetch-Control", value: "on" },
|
|
// Client dashboard tokens travel in the URL path, so an active downgrade
|
|
// would expose them in cleartext — HSTS matters more here than usual.
|
|
{
|
|
key: "Strict-Transport-Security",
|
|
value: "max-age=63072000; includeSubDomains; preload",
|
|
},
|
|
{ key: "Content-Security-Policy", value: contentSecurityPolicy },
|
|
];
|
|
|
|
const nextConfig: NextConfig = {
|
|
output: "standalone",
|
|
async headers() {
|
|
return [{ source: "/(.*)", headers: securityHeaders }];
|
|
},
|
|
};
|
|
|
|
export default nextConfig;
|