Restore config/security_headers.php

mmdvmhost/lh.php (and admin/mmdvmhost/lh.php) require_once this file
and call setEmbeddableSecurityHeaders(), but the file was never
committed to this repo (confirmed absent across main/dev/development
history), causing a fatal "failed opening required" 500 on the Last
Heard panel.

Restores both setSecurityHeaders() (full page: adds X-Frame-Options +
CSP frame-ancestors) and setEmbeddableSecurityHeaders() (AJAX partial
variant, omits the framing directives since it's loaded into the
parent via $.load(), not an iframe), matching the behavior described
in the comments already present in lh.php and functions.php.
This commit is contained in:
2026-07-19 18:41:31 +08:00
parent 0febcf69f4
commit 0c4bed5b6f
+50
View File
@@ -0,0 +1,50 @@
<?php
/**
* Security response headers, shared by full pages and AJAX-loaded partials.
*
* Two variants because the dashboard mixes top-level pages with partials
* that get pulled into them via jQuery's $.load() (see mmdvmhost/lh.php,
* mmdvmhost/bm_links.php, dstarrepeater/css_connections.php, etc.):
*
* - setSecurityHeaders() Full page. Locks framing to same
* origin (X-Frame-Options + CSP
* frame-ancestors) on top of the
* baseline hardening headers.
* - setEmbeddableSecurityHeaders() AJAX partial. Same baseline
* headers, but deliberately omits
* the framing directives — the
* partial is loaded into the
* parent document via XHR, not an
* iframe, so frame-ancestors has
* no target; the *parent* page
* already asserted it when it was
* first requested.
*
* Both are no-ops if headers were already sent (e.g. a caller invokes
* both variants back to back, or output has already started) so they
* are always safe to call defensively.
*/
if (!function_exists('setSecurityHeaders')) {
function setSecurityHeaders() {
if (headers_sent()) {
return;
}
header('X-Content-Type-Options: nosniff');
header('Referrer-Policy: same-origin');
header('X-XSS-Protection: 0');
header('X-Frame-Options: SAMEORIGIN');
header("Content-Security-Policy: frame-ancestors 'self'");
}
}
if (!function_exists('setEmbeddableSecurityHeaders')) {
function setEmbeddableSecurityHeaders() {
if (headers_sent()) {
return;
}
header('X-Content-Type-Options: nosniff');
header('Referrer-Policy: same-origin');
header('X-XSS-Protection: 0');
}
}