commit 44d804ada316d06b43da1f78d0c8361cb879dc55
parent 63a88a5d059cee15354771f1c73b4aeed9f60d78
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Mon, 28 Sep 2026 19:52:58 -0400
common, export, homepage: each social key clips its icon's paint and a refused icon's label is bounded; e2e proves the parser invariant and that nothing is fetched (re-review R1, R2, R4, R7)
- R4: a key (the homepage's row, the export footer's links) is
overflow-hidden with contain: paint, so an icon paints in its box only; the
focus ring is the key's own shadow, outside that clip.
- R7: a refused icon's text fallback is capped at 10rem with an ellipsis, its
full label in title.
- svg-vectors.spec: every accepted vector and real shape, rendered as a page
renders it and parsed as a whole document by Chromium, leaves the element
after the link outside the icon, runs nothing and requests nothing.
- social.spec: the hostile stored icons now include the eight that loaded
from elsewhere; the page makes no request to another origin. The e2e's
gradient icon is the regression shape.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Diffstat:
5 files changed, 67 insertions(+), 13 deletions(-)
diff --git a/common/components/SocialLinks.tsx b/common/components/SocialLinks.tsx
@@ -34,10 +34,14 @@ export type SocialLinksPlacement = "header" | "footer";
// Renders nothing when there is nothing to show, so a caller can drop the
// column or the group around it on the same condition.
// Complete literal class strings (Tailwind v4 scans them as written).
+// A key clips what it holds (`overflow-hidden`, `contain: paint`): an icon
+// paints inside its 36 px box and nowhere else. The focus ring is the key's
+// own box-shadow, outside that clip. A refused icon's text fallback is capped
+// at 10rem and ends in an ellipsis, its full label in `title`.
const KEY =
- "inline-flex size-9 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring not-forced-colors:focus-visible:outline-none pointer-coarse:size-11 [&_svg]:size-5 [&_svg]:shrink-0";
+ "inline-flex size-9 shrink-0 items-center justify-center overflow-hidden [contain:paint] rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring not-forced-colors:focus-visible:outline-none pointer-coarse:size-11 [&_svg]:size-5 [&_svg]:shrink-0";
const TEXT_KEY =
- "inline-flex h-9 shrink-0 items-center rounded-md px-2 text-sm text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring not-forced-colors:focus-visible:outline-none pointer-coarse:h-11";
+ "inline-block h-9 max-w-40 shrink-0 truncate rounded-md px-2 text-sm leading-9 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring not-forced-colors:focus-visible:outline-none pointer-coarse:h-11 pointer-coarse:leading-[2.75rem]";
export function SocialLinks({
links,
@@ -84,6 +88,7 @@ export function SocialLinks({
target="_blank"
rel="noopener noreferrer"
data-social-icon-refused=""
+ title={link.label}
className={TEXT_KEY}
>
{link.label}
diff --git a/export/app/components/Footer.tsx b/export/app/components/Footer.tsx
@@ -107,7 +107,7 @@ export default function Footer() {
aria-label={link.label}
target="_blank"
rel="noopener noreferrer"
- className="inline-block w-5 h-5 text-muted-foreground hover:text-brand transition-colors [&_svg]:w-full [&_svg]:h-full"
+ className="inline-block w-5 h-5 overflow-hidden [contain:paint] text-muted-foreground hover:text-brand transition-colors [&_svg]:w-full [&_svg]:h-full"
dangerouslySetInnerHTML={{ __html: sizeSocialSvg(svg) }}
/>
) : (
@@ -115,7 +115,8 @@ export default function Footer() {
href={link.url}
target="_blank"
rel="noopener noreferrer"
- className="text-muted-foreground hover:text-brand transition-colors"
+ title={link.label}
+ className="inline-block max-w-40 truncate align-bottom text-muted-foreground hover:text-brand transition-colors"
>
{link.label}
</a>
diff --git a/homepage/e2e/fixture-social.ts b/homepage/e2e/fixture-social.ts
@@ -3,6 +3,7 @@ import {
normalizeSocialSvg,
type SocialLink,
} from "../../common/lib/settingsSchema";
+import { REAL_SHAPES } from "../../common/lib/socialSvg.vectors";
// THE HOMEPAGE E2E'S SOCIAL LINKS, never the operator's.
//
@@ -35,13 +36,10 @@ import {
export const FIXTURE_SETTINGS_NAME = ".e2e-settings.json";
-const LEAF =
- `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">` +
- `<defs><linearGradient id="leaf-grad" x1="0" y1="0" x2="0" y2="1">` +
- `<stop offset="0" stop-color="#E0A030"/><stop offset="1" stop-color="#1C9A5B"/>` +
- `</linearGradient></defs>` +
- `<path fill="url(#leaf-grad)" d="M12 2c4.9 3.6 8 7.6 8 11.3A8 8 0 0 1 4 13.3C4 9.6 7.1 5.6 12 2z"/>` +
- `<circle fill="#333333" cx="12" cy="15" r="2.4"/></svg>`;
+// The gradient icon is the checker's regression shape (socialSvg.vectors.ts
+// REAL_SHAPES): a drawing program's root, a gradient with `style` stops, a dark
+// outline under the body (`paint-order`), a negative-origin viewBox.
+const LEAF = REAL_SHAPES.gradient_outlined;
const BUBBLE =
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none">` +
diff --git a/homepage/e2e/social.spec.ts b/homepage/e2e/social.spec.ts
@@ -10,6 +10,7 @@ import {
writeRawFixtureSettings,
} from "./fixture-social";
import { themeToggle } from "./helpers";
+import { ADVERSARIAL, LOADS_ELSEWHERE } from "../../common/lib/socialSvg.vectors";
// THE SOCIAL ROW: in the header at every width (at most four links), and in the
// footer (every link). The links are the e2e's own (fixture-social.ts): Leaf (a
@@ -396,15 +397,21 @@ test("no links: no row in the header, and no Elsewhere column in the footer", as
// A settings.json edited by hand (never through a save) holding icons that
// would run script: none is inlined, none runs, and each link still renders —
// as its label — while the good icons beside them render as icons.
-test("a stored icon that would run script is not inlined: no script runs, and the link shows its label", async ({
+test("a stored icon that would run script or load from elsewhere is not inlined: nothing runs, nothing is fetched, the link shows its label", async ({
page,
}) => {
- const hostile = FIXTURE_HOSTILE_SVGS.map((svg, i) => ({
+ const hostile = [...FIXTURE_HOSTILE_SVGS, ...LOADS_ELSEWHERE.map((n) => ADVERSARIAL[n])].map((svg, i) => ({
label: `Hostile ${i + 1}`,
url: `https://hostile-${i + 1}.example/`,
svg,
}));
writeRawFixtureSettings(settingsFile(), [...FIXTURE_SOCIAL_TRIO.slice(0, 1), ...hostile]);
+ const elsewhere: string[] = [];
+ page.on("request", (r) => {
+ if (new URL(r.url()).origin !== new URL(page.url() || "http://localhost").origin && !r.url().startsWith("data:")) {
+ elsewhere.push(r.url());
+ }
+ });
await page.goto("/");
await page.waitForLoadState("load");
await page.waitForTimeout(300);
@@ -419,4 +426,5 @@ test("a stored icon that would run script is not inlined: no script runs, and th
// The good icon beside them is still an icon.
await expect(footer.getByRole("link", { name: "Leaf", exact: true }).locator("svg")).toHaveCount(1);
expect(await page.evaluate(() => document.querySelectorAll("[data-social-links] img, [data-social-links] image").length)).toBe(0);
+ expect(elsewhere.filter((u) => !u.startsWith(new URL(page.url()).origin)), "requests to another origin").toEqual([]);
});
diff --git a/homepage/e2e/svg-vectors.spec.ts b/homepage/e2e/svg-vectors.spec.ts
@@ -0,0 +1,42 @@
+import { test, expect } from "@playwright/test";
+import { ADVERSARIAL, REAL_SHAPES } from "../../common/lib/socialSvg.vectors";
+import { safeSocialSvg, scopeSvgIds, sizeSocialSvg } from "../../common/lib/socialLinks";
+
+// THE INVARIANT THE READ PATH DEPENDS ON, checked with Chromium's own HTML
+// parser: for every icon the checker accepts (the review's adversarial battery
+// and the shapes real icons take), rendered as a page renders it and parsed as
+// a whole document the way the static HTML carries it, the element after the
+// link stays OUTSIDE the icon, no script runs, and nothing is requested from
+// anywhere.
+
+const accepted = Object.entries({ ...ADVERSARIAL, ...REAL_SHAPES })
+ .map(([name, raw]) => {
+ const safe = safeSocialSvg(raw);
+ return safe ? { name, html: sizeSocialSvg(scopeSvgIds(safe, "sl_S_1_-0")) } : null;
+ })
+ .filter((x): x is { name: string; html: string } => x !== null);
+
+test("every accepted icon leaves the page after it outside the icon, runs nothing and fetches nothing", async ({
+ page,
+}) => {
+ expect(accepted.length).toBeGreaterThan(10);
+ const requests: string[] = [];
+ await page.route("**/*", (route) => {
+ requests.push(route.request().url());
+ return route.abort();
+ });
+ for (const { name, html } of accepted) {
+ await page.setContent(
+ `<!doctype html><html><body><header><a id="l" href="#x">${html}</a><b id="after">after</b></header><main id="main">m</main></body></html>`,
+ );
+ await page.waitForTimeout(50);
+ const r = await page.evaluate(() => ({
+ afterInSvg: !!document.getElementById("after")?.closest("svg"),
+ afterHolder: document.getElementById("after")?.parentElement?.tagName ?? null,
+ mainInSvg: !!document.getElementById("main")?.closest("svg"),
+ x: (window as unknown as { __x?: unknown }).__x ?? null,
+ }));
+ expect(r, name).toEqual({ afterInSvg: false, afterHolder: "HEADER", mainInSvg: false, x: null });
+ }
+ expect(requests, "requests").toEqual([]);
+});