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 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
b120f9b6bd
commit
a8a63cc27b
@@ -143,7 +143,11 @@ const escapeHtml = (t: string) =>
|
|||||||
|
|
||||||
/** index.html with this share's OpenGraph card folded into its head —
|
/** index.html with this share's OpenGraph card folded into its head —
|
||||||
* crawlers never run the app, so the unfurl must arrive pre-baked. */
|
* 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
|
// The stock page carries its own generic card; strip it, or crawlers
|
||||||
// (which take the FIRST tag they meet) never see this turn's.
|
// (which take the FIRST tag they meet) never see this turn's.
|
||||||
const html = readFileSync(join(staticRoot!, "index.html"), "utf8")
|
const html = readFileSync(join(staticRoot!, "index.html"), "utf8")
|
||||||
|
|||||||
Reference in New Issue
Block a user