From a8a63cc27bb78cd92c5dd93a2a9347601a849030 Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Sun, 23 Aug 2026 17:42:20 -0400 Subject: [PATCH] The share card stops trusting request headers Host and x-forwarded-proto are attacker-writable and were interpolated raw into the share page's meta attributes. The proto must now be literally http or https, and the host is HTML-escaped like everything else that reaches the head. Co-Authored-By: Claude Fable 5 --- packages/server/src/index.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 5956b6c..f864bfe 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -143,7 +143,11 @@ const escapeHtml = (t: string) => /** index.html with this share's OpenGraph card folded into its head — * crawlers never run the app, so the unfurl must arrive pre-baked. */ -function shareHtml(id: string, data: ShareData, host: string, proto: string): string { +function shareHtml(id: string, data: ShareData, rawHost: string, rawProto: string): string { + // Host and proto arrive from request headers — attacker-writable text + // that must never reach an HTML attribute raw. + const proto = /^https?$/.test(rawProto) ? rawProto : "https"; + const host = escapeHtml(rawHost); // The stock page carries its own generic card; strip it, or crawlers // (which take the FIRST tag they meet) never see this turn's. const html = readFileSync(join(staticRoot!, "index.html"), "utf8")