Die HoneyBar braucht kein 'unsafe-inline' mehr
Ihre Stile hingen als <style> im Shadow DOM, und Markup faellt unter die CSP der Gastgeber-App: Jede fremde App musste deshalb style-src 'unsafe-inline' erlauben, sonst stand die HoneyBar schwarz auf schwarz da. Jetzt kommen sie als konstruiertes Stylesheet ueber die CSSOM herein, die keine CSP kennt. Damit braucht die App gar keine style-src-Erlaubnis mehr -- auch keine fuer die HoneyOS-Domain, was der geplante Weg ueber eine zweite Datei und <link> gekostet haette, samt zweiter Anfrage. Der Strich zum Wiederausklappen hat seinen eigenen Shadow-Root und geht denselben Weg. Der Rueckfall fuer alte Browser laeuft nach innerHTML, sonst raeumt das Markup ihn gleich wieder weg. Gemessen an einer nachgebauten App mit style-src 'self': Leiste bernstein, 32 px, Strich mit Verlauf, keine CSP-Meldung. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8ca16aec84
commit
3b47003492
4 files changed
+97
-33
No files matched your search
@@ -288,21 +288,26 @@ Zwei Auswege:
|
|||||||
→ Ersteres ist das Richtige, sobald die HoneyBar mehr kann als heute (Widgets,
|
→ Ersteres ist das Richtige, sobald die HoneyBar mehr kann als heute (Widgets,
|
||||||
Systemmenü). Vorher lohnt der Umbau nicht.
|
Systemmenü). Vorher lohnt der Umbau nicht.
|
||||||
|
|
||||||
### Die HoneyBar ohne `unsafe-inline`
|
### Die HoneyBar ohne `unsafe-inline` — ✅ gebaut (11.09.2026)
|
||||||
|
|
||||||
Fremde Apps bekommen die HoneyBar über den Reverse Proxy (siehe README). Dabei
|
Fremde Apps mussten für die HoneyBar `style-src 'unsafe-inline'` erlauben, weil
|
||||||
muss deren CSP heute `style-src 'unsafe-inline'` erlauben, weil die HoneyBar ihre
|
sie ihre Stile im Shadow DOM als `<style>` mitbrachte — ohne das erschien sie
|
||||||
Stile im Shadow DOM mitbringt — ohne das erscheint sie schwarz auf schwarz.
|
schwarz auf schwarz (gemessen am 28.08.2026).
|
||||||
Gemessen am 28.08.2026 an einer nachgebauten fremden App.
|
|
||||||
|
|
||||||
**Auflösbar:** Die Stile als eigene Datei neben `honeybar.js` ausliefern und im
|
**Anders gelöst als hier geplant.** Der Plan war, die Stile als eigene Datei
|
||||||
Shadow DOM per `<link>` einbinden. Dann genügt `style-src 'self' https://honeyos…`,
|
auszuliefern und per `<link>` einzubinden. Das hätte `'unsafe-inline'` gegen
|
||||||
und die CSP der fremden App bleibt streng. Kostet eine zweite Anfrage, die
|
`style-src … https://honeyos…` getauscht — eine CSP-Änderung gegen eine andere,
|
||||||
zwischengespeichert wird.
|
plus eine zweite Anfrage. Stattdessen hängt die HoneyBar ihre Stile jetzt als
|
||||||
|
konstruiertes Stylesheet (`adoptedStyleSheets`) ein: Die CSP greift nur auf
|
||||||
|
Markup, nicht auf die CSSOM. Damit braucht die Gastgeber-App **gar keine
|
||||||
|
`style-src`-Erlaubnis** mehr, und es bleibt bei einer Datei ohne zweite Anfrage.
|
||||||
|
|
||||||
Zu prüfen ist dabei, was sonst noch inline gesetzt wird: die Lage der HoneyBar
|
Der Strich zum Wiederausklappen hat seinen eigenen Shadow-Root und wäre sonst
|
||||||
selbst und der Innenabstand, den sie der Seite verpasst. Über die CSSOM gesetzte
|
das einzige Stück ohne Farbe geblieben — er geht denselben Weg.
|
||||||
Stile (`el.style.x = …`) fallen nicht unter die CSP — was im Markup steht, schon.
|
|
||||||
|
Zwei Fallen dabei: Der Rückfall für alte Browser hängt ein `<style>` in den
|
||||||
|
Root, also muss er **nach** `innerHTML` laufen, sonst räumt das Markup ihn
|
||||||
|
gleich wieder weg. Und `el.style.…` war nie das Problem — das ist schon CSSOM.
|
||||||
|
|
||||||
### Bild-Widget — ✅ gebaut (30.08.2026)
|
### Bild-Widget — ✅ gebaut (30.08.2026)
|
||||||
|
|
||||||
|
|||||||
@@ -448,7 +448,7 @@ location / {
|
|||||||
# 2. Die CSP der App ersetzen, nicht ergänzen. Zwei CSP-Kopfzeilen bedeuten
|
# 2. Die CSP der App ersetzen, nicht ergänzen. Zwei CSP-Kopfzeilen bedeuten
|
||||||
# nicht „mehr erlaubt", sondern „beide müssen zustimmen".
|
# nicht „mehr erlaubt", sondern „beide müssen zustimmen".
|
||||||
proxy_hide_header Content-Security-Policy;
|
proxy_hide_header Content-Security-Policy;
|
||||||
add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://honeyos.example.com; connect-src 'self' https://honeyos.example.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:" always;
|
add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://honeyos.example.com; connect-src 'self' https://honeyos.example.com; style-src 'self'; img-src 'self' data:" always;
|
||||||
|
|
||||||
# 3. Nur HTML anfassen; JSON, Bilder und Downloads bleiben unberührt.
|
# 3. Nur HTML anfassen; JSON, Bilder und Downloads bleiben unberührt.
|
||||||
sub_filter_types text/html;
|
sub_filter_types text/html;
|
||||||
@@ -463,12 +463,18 @@ Liste ist zugleich die CORS-Freigabe. **Auf den Ursprung genau**: `localhost` un
|
|||||||
oder ein abweichender Port. Passt er nicht, erscheint die HoneyBar mit leerer
|
oder ein abweichender Port. Passt er nicht, erscheint die HoneyBar mit leerer
|
||||||
App-Liste und in der Konsole steht ein CORS-Fehler.
|
App-Liste und in der Konsole steht ein CORS-Fehler.
|
||||||
|
|
||||||
**Warum `style-src 'unsafe-inline'`:** Die HoneyBar bringt ihre Stile mit, im
|
**Kein `style-src 'unsafe-inline'` nötig** (seit 11.09.2026). Die HoneyBar
|
||||||
Shadow DOM. Eine strenge CSP verbietet die — gemessen: die HoneyBar erscheint,
|
bringt ihre Stile zwar mit, hängt sie aber als konstruiertes Stylesheet
|
||||||
aber schwarz auf schwarz, ohne Röhre. Das ist eine echte Abschwächung der
|
(`adoptedStyleSheets`) in ihren Shadow DOM statt als `<style>`-Element. Nur
|
||||||
CSP dieser App; viele fertige Werkzeuge brauchen `unsafe-inline` ohnehin für
|
Markup fällt unter die CSP, über die CSSOM gesetzte Stile nicht — die App
|
||||||
sich selbst, aber wissen sollte man es. (Auflösbar, indem die HoneyBar ihre Stile
|
braucht für die HoneyBar also weder `'unsafe-inline'` noch eine Freigabe für
|
||||||
als eigene Datei ausliefert — steht in [IDEAS.md](IDEAS.md).)
|
die HoneyOS-Domain. Vorher stand die HoneyBar unter einer strengen CSP schwarz
|
||||||
|
auf schwarz da. Gemessen an einer nachgebauten App mit `style-src 'self'`:
|
||||||
|
Leiste bernstein, 32 px, keine CSP-Meldung.
|
||||||
|
|
||||||
|
Browser ohne konstruierbare Stylesheets (vor Safari 16.4, Firefox 101) fallen
|
||||||
|
auf das `<style>`-Element zurück — dort gilt der alte Stand, also `unsafe-inline`
|
||||||
|
oder eine Leiste ohne Farbe.
|
||||||
|
|
||||||
**Apps mit COEP** (alles mit WASM oder SharedArrayBuffer — BentoPDF etwa)
|
**Apps mit COEP** (alles mit WASM oder SharedArrayBuffer — BentoPDF etwa)
|
||||||
lehnen fremde Dateien ab, die sich nicht zur Einbettung bekennen. HoneyOS
|
lehnen fremde Dateien ab, die sich nicht zur Einbettung bekennen. HoneyOS
|
||||||
|
|||||||
+46
-14
@@ -105,10 +105,37 @@
|
|||||||
*/
|
*/
|
||||||
const WAPPEN = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 12" shape-rendering="crispEdges" aria-hidden="true"><rect x="4" y="0" width="2" height="1" fill="#f5f2e7"/><rect x="9" y="0" width="2" height="1" fill="#f5f2e7"/><rect x="2" y="1" width="5" height="1" fill="#f5f2e7"/><rect x="8" y="1" width="5" height="1" fill="#f5f2e7"/><rect x="2" y="2" width="11" height="1" fill="#f5f2e7"/><rect x="14" y="2" width="1" height="1" fill="#3b2412"/><rect x="3" y="3" width="9" height="1" fill="#f5f2e7"/><rect x="13" y="3" width="2" height="1" fill="#3b2412"/><rect x="3" y="4" width="11" height="1" fill="#ffd060"/><rect x="2" y="5" width="2" height="1" fill="#f2b33d"/><rect x="4" y="5" width="2" height="1" fill="#5c3a1c"/><rect x="6" y="5" width="2" height="1" fill="#f2b33d"/><rect x="8" y="5" width="2" height="1" fill="#5c3a1c"/><rect x="10" y="5" width="5" height="1" fill="#f2b33d"/><rect x="1" y="6" width="3" height="1" fill="#f2b33d"/><rect x="4" y="6" width="2" height="1" fill="#5c3a1c"/><rect x="6" y="6" width="2" height="1" fill="#f2b33d"/><rect x="8" y="6" width="2" height="1" fill="#5c3a1c"/><rect x="10" y="6" width="2" height="1" fill="#f2b33d"/><rect x="12" y="6" width="1" height="1" fill="#181410"/><rect x="13" y="6" width="1" height="1" fill="#63c7c0"/><rect x="14" y="6" width="1" height="1" fill="#f2b33d"/><rect x="0" y="7" width="1" height="1" fill="#5c3a1c"/><rect x="1" y="7" width="3" height="1" fill="#f2b33d"/><rect x="4" y="7" width="2" height="1" fill="#5c3a1c"/><rect x="6" y="7" width="2" height="1" fill="#f2b33d"/><rect x="8" y="7" width="2" height="1" fill="#5c3a1c"/><rect x="10" y="7" width="2" height="1" fill="#f2b33d"/><rect x="12" y="7" width="2" height="1" fill="#181410"/><rect x="14" y="7" width="1" height="1" fill="#f2b33d"/><rect x="1" y="8" width="3" height="1" fill="#f2b33d"/><rect x="4" y="8" width="2" height="1" fill="#5c3a1c"/><rect x="6" y="8" width="2" height="1" fill="#f2b33d"/><rect x="8" y="8" width="2" height="1" fill="#5c3a1c"/><rect x="10" y="8" width="5" height="1" fill="#f2b33d"/><rect x="2" y="9" width="1" height="1" fill="#d2952a"/><rect x="3" y="9" width="2" height="1" fill="#462b14"/><rect x="5" y="9" width="2" height="1" fill="#d2952a"/><rect x="7" y="9" width="2" height="1" fill="#462b14"/><rect x="9" y="9" width="6" height="1" fill="#d2952a"/><rect x="3" y="10" width="2" height="1" fill="#181410"/><rect x="7" y="10" width="2" height="1" fill="#181410"/><rect x="11" y="10" width="2" height="1" fill="#181410"/><rect x="3" y="11" width="2" height="1" fill="#181410"/><rect x="7" y="11" width="2" height="1" fill="#181410"/><rect x="11" y="11" width="2" height="1" fill="#181410"/></svg>';
|
const WAPPEN = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 15 12" shape-rendering="crispEdges" aria-hidden="true"><rect x="4" y="0" width="2" height="1" fill="#f5f2e7"/><rect x="9" y="0" width="2" height="1" fill="#f5f2e7"/><rect x="2" y="1" width="5" height="1" fill="#f5f2e7"/><rect x="8" y="1" width="5" height="1" fill="#f5f2e7"/><rect x="2" y="2" width="11" height="1" fill="#f5f2e7"/><rect x="14" y="2" width="1" height="1" fill="#3b2412"/><rect x="3" y="3" width="9" height="1" fill="#f5f2e7"/><rect x="13" y="3" width="2" height="1" fill="#3b2412"/><rect x="3" y="4" width="11" height="1" fill="#ffd060"/><rect x="2" y="5" width="2" height="1" fill="#f2b33d"/><rect x="4" y="5" width="2" height="1" fill="#5c3a1c"/><rect x="6" y="5" width="2" height="1" fill="#f2b33d"/><rect x="8" y="5" width="2" height="1" fill="#5c3a1c"/><rect x="10" y="5" width="5" height="1" fill="#f2b33d"/><rect x="1" y="6" width="3" height="1" fill="#f2b33d"/><rect x="4" y="6" width="2" height="1" fill="#5c3a1c"/><rect x="6" y="6" width="2" height="1" fill="#f2b33d"/><rect x="8" y="6" width="2" height="1" fill="#5c3a1c"/><rect x="10" y="6" width="2" height="1" fill="#f2b33d"/><rect x="12" y="6" width="1" height="1" fill="#181410"/><rect x="13" y="6" width="1" height="1" fill="#63c7c0"/><rect x="14" y="6" width="1" height="1" fill="#f2b33d"/><rect x="0" y="7" width="1" height="1" fill="#5c3a1c"/><rect x="1" y="7" width="3" height="1" fill="#f2b33d"/><rect x="4" y="7" width="2" height="1" fill="#5c3a1c"/><rect x="6" y="7" width="2" height="1" fill="#f2b33d"/><rect x="8" y="7" width="2" height="1" fill="#5c3a1c"/><rect x="10" y="7" width="2" height="1" fill="#f2b33d"/><rect x="12" y="7" width="2" height="1" fill="#181410"/><rect x="14" y="7" width="1" height="1" fill="#f2b33d"/><rect x="1" y="8" width="3" height="1" fill="#f2b33d"/><rect x="4" y="8" width="2" height="1" fill="#5c3a1c"/><rect x="6" y="8" width="2" height="1" fill="#f2b33d"/><rect x="8" y="8" width="2" height="1" fill="#5c3a1c"/><rect x="10" y="8" width="5" height="1" fill="#f2b33d"/><rect x="2" y="9" width="1" height="1" fill="#d2952a"/><rect x="3" y="9" width="2" height="1" fill="#462b14"/><rect x="5" y="9" width="2" height="1" fill="#d2952a"/><rect x="7" y="9" width="2" height="1" fill="#462b14"/><rect x="9" y="9" width="6" height="1" fill="#d2952a"/><rect x="3" y="10" width="2" height="1" fill="#181410"/><rect x="7" y="10" width="2" height="1" fill="#181410"/><rect x="11" y="10" width="2" height="1" fill="#181410"/><rect x="3" y="11" width="2" height="1" fill="#181410"/><rect x="7" y="11" width="2" height="1" fill="#181410"/><rect x="11" y="11" width="2" height="1" fill="#181410"/></svg>';
|
||||||
|
|
||||||
|
/* ── Stile ohne CSP-Ausnahme ──────────────────────────────────────────────
|
||||||
|
* Die Stile kommen als konstruiertes Stylesheet in den Shadow-Root, nicht
|
||||||
|
* als style-Element. Der Unterschied ist nicht Geschmack, sondern der
|
||||||
|
* ganze Punkt: Ein style-Element ist Markup und faellt damit unter die CSP der
|
||||||
|
* fremden App — die musste deshalb bis hierher style-src 'unsafe-inline'
|
||||||
|
* erlauben, sonst stand die HoneyBar schwarz auf schwarz da. Ueber die
|
||||||
|
* CSSOM gesetzte Stile fallen nicht darunter. So braucht die Gastgeber-App
|
||||||
|
* fuer die HoneyBar gar keine style-src-Erlaubnis mehr, und es bleibt
|
||||||
|
* trotzdem bei einer Datei ohne zweite Anfrage.
|
||||||
|
*
|
||||||
|
* Der Rueckfall ist fuer Browser ohne konstruierbare Stylesheets (vor
|
||||||
|
* Safari 16.4, Firefox 101): Dort steht es wie bisher, also nicht
|
||||||
|
* schlechter — nur eben weiter mit 'unsafe-inline'.
|
||||||
|
*/
|
||||||
|
function stileAnlegen(wurzel, css) {
|
||||||
|
try {
|
||||||
|
const blatt = new CSSStyleSheet();
|
||||||
|
blatt.replaceSync(css);
|
||||||
|
wurzel.adoptedStyleSheets = [blatt];
|
||||||
|
return;
|
||||||
|
} catch {
|
||||||
|
/* alter Browser: unten der Weg ueber das Markup */
|
||||||
|
}
|
||||||
|
const el = document.createElement("style");
|
||||||
|
el.textContent = css;
|
||||||
|
wurzel.appendChild(el);
|
||||||
|
}
|
||||||
|
|
||||||
const root = host.attachShadow({ mode: "open" });
|
const root = host.attachShadow({ mode: "open" });
|
||||||
|
|
||||||
root.innerHTML = `
|
const STIL = `
|
||||||
<style>
|
|
||||||
:host, * { box-sizing: border-box; }
|
:host, * { box-sizing: border-box; }
|
||||||
/* Dieselben Röhren wie auf dem Schreibtisch. Welche gilt, sagt der
|
/* Dieselben Röhren wie auf dem Schreibtisch. Welche gilt, sagt der
|
||||||
Server; bis seine Antwort da ist, leuchtet Bernstein. */
|
Server; bis seine Antwort da ist, leuchtet Bernstein. */
|
||||||
@@ -253,8 +280,9 @@
|
|||||||
Logo links, das App-Menue am Pfeil rechts. */
|
Logo links, das App-Menue am Pfeil rechts. */
|
||||||
.menu.sys { left: 6px; right: auto; }
|
.menu.sys { left: 6px; right: auto; }
|
||||||
@media (max-width: 560px) { .clock { display: none; } }
|
@media (max-width: 560px) { .clock { display: none; } }
|
||||||
</style>
|
`;
|
||||||
|
|
||||||
|
root.innerHTML = `
|
||||||
<div class="bar">
|
<div class="bar">
|
||||||
<button class="home" id="home" title="HoneyOS-Menü" aria-haspopup="true" aria-expanded="false"><span class="hex">${WAPPEN}</span> HONEY<b>OS</b></button>
|
<button class="home" id="home" title="HoneyOS-Menü" aria-haspopup="true" aria-expanded="false"><span class="hex">${WAPPEN}</span> HONEY<b>OS</b></button>
|
||||||
<nav class="apps" id="apps"></nav>
|
<nav class="apps" id="apps"></nav>
|
||||||
@@ -277,6 +305,8 @@
|
|||||||
<div class="drawer" id="menuApps"></div>
|
<div class="drawer" id="menuApps"></div>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
|
stileAnlegen(root, STIL);
|
||||||
|
|
||||||
/* ── Aussehen ─────────────────────────────────────────────────────────────
|
/* ── Aussehen ─────────────────────────────────────────────────────────────
|
||||||
* Dieselben drei Röhren wie in style.css. Sie hier zu wiederholen ist die
|
* Dieselben drei Röhren wie in style.css. Sie hier zu wiederholen ist die
|
||||||
* bewusste Entscheidung: die HoneyBar soll ohne zweite Datei auskommen, damit
|
* bewusste Entscheidung: die HoneyBar soll ohne zweite Datei auskommen, damit
|
||||||
@@ -520,17 +550,19 @@
|
|||||||
// auch wenn in HoneyOS Grün eingestellt war. Er gehört zur HoneyBar, also
|
// auch wenn in HoneyOS Grün eingestellt war. Er gehört zur HoneyBar, also
|
||||||
// trägt er dieselbe Röhre.
|
// trägt er dieselbe Röhre.
|
||||||
const t = ROEHREN[(settings && settings.phosphor) || "amber"] || ROEHREN.amber;
|
const t = ROEHREN[(settings && settings.phosphor) || "amber"] || ROEHREN.amber;
|
||||||
r.innerHTML = `
|
// Auch hier ueber die CSSOM statt als style-Element: Der Strich hat seinen
|
||||||
<style>
|
// eigenen Shadow-Root und waere sonst das einzige Stueck geblieben, das
|
||||||
.strich {
|
// unter einer strengen CSP unsichtbar bleibt.
|
||||||
height: 3px;
|
r.innerHTML = `<div class="strich"></div>`;
|
||||||
background: linear-gradient(90deg, ${t.dim}, ${t.p} 50%, ${t.dim});
|
stileAnlegen(r, `
|
||||||
opacity: .55;
|
.strich {
|
||||||
transition: opacity .15s, height .15s;
|
height: 3px;
|
||||||
}
|
background: linear-gradient(90deg, ${t.dim}, ${t.p} 50%, ${t.dim});
|
||||||
:host(:hover) .strich { opacity: 1; height: 3px; }
|
opacity: .55;
|
||||||
</style>
|
transition: opacity .15s, height .15s;
|
||||||
<div class="strich"></div>`;
|
}
|
||||||
|
:host(:hover) .strich { opacity: 1; height: 3px; }
|
||||||
|
`);
|
||||||
|
|
||||||
nub.addEventListener("click", () => {
|
nub.addEventListener("click", () => {
|
||||||
forget();
|
forget();
|
||||||
|
|||||||
@@ -204,3 +204,24 @@ func TestWappenIstUeberallDasselbe(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Die HoneyBar darf ihre Stile nicht als <style>-Element mitbringen: Markup
|
||||||
|
// faellt unter die CSP der fremden App, ueber die CSSOM gesetzte Stile nicht.
|
||||||
|
// Mit einem <style> muesste jede Gastgeber-App wieder 'unsafe-inline' erlauben,
|
||||||
|
// und ohne das stuende die HoneyBar dort schwarz auf schwarz — genau so war es
|
||||||
|
// bis zum 11.09.2026. Der Rueckfall fuer alte Browser baut das Element per
|
||||||
|
// createElement, nicht im Markup; deshalb sucht der Test nach dem Tag.
|
||||||
|
func TestHoneyBarBringtKeinStyleElementMit(t *testing.T) {
|
||||||
|
b, err := os.ReadFile("web/honeybar/honeybar.js")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
src := string(b)
|
||||||
|
|
||||||
|
if strings.Contains(src, "<style>") || strings.Contains(src, "<style ") {
|
||||||
|
t.Error("honeybar.js enthaelt ein <style>-Element im Markup — das braucht 'unsafe-inline' in der fremden App")
|
||||||
|
}
|
||||||
|
if !strings.Contains(src, "adoptedStyleSheets") {
|
||||||
|
t.Error("honeybar.js setzt keine adoptedStyleSheets mehr — wie kommen die Stile in den Shadow DOM?")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user