Commit technique : reproduit à l'identique l'état non commité de la copie de travail (sécurisation V7.8, CSP à nonce, Session, migration des droits) afin que le travail d'ergonomie qui suit soit isolable. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
90 lines
2.7 KiB
PHP
90 lines
2.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Core;
|
|
|
|
/**
|
|
* En-têtes de sécurité des réponses HTML.
|
|
*
|
|
* La CSP est nominative : aucun script inline n'est autorisé sans le nonce
|
|
* généré à chaque requête. PTA ne contient aucun gestionnaire d'événement
|
|
* inline (onclick=, onchange=…) ni style inline, la politique peut donc rester
|
|
* stricte sans dégrader l'interface.
|
|
*/
|
|
final class Security
|
|
{
|
|
private static ?string $nonce = null;
|
|
|
|
public static function nonce(): string
|
|
{
|
|
if (self::$nonce === null) {
|
|
self::$nonce = base64_encode(random_bytes(16));
|
|
}
|
|
|
|
return self::$nonce;
|
|
}
|
|
|
|
public static function sendHtmlHeaders(): void
|
|
{
|
|
if (headers_sent()) {
|
|
return;
|
|
}
|
|
|
|
$nonce = self::nonce();
|
|
$policy = [
|
|
"default-src 'self'",
|
|
"base-uri 'self'",
|
|
"object-src 'none'",
|
|
"frame-ancestors 'none'",
|
|
"form-action 'self'",
|
|
"img-src 'self' data:",
|
|
"style-src 'self'",
|
|
"font-src 'self'",
|
|
// Aucune requête sortante depuis le navigateur : les appels à
|
|
// data.education.gouv.fr sont faits côté serveur.
|
|
"connect-src 'self'",
|
|
sprintf("script-src 'self' 'nonce-%s'", $nonce),
|
|
];
|
|
|
|
header('Content-Security-Policy: ' . implode('; ', $policy));
|
|
header('X-Content-Type-Options: nosniff');
|
|
header('X-Frame-Options: DENY');
|
|
header('Referrer-Policy: no-referrer');
|
|
header('Permissions-Policy: geolocation=(), camera=(), microphone=(), payment=()');
|
|
header('Cross-Origin-Opener-Policy: same-origin');
|
|
|
|
if (Session::isHttps()) {
|
|
header('Strict-Transport-Security: max-age=31536000; includeSubDomains');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Vrai uniquement si le mode debug a été activé explicitement.
|
|
* Par défaut, aucun détail technique ne sort du serveur.
|
|
*/
|
|
public static function debugEnabled(): bool
|
|
{
|
|
return in_array(strtolower((string) (getenv('PTA_DEBUG') ?: '0')), ['1', 'true', 'on', 'yes'], true);
|
|
}
|
|
|
|
/**
|
|
* Journalise l'exception côté serveur et renvoie le message à afficher.
|
|
* Le détail technique n'est rendu que si PTA_DEBUG est explicitement actif.
|
|
*/
|
|
public static function reportThrowable(\Throwable $exception, string $publicMessage): string
|
|
{
|
|
error_log(sprintf(
|
|
'[PTA] %s: %s in %s:%d',
|
|
get_class($exception),
|
|
$exception->getMessage(),
|
|
$exception->getFile(),
|
|
$exception->getLine()
|
|
));
|
|
|
|
return self::debugEnabled()
|
|
? $publicMessage . ' Détail : ' . $exception->getMessage()
|
|
: $publicMessage;
|
|
}
|
|
}
|