From 823c72122c54901831b1c307481857493fe237b5 Mon Sep 17 00:00:00 2001 From: Mitchell-Johnson Date: Thu, 24 Sep 2026 16:42:51 -0700 Subject: [PATCH] A proxied KVM may be framed by the console that proxies it frame-ancestors 'none' forbids framing even from the same origin, so /kvm/ paths say 'self' instead. Without this the iframe was refused before it loaded. Co-Authored-By: Claude Opus 5 --- internal/api/server.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/internal/api/server.go b/internal/api/server.go index b2dbd66..26ef695 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -321,9 +321,16 @@ func sensorPath(p string) bool { func (s *Server) Handler() http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { h := w.Header() - // The console must never be framed. An embedded widget exists to be: it is a page - // that can only read sensors, and only with a read-only token in its URL. - if r.URL.Path != "/embed/widget" { + // The console must never be framed. Two exceptions: an embedded widget exists to be + // (a page that can only read sensors, and only with a read-only token in its URL), + // and a proxied KVM is framed by the console itself — 'none' forbids that even from + // the same origin, so those paths say 'self' instead of opening up. + switch { + case r.URL.Path == "/embed/widget": + case strings.HasPrefix(r.URL.Path, kvmPrefix): + h.Set("X-Frame-Options", "SAMEORIGIN") + h.Set("Content-Security-Policy", "frame-ancestors 'self'") + default: h.Set("X-Frame-Options", "DENY") h.Set("Content-Security-Policy", "frame-ancestors 'none'") }