Files
oki-atlas-fediverse/atlas-fediverse/scripts/postbuild-csp.mjs
T

87 lines
3.3 KiB
JavaScript
Raw Normal View History

/* 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(/<script(?![^>]*\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/<app>/;
# include /var/www/<app>/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).
<IfModule mod_headers.c>
Header unset Content-Security-Policy
Header always unset Content-Security-Policy
Header set Content-Security-Policy "${csp}"
</IfModule>
`;
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))`);