Fix widget styles being silently dropped under strict style-src CSP

element.ts re-injected the widget's CSS as a fresh inline <style> tag
on every render() call. A host with a strict style-src (e.g.
`style-src 'self'`, no 'unsafe-inline') silently drops an inline
<style>'s rules entirely -- every element inside the shadow root fell
back to unstyled browser defaults. The visible symptom: the brand SVG
rendered at its raw intrinsic ~590x119px instead of the intended
20px-tall lockup, since its `display: block; height: 20px` rule never
took effect.

Build the stylesheet once as a CSSStyleSheet and assign it via
adoptedStyleSheets instead -- not subject to style-src the way a
parsed inline <style> element is, since it's never parsed as untrusted
markup by the HTML parser. render() no longer touches styling at all.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01DY9UMTuF43Ekc2U4E1eDwX
This commit is contained in:
thePR0M3TH3ANandClaude Sonnet 5 committed 2026-07-21 22:09:30 -04:00
1 parent 863f035990
commit e8712f9aa9
1 file changed
+11 -1
+11 -1
View File
@@ -101,6 +101,16 @@ export class BitLoginAuthElement extends HTMLElement {
constructor() {
super();
this.root = this.attachShadow({ mode: "open" });
// A parsed CSSStyleSheet assigned via adoptedStyleSheets isn't subject to a page's
// style-src CSP the way an inline <style> element (or repeatedly re-injecting one on
// every render()) is -- a host with a strict `style-src 'self'` (no 'unsafe-inline')
// silently drops an inline <style>'s rules entirely, which left every shadow-DOM
// element unstyled (the brand SVG rendering at its raw intrinsic ~590x119 size instead
// of the intended 20px-tall lockup was the visible symptom). Built once here rather
// than in render(), since the stylesheet text itself never changes between renders.
const sheet = new CSSStyleSheet();
sheet.replaceSync(WIDGET_STYLES);
this.root.adoptedStyleSheets = [sheet];
this.worker = new WorkerClient();
}
@@ -802,7 +812,7 @@ export class BitLoginAuthElement extends HTMLElement {
private render(): void {
const successOverlay = this.pendingSuccessLabel ? this.renderSuccessOverlay(this.pendingSuccessLabel) : "";
this.root.innerHTML = `<style>${WIDGET_STYLES}</style><div class="card">${this.renderScreen()}${successOverlay}</div>`;
this.root.innerHTML = `<div class="card">${this.renderScreen()}${successOverlay}</div>`;
}
private renderSuccessOverlay(label: string): string {