fix(website): harden detail heading sanitization

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 80686fef-efe3-4cdd-8cd6-bfa61a5d0af6
This commit is contained in:
Aaron Powell
2026-08-19 12:19:41 +10:00
parent 5c50251e78
commit 960df5cba3
+25 -2
View File
@@ -381,6 +381,30 @@ const slugify = (text: string) =>
.replace(/[^a-z0-9]+/g, "-") .replace(/[^a-z0-9]+/g, "-")
.replace(/^-+|-+$/g, ""); .replace(/^-+|-+$/g, "");
const stripHtmlTags = (html: string) => {
let text = "";
let inTag = false;
let quote: '"' | "'" | undefined;
for (const character of html) {
if (inTag) {
if (quote) {
if (character === quote) quote = undefined;
} else if (character === '"' || character === "'") {
quote = character;
} else if (character === ">") {
inTag = false;
}
} else if (character === "<") {
inTag = true;
} else {
text += character;
}
}
return text;
};
/** /**
* Derive the in-page table of contents from rendered markdown, stamping a * Derive the in-page table of contents from rendered markdown, stamping a
* stable `id` onto every `<h2>` so the TOC links and scroll-spy have anchors. * stable `id` onto every `<h2>` so the TOC links and scroll-spy have anchors.
@@ -400,8 +424,7 @@ export function buildDetailToc(markdownHtml: string): {
const html = markdownHtml.replace( const html = markdownHtml.replace(
/<h2([^>]*)>([\s\S]*?)<\/h2>/g, /<h2([^>]*)>([\s\S]*?)<\/h2>/g,
(match, attrs: string, inner: string) => { (match, attrs: string, inner: string) => {
const label = inner const label = stripHtmlTags(inner)
.replace(/<[^>]+>/g, "")
.replace(/&lt;|&gt;|&quot;|&#39;|&amp;/g, (entity) => { .replace(/&lt;|&gt;|&quot;|&#39;|&amp;/g, (entity) => {
switch (entity) { switch (entity) {
case "&lt;": case "&lt;":