/* Génère les fichiers de configuration CSP du déploiement statique : - build/nginx-csp.conf → snippet à inclure dans la config nginx de l'app YunoHost (cible de production : https://syel.o-k-i.net). - build/.htaccess → équivalent Apache (secours, ignoré par nginx). Pourquoi : SvelteKit démarre l'hydratation via un script inline dans le HTML. Une CSP stricte sans « unsafe-inline » (ex. script-src 'self') bloque ce bootstrap → site visible mais non interactif. Les empreintes SHA-256 des scripts inline l'autorisent précisément, sans unsafe-inline. Régénéré à chaque build (les empreintes changent avec les chunks). */ import { createHash } from 'node:crypto'; import { readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs'; import { join } from 'node:path'; import { fileURLToPath } from 'node:url'; const buildDir = fileURLToPath(new URL('../build/', import.meta.url)); function* walkHtml(dir) { for (const entry of readdirSync(dir)) { const full = join(dir, entry); if (statSync(full).isDirectory()) yield* walkHtml(full); else if (entry.endsWith('.html')) yield full; } } const hashes = new Set(); for (const file of walkHtml(buildDir)) { const html = readFileSync(file, 'utf8'); // Scripts inline uniquement (pas d'attribut src) — le navigateur empreinte // le contenu texte exact du bloc. for (const match of html.matchAll(/]*\bsrc\s*=)[^>]*>([\s\S]*?)<\/script>/g)) { const content = match[1]; if (!content.trim()) continue; const digest = createHash('sha256').update(content, 'utf8').digest('base64'); hashes.add(`'sha256-${digest}'`); } } if (hashes.size === 0) { console.warn('postbuild-csp: aucun script inline trouvé — vérifier le build'); } // Politique sobre pour un site statique autohébergé, + empreintes du bootstrap. const csp = [ "default-src 'self'", `script-src 'self' ${[...hashes].sort().join(' ')}`, "style-src 'self' 'unsafe-inline'", "img-src 'self'", "font-src 'self'", "connect-src 'self'", "manifest-src 'self'", "worker-src 'self'", "frame-ancestors 'none'", "base-uri 'self'", "form-action 'self'", "object-src 'none'" ].join('; '); const nginx = `# Généré par scripts/postbuild-csp.mjs — ne pas éditer à la main. # À inclure dans la config nginx de l'app YunoHost, dans le bloc location # servant les fichiers statiques, ex. : # location / { # alias /var/www//; # include /var/www//nginx-csp.conf; # try_files $uri $uri/ =404; # } # Note nginx : un add_header au niveau le plus précis REMPLACE les add_header # hérités des niveaux supérieurs — une éventuelle CSP globale est neutralisée. # Régénéré à chaque build : le ré-uploader avec le reste du contenu de build/. add_header Content-Security-Policy "${csp}" always; `; const htaccess = `# Généré par scripts/postbuild-csp.mjs — ne pas éditer à la main. # Équivalent Apache du snippet nginx (secours uniquement — nginx l'ignore). Header unset Content-Security-Policy Header always unset Content-Security-Policy Header set Content-Security-Policy "${csp}" `; writeFileSync(join(buildDir, 'nginx-csp.conf'), nginx); writeFileSync(join(buildDir, '.htaccess'), htaccess); console.log(`postbuild-csp: nginx-csp.conf + .htaccess écrits (${hashes.size} empreinte(s))`);