WIP! A BB-style forum, on the ATmosphere! We're still working... we'll be back soon when we have something to show off!
node typescript hono htmx atproto
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix(web): sanitize cssOverrides before injection, add null branch tests (ATB-53)

Malpercio 36a4f2ff e227c7a1

+32 -1
+27
apps/web/src/layouts/__tests__/base.test.tsx
··· 95 95 expect(html).toContain(".card { border: 2px solid black; }"); 96 96 }); 97 97 98 + it("does not render Google Fonts preconnect tags when fontUrls is null", async () => { 99 + const themeNoFonts = { ...FALLBACK_THEME, fontUrls: null }; 100 + const noFontsApp = new Hono().get("/", (c) => 101 + c.html( 102 + <BaseLayout resolvedTheme={themeNoFonts}>content</BaseLayout> 103 + ) 104 + ); 105 + const res = await noFontsApp.request("/"); 106 + const html = await res.text(); 107 + expect(html).not.toContain("fonts.googleapis.com"); 108 + }); 109 + 110 + it("does not render cssOverrides style tag when cssOverrides is null", async () => { 111 + const themeNoOverrides = { ...FALLBACK_THEME, cssOverrides: null }; 112 + const noOverridesApp = new Hono().get("/", (c) => 113 + c.html( 114 + <BaseLayout resolvedTheme={themeNoOverrides}>content</BaseLayout> 115 + ) 116 + ); 117 + const res = await noOverridesApp.request("/"); 118 + const html = await res.text(); 119 + // The only <style> tag should be the :root block — no second style tag for overrides 120 + const styleTagMatches = html.match(/<style/g); 121 + expect(styleTagMatches).toHaveLength(1); 122 + expect(html).toContain(":root {"); 123 + }); 124 + 98 125 describe("auth-aware navigation", () => { 99 126 it("shows Log in link when auth is not provided (default unauthenticated)", async () => { 100 127 const unauthApp = new Hono().get("/", (c) =>
+5 -1
apps/web/src/layouts/base.tsx
··· 3 3 import type { ResolvedTheme } from "../lib/theme-resolution.js"; 4 4 import type { WebSession } from "../lib/session.js"; 5 5 6 + function sanitizeCss(css: string): string { 7 + return css.replace(/<\/style/gi, ""); 8 + } 9 + 6 10 const NavContent: FC<{ auth?: WebSession }> = ({ auth }) => ( 7 11 <> 8 12 {auth?.authenticated ? ( ··· 44 48 /> 45 49 {resolvedTheme.cssOverrides && ( 46 50 <style 47 - dangerouslySetInnerHTML={{ __html: resolvedTheme.cssOverrides }} 51 + dangerouslySetInnerHTML={{ __html: sanitizeCss(resolvedTheme.cssOverrides) }} 48 52 /> 49 53 )} 50 54 {resolvedTheme.fontUrls && resolvedTheme.fontUrls.length > 0 && (