forked from ORGANISATION-KA-INTERNATIONALE/FEDIVERSE-OKI
feat: improve security headers and csp nonce handling
This commit is contained in:
+1
-1
@@ -74,7 +74,7 @@
|
||||
<button id="theme-toggle" class="icon-button" aria-label="Basculer entre mode clair et sombre" title="Changer le thème">
|
||||
<i class="fas fa-sun" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button id="install-pwa" class="icon-button install-pwa-button" style="display: none;" aria-label="Installer l'application">
|
||||
<button id="install-pwa" class="icon-button install-pwa-button" style="display: none;" nonce="<?php echo getCspNonce(); ?>" aria-label="Installer l'application">
|
||||
<i class="fas fa-download" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button class="mobile-menu-toggle" aria-expanded="false" aria-controls="mobile-menu" aria-label="Ouvrir le menu de navigation">
|
||||
|
||||
@@ -82,7 +82,7 @@ if ($heroType === 'none') {
|
||||
$bgImageStyle = 'background-image: url(\'' . htmlspecialchars(NEXT_LIVE_IMAGE) . '\');';
|
||||
}
|
||||
?>
|
||||
<div class="hero-next-live" style="<?php echo $bgImageStyle; ?>">
|
||||
<div class="hero-next-live" style="<?php echo $bgImageStyle; ?>" nonce="<?php echo getCspNonce(); ?>">
|
||||
<?php if (!empty(NEXT_LIVE_IMAGE) && file_exists(NEXT_LIVE_IMAGE)): ?>
|
||||
<div class="hero-next-live-image-container">
|
||||
<img src="<?php echo htmlspecialchars(NEXT_LIVE_IMAGE); ?>"
|
||||
|
||||
@@ -18,7 +18,7 @@ function addPWAHeaders() {
|
||||
function addPWAScripts() {
|
||||
?>
|
||||
<!-- PWA Service Worker -->
|
||||
<script>
|
||||
<script nonce="<?php echo getCspNonce(); ?>">
|
||||
if ('serviceWorker' in navigator) {
|
||||
window.addEventListener('load', function() {
|
||||
navigator.serviceWorker.register('/sw.js')
|
||||
|
||||
+46
-27
@@ -177,26 +177,48 @@ function validateCSRFToken($token) {
|
||||
return hash_equals($_SESSION['csrf_token'], $token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Génère ou récupère le nonce CSP de la requête courante.
|
||||
* Doit être appelé après setSecurityHeaders() pour que le nonce soit envoyé dans le header CSP.
|
||||
*
|
||||
* @return string Nonce CSP
|
||||
*/
|
||||
function getCspNonce() {
|
||||
if (!isset($GLOBALS['csp_nonce'])) {
|
||||
$GLOBALS['csp_nonce'] = base64_encode(random_bytes(16));
|
||||
}
|
||||
return $GLOBALS['csp_nonce'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Applique des en-têtes de sécurité HTTP
|
||||
*/
|
||||
function setSecurityHeaders() {
|
||||
// Protection contre le clickjacking (permettre les iframes du même site)
|
||||
header('X-Frame-Options: SAMEORIGIN');
|
||||
|
||||
|
||||
// Protection contre le MIME sniffing
|
||||
header('X-Content-Type-Options: nosniff');
|
||||
|
||||
|
||||
// Protection XSS basique
|
||||
header('X-XSS-Protection: 1; mode=block');
|
||||
|
||||
|
||||
// Politique de référent
|
||||
header('Referrer-Policy: strict-origin-when-cross-origin');
|
||||
|
||||
|
||||
// Isolation cross-origin
|
||||
header('Cross-Origin-Resource-Policy: same-origin');
|
||||
header('Cross-Origin-Opener-Policy: same-origin');
|
||||
header('Cross-Origin-Embedder-Policy: require-corp');
|
||||
|
||||
// Permissions Policy (feature policy)
|
||||
header('Permissions-Policy: accelerometer=(), autoplay=(), camera=(), cross-origin-isolated=(), display-capture=(), encrypted-media=(), fullscreen=(self), geolocation=(), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), payment=(), picture-in-picture=(self), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=()');
|
||||
|
||||
// Content Security Policy avec support Mastodon et PeerTube
|
||||
$nonce = getCspNonce();
|
||||
$mastodonDomain = '';
|
||||
$peertubeDomain = '';
|
||||
|
||||
|
||||
// Extraire le domaine Mastodon si configuré
|
||||
if (defined('MASTODON_INSTANCE_URL')) {
|
||||
$mastodonParsed = parse_url(MASTODON_INSTANCE_URL);
|
||||
@@ -204,7 +226,7 @@ function setSecurityHeaders() {
|
||||
$mastodonDomain = $mastodonParsed['scheme'] . '://' . $mastodonParsed['host'];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Extraire le domaine PeerTube si configuré
|
||||
if (defined('PEERTUBE_URL')) {
|
||||
$peertubeParsed = parse_url(PEERTUBE_URL);
|
||||
@@ -212,15 +234,15 @@ function setSecurityHeaders() {
|
||||
$peertubeDomain = $peertubeParsed['scheme'] . '://' . $peertubeParsed['host'];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Détecter si on est en développement local
|
||||
$isLocalDev = in_array($_SERVER['HTTP_HOST'] ?? '', ['127.0.0.1:8080', '127.0.0.1:8001', 'localhost:8080', 'localhost:8001', '127.0.0.1', 'localhost']);
|
||||
|
||||
|
||||
$csp = "default-src 'self'; ";
|
||||
$csp .= "style-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com; ";
|
||||
$csp .= "script-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com https://plausible.io; "; // PLAUSIBLE UPDATED
|
||||
|
||||
// Images : autoriser les domaines externes plus HTTPS général en dev
|
||||
$csp .= "style-src 'self' 'nonce-{$nonce}' https://cdnjs.cloudflare.com; ";
|
||||
$csp .= "script-src 'self' 'nonce-{$nonce}' https://cdnjs.cloudflare.com https://plausible.io; ";
|
||||
|
||||
// Images : autoriser les domaines connus plus HTTPS général pour le contenu fédéré
|
||||
$imgSrc = "'self' data: " . ($mastodonDomain ? $mastodonDomain : '') . " " . ($peertubeDomain ? $peertubeDomain : '');
|
||||
if ($isLocalDev) {
|
||||
$imgSrc .= " https: http:";
|
||||
@@ -228,34 +250,30 @@ function setSecurityHeaders() {
|
||||
$imgSrc .= " https:";
|
||||
}
|
||||
$csp .= "img-src " . $imgSrc . "; ";
|
||||
|
||||
|
||||
$csp .= "font-src 'self' https://cdnjs.cloudflare.com; ";
|
||||
|
||||
// Frames : autoriser PeerTube et HTTPS général
|
||||
|
||||
// Frames : autoriser PeerTube uniquement
|
||||
$frameSrc = "'self' " . ($peertubeDomain ? $peertubeDomain : '');
|
||||
if ($isLocalDev) {
|
||||
$frameSrc .= " https: http:";
|
||||
} else {
|
||||
$frameSrc .= " https:";
|
||||
}
|
||||
$csp .= "frame-src " . $frameSrc . "; ";
|
||||
|
||||
// Connexions : autoriser Mastodon et PeerTube
|
||||
|
||||
// Connexions : autoriser Mastodon, PeerTube et Plausible
|
||||
$connectSrc = "'self' https://plausible.io " . ($mastodonDomain ? $mastodonDomain : '') . " " . ($peertubeDomain ? $peertubeDomain : '');
|
||||
if ($isLocalDev) {
|
||||
$connectSrc .= " ws: wss:"; // WebSockets pour le dev
|
||||
}
|
||||
$csp .= "connect-src " . $connectSrc . "; ";
|
||||
|
||||
// Médias : toujours autoriser 'self', Mastodon et PeerTube
|
||||
|
||||
// Médias : autoriser 'self', Mastodon, PeerTube et S3 Mastodon
|
||||
$mediaSrc = "'self'";
|
||||
|
||||
// Ajouter l'instance Mastodon (pour les médias stockés sur l'instance)
|
||||
if ($mastodonDomain) {
|
||||
$mediaSrc .= " " . $mastodonDomain;
|
||||
}
|
||||
|
||||
// Ajouter PeerTube
|
||||
if ($peertubeDomain) {
|
||||
$mediaSrc .= " " . $peertubeDomain;
|
||||
}
|
||||
@@ -277,13 +295,14 @@ function setSecurityHeaders() {
|
||||
$csp .= "media-src " . $mediaSrc . "; ";
|
||||
|
||||
$csp .= "object-src 'none'; ";
|
||||
$csp .= "base-uri 'self';";
|
||||
|
||||
$csp .= "base-uri 'self'; ";
|
||||
$csp .= "frame-ancestors 'self';";
|
||||
|
||||
header('Content-Security-Policy: ' . $csp);
|
||||
|
||||
|
||||
// HTTPS strict transport security (seulement si HTTPS)
|
||||
if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') {
|
||||
header('Strict-Transport-Security: max-age=31536000; includeSubDomains');
|
||||
header('Strict-Transport-Security: max-age=63072000; includeSubDomains; preload');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -313,7 +313,8 @@ function getBaseUrl() {
|
||||
* @param string $jsonLd Données JSON-LD
|
||||
*/
|
||||
function outputJsonLd($jsonLd) {
|
||||
echo '<script type="application/ld+json">' . "\n" . $jsonLd . "\n" . '</script>' . "\n";
|
||||
$nonce = getCspNonce();
|
||||
echo '<script type="application/ld+json" nonce="' . $nonce . '">' . "\n" . $jsonLd . "\n" . '</script>' . "\n";
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
Reference in New Issue
Block a user