Harden MkDocs header auth-check: targeted postMessage, tighter CSP
- Replace postMessage wildcard ('*') with explicit parent origin passed
via ?origin= parameter to prevent auth state disclosure to arbitrary
embedders
- Tighten frame-ancestors CSP: production restricts to self + DOMAIN,
dev restricts to localhost origins (was frame-ancestors *)
- Remove deprecated X-Frame-Options ALLOW-FROM header (CSP
frame-ancestors is the modern replacement)
- Validate targetOrigin with URL constructor before use
Bunker Admin
This commit is contained in:
parent
eba6453981
commit
3f35e4b18d
@ -5,6 +5,7 @@
|
|||||||
// This page is loaded in a hidden iframe from the MkDocs header.
|
// This page is loaded in a hidden iframe from the MkDocs header.
|
||||||
// It reads the auth state from this origin's localStorage and
|
// It reads the auth state from this origin's localStorage and
|
||||||
// posts it back to the parent window via postMessage.
|
// posts it back to the parent window via postMessage.
|
||||||
|
// The parent passes its origin as ?origin=... so we can target the reply.
|
||||||
(function() {
|
(function() {
|
||||||
var authenticated = false;
|
var authenticated = false;
|
||||||
try {
|
try {
|
||||||
@ -16,11 +17,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch(e) {}
|
} catch(e) {}
|
||||||
|
// Only post back to the declared parent origin (prevents state disclosure to arbitrary embedders)
|
||||||
|
var params = new URLSearchParams(location.search);
|
||||||
|
var targetOrigin = params.get('origin');
|
||||||
|
if (!targetOrigin) return;
|
||||||
|
// Validate targetOrigin is a proper origin (protocol + host, no path)
|
||||||
|
try {
|
||||||
|
var url = new URL(targetOrigin);
|
||||||
|
targetOrigin = url.origin;
|
||||||
|
} catch(e) { return; }
|
||||||
if (window.parent && window.parent !== window) {
|
if (window.parent && window.parent !== window) {
|
||||||
window.parent.postMessage({
|
window.parent.postMessage({
|
||||||
type: 'cml-auth-status',
|
type: 'cml-auth-status',
|
||||||
authenticated: authenticated
|
authenticated: authenticated
|
||||||
}, '*');
|
}, targetOrigin);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -716,7 +716,7 @@ class HeaderBuilderService {
|
|||||||
// 2. Cross-origin check via hidden iframe + postMessage
|
// 2. Cross-origin check via hidden iframe + postMessage
|
||||||
var iframe = document.createElement('iframe');
|
var iframe = document.createElement('iframe');
|
||||||
iframe.style.display = 'none';
|
iframe.style.display = 'none';
|
||||||
iframe.src = base + '/auth-check.html';
|
iframe.src = base + '/auth-check.html?origin=' + encodeURIComponent(location.origin);
|
||||||
window.addEventListener('message', function(event) {
|
window.addEventListener('message', function(event) {
|
||||||
if (event.origin !== base) return;
|
if (event.origin !== base) return;
|
||||||
if (event.data && event.data.type === 'cml-auth-status' && event.data.authenticated) {
|
if (event.data && event.data.type === 'cml-auth-status' && event.data.authenticated) {
|
||||||
|
|||||||
@ -24,8 +24,6 @@
|
|||||||
<a href="#" data-path="/shifts" class="cm-header-nav__dropdown-item" data-nav-id="shifts"><span class="material-icons-outlined">schedule</span><span>Shifts</span></a>
|
<a href="#" data-path="/shifts" class="cm-header-nav__dropdown-item" data-nav-id="shifts"><span class="material-icons-outlined">schedule</span><span>Shifts</span></a>
|
||||||
<a href="#" data-path="/events" class="cm-header-nav__dropdown-item" data-nav-id="events"><span class="material-icons-outlined">event</span><span>Calendar</span></a>
|
<a href="#" data-path="/events" class="cm-header-nav__dropdown-item" data-nav-id="events"><span class="material-icons-outlined">event</span><span>Calendar</span></a>
|
||||||
<a href="#" data-path="/polls" class="cm-header-nav__dropdown-item" data-nav-id="polls"><span class="material-icons-outlined">bar_chart</span><span>Polls</span></a>
|
<a href="#" data-path="/polls" class="cm-header-nav__dropdown-item" data-nav-id="polls"><span class="material-icons-outlined">bar_chart</span><span>Polls</span></a>
|
||||||
<a href="#" data-path="/events/tickets" class="cm-header-nav__dropdown-item" data-nav-id="tickets"><span class="material-icons-outlined">sell</span><span>Tickets</span></a>
|
|
||||||
<a href="#" data-path="/meet" class="cm-header-nav__dropdown-item" data-nav-id="meet"><span class="material-icons-outlined">videocam</span><span>Meet</span></a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a href="#" data-path="/gallery" class="cm-header-nav__link" data-nav-id="gallery"><span class="material-icons-outlined">play_circle</span><span class="cm-header-nav__label">Gallery</span></a>
|
<a href="#" data-path="/gallery" class="cm-header-nav__link" data-nav-id="gallery"><span class="material-icons-outlined">play_circle</span><span class="cm-header-nav__label">Gallery</span></a>
|
||||||
@ -89,8 +87,6 @@
|
|||||||
<a href="#" data-path="/shifts" class="cm-header-nav__mobile-link" data-nav-id="shifts" style="padding-left:48px"><span class="material-icons-outlined">schedule</span><span>Shifts</span></a>
|
<a href="#" data-path="/shifts" class="cm-header-nav__mobile-link" data-nav-id="shifts" style="padding-left:48px"><span class="material-icons-outlined">schedule</span><span>Shifts</span></a>
|
||||||
<a href="#" data-path="/events" class="cm-header-nav__mobile-link" data-nav-id="events" style="padding-left:48px"><span class="material-icons-outlined">event</span><span>Calendar</span></a>
|
<a href="#" data-path="/events" class="cm-header-nav__mobile-link" data-nav-id="events" style="padding-left:48px"><span class="material-icons-outlined">event</span><span>Calendar</span></a>
|
||||||
<a href="#" data-path="/polls" class="cm-header-nav__mobile-link" data-nav-id="polls" style="padding-left:48px"><span class="material-icons-outlined">bar_chart</span><span>Polls</span></a>
|
<a href="#" data-path="/polls" class="cm-header-nav__mobile-link" data-nav-id="polls" style="padding-left:48px"><span class="material-icons-outlined">bar_chart</span><span>Polls</span></a>
|
||||||
<a href="#" data-path="/events/tickets" class="cm-header-nav__mobile-link" data-nav-id="tickets" style="padding-left:48px"><span class="material-icons-outlined">sell</span><span>Tickets</span></a>
|
|
||||||
<a href="#" data-path="/meet" class="cm-header-nav__mobile-link" data-nav-id="meet" style="padding-left:48px"><span class="material-icons-outlined">videocam</span><span>Meet</span></a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a href="#" data-path="/gallery" class="cm-header-nav__mobile-link" data-nav-id="gallery"><span class="material-icons-outlined">play_circle</span><span>Gallery</span></a>
|
<a href="#" data-path="/gallery" class="cm-header-nav__mobile-link" data-nav-id="gallery"><span class="material-icons-outlined">play_circle</span><span>Gallery</span></a>
|
||||||
@ -205,7 +201,7 @@
|
|||||||
// 2. Cross-origin check via hidden iframe + postMessage
|
// 2. Cross-origin check via hidden iframe + postMessage
|
||||||
var iframe = document.createElement('iframe');
|
var iframe = document.createElement('iframe');
|
||||||
iframe.style.display = 'none';
|
iframe.style.display = 'none';
|
||||||
iframe.src = base + '/auth-check.html';
|
iframe.src = base + '/auth-check.html?origin=' + encodeURIComponent(location.origin);
|
||||||
window.addEventListener('message', function(event) {
|
window.addEventListener('message', function(event) {
|
||||||
if (event.origin !== base) return;
|
if (event.origin !== base) return;
|
||||||
if (event.data && event.data.type === 'cml-auth-status' && event.data.authenticated) {
|
if (event.data && event.data.type === 'cml-auth-status' && event.data.authenticated) {
|
||||||
|
|||||||
@ -7,7 +7,7 @@ server {
|
|||||||
|
|
||||||
# Auth check iframe — allows cross-origin login state detection (MkDocs header)
|
# Auth check iframe — allows cross-origin login state detection (MkDocs header)
|
||||||
location = /auth-check.html {
|
location = /auth-check.html {
|
||||||
add_header Content-Security-Policy "frame-ancestors *" always;
|
add_header Content-Security-Policy "frame-ancestors 'self' http://localhost:* http://127.0.0.1:*" always;
|
||||||
set $upstream_admin_authcheck http://changemaker-v2-admin:3000;
|
set $upstream_admin_authcheck http://changemaker-v2-admin:3000;
|
||||||
proxy_pass $upstream_admin_authcheck;
|
proxy_pass $upstream_admin_authcheck;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
|
|||||||
@ -372,7 +372,6 @@ server {
|
|||||||
# Auth check iframe — allows root domain to embed this tiny page
|
# Auth check iframe — allows root domain to embed this tiny page
|
||||||
# for cross-origin login state detection (MkDocs header)
|
# for cross-origin login state detection (MkDocs header)
|
||||||
location = /auth-check.html {
|
location = /auth-check.html {
|
||||||
add_header X-Frame-Options "ALLOW-FROM https://${DOMAIN}" always;
|
|
||||||
add_header Content-Security-Policy "frame-ancestors 'self' https://${DOMAIN} http://${DOMAIN}" always;
|
add_header Content-Security-Policy "frame-ancestors 'self' https://${DOMAIN} http://${DOMAIN}" always;
|
||||||
set $upstream_admin_authcheck http://changemaker-v2-admin:3000;
|
set $upstream_admin_authcheck http://changemaker-v2-admin:3000;
|
||||||
proxy_pass $upstream_admin_authcheck;
|
proxy_pass $upstream_admin_authcheck;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user