Files
Aether/backend/pkg/middleware/security.go
T
marcandClaude Opus 5 9b8e82cdb2 feat(honeybar): HoneyOS-Leiste einbinden, CSP-Ausnahme dafür
Die Leiste kommt als Skript von der HoneyOS-Domain und holt sich von dort
ihre App-Liste. Beides blockte die CSP bisher (script-src / connect-src
fielen auf 'self' zurueck) – ohne Ausnahme erschien sie gar nicht.

- HONEYBAR_ORIGIN nimmt genau diesen einen Ursprung in script-src und
  connect-src auf. Leer heisst: keine Leiste, und die Regel bleibt so eng
  wie bisher. Frame-Freigabe erbt sie ausdruecklich nicht.
- SecurityHeaders nimmt die Ausnahmen jetzt als CSP-Struct statt als
  variadische frameSrc-Liste – mit drei Richtungen wurde das sonst unklar.
- Das <script>-Tag steht in der index.html und nicht serverseitig
  eingespritzt: der Service Worker legt die index.html in seinen Vorrat
  (navigateFallback), was zur Bauzeit fehlt, kaeme im Browser nie an.
- defer, damit ein langsames oder abgeschaltetes HoneyOS den Start von
  Aether nicht ausbremst.

Tests fuer die neue Regel: eng ohne Ausnahme, beide Richtungen mit,
Pfad wird auf den Ursprung gekuerzt, Adresse ohne Schema faellt raus.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 21:03:01 +02:00

82 lines
2.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package middleware
import (
"net/http"
"net/url"
"strings"
)
// CSP nennt die Ursprünge, die zusätzlich zu 'self' erlaubt sind.
//
// Jedes Feld ist eine bewusst enge Ausnahme mit einem konkreten Anlass. Leer
// heißt: keine Ausnahme — dann gilt für diese Richtung allein 'self'.
type CSP struct {
// FrameSrc: Seiten, die in einem <iframe> erscheinen dürfen.
// Gebraucht für den Office-Server; `default-src 'self'` deckt auch
// `frame-src` ab, ohne Ausnahme blockt der Browser ihn also, noch bevor
// eine Anfrage hinausgeht. Gemessen als „Framing 'http://…:9980/'
// violates … default-src 'self'".
FrameSrc []string
// ScriptSrc: fremde Ursprünge, von denen Skripte geladen werden dürfen.
// ConnectSrc: fremde Ursprünge, die per fetch/XHR erreichbar sein sollen.
// Beide gebraucht für eine eingebettete Leiste, die von woanders kommt und
// sich ihre Daten selbst holt — das Skript über das eine, die Anfrage über
// das andere. Nur eines von beiden zu setzen ergibt selten Sinn.
ScriptSrc []string
ConnectSrc []string
}
// SecurityHeaders adds security headers that the app controls.
// HSTS is intentionally omitted — it belongs on the edge reverse proxy.
//
// Ausnahmen werden **einzeln benannt** und nie als Platzhalter aufgenommen:
// Sie sollen so schmal sein wie ihr Anlass. Ein Eintrag, der sich nicht als
// Adresse lesen lässt, wird verworfen statt durchgereicht.
func SecurityHeaders(csp CSP) func(http.Handler) http.Handler {
directive := func(name string, extra []string) string {
out := name + " 'self'"
for _, raw := range extra {
if origin := originOf(raw); origin != "" {
out += " " + origin
}
}
return out
}
policy := strings.Join([]string{
"default-src 'self'",
directive("script-src", csp.ScriptSrc),
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: blob:",
directive("connect-src", csp.ConnectSrc),
directive("frame-src", csp.FrameSrc),
}, "; ")
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
h := w.Header()
h.Set("X-Content-Type-Options", "nosniff")
h.Set("X-Frame-Options", "DENY")
h.Set("Referrer-Policy", "strict-origin-when-cross-origin")
h.Set("Content-Security-Policy", policy)
next.ServeHTTP(w, r)
})
}
}
// originOf reduziert eine Adresse auf Schema und Host. Ein Pfad in einer
// CSP-Quelle wäre nicht falsch, aber enger als gemeint – und ein Tippfehler
// darin fiele erst auf, wenn der Rahmen leer bleibt.
func originOf(raw string) string {
raw = strings.TrimSpace(raw)
if raw == "" {
return ""
}
u, err := url.Parse(raw)
if err != nil || u.Scheme == "" || u.Host == "" {
return ""
}
return u.Scheme + "://" + u.Host
}