tags can * adopt the nonce incrementally without breaking legacy pages. * 'nonce-enforce' Enforces the nonce policy. This mode is additionally * gated by CACTI_CSP_NONCE_ENFORCE so selecting it before the * migration is complete cannot unexpectedly blank the UI. * 'unsafe-eval' None - most permissive, also allows eval(). This is the * legacy value, kept so upgrades that already selected it are * not silently rewritten. * * Nonce migration keeps the enforced policy byte-compatible and emits the * stricter script-src in a separate report-only header. The rest of each policy * is built at the call sites. The buildScriptSrc()/buildReportUriDirective() * methods are pure so mode branching can be tested without request state. */ class CactiSecureHeaders { /** * Per-request nonce. 18 random bytes base64url-encoded (RFC 4648 sec 5) * give 24 chars with no padding and none of the '+' '/' '=' characters * that are unsafe unquoted in a CSP source token. */ public static function getNonce(): string { static $nonce = null; if ($nonce !== null) { return $nonce; } try { $bytes = random_bytes(18); } catch (\Throwable $e) { if (function_exists('cacti_log')) { cacti_log('CSP nonce generation failed: ' . $e->getMessage(), false, 'SYSTEM'); } throw new \RuntimeException('Unable to generate a cryptographically secure CSP nonce.', 0, $e); } $nonce = rtrim(strtr(base64_encode($bytes), '+/', '-_'), '='); return $nonce; } /** * `nonce="..."` attribute for inline