From 0d7d845a8788b2d85e55e4a5f6a802eb80614ac4 Mon Sep 17 00:00:00 2001 From: Vatsal Sanjay Date: Wed, 1 Apr 2026 12:49:30 +0100 Subject: [PATCH 1/7] Render command palette text safely Build command result rows with DOM nodes so search titles and excerpts are inserted as text instead of interpolated into innerHTML. Keep icon rendering unchanged because those strings are defined in-repo, and add a regression test that proves HTML in search results is displayed as text rather than creating DOM nodes. --- assets/js/command-palette.js | 28 ++++++++++++---------- tests/command-palette-stale-search.test.js | 20 ++++++++++++++++ 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/assets/js/command-palette.js b/assets/js/command-palette.js index 60299cd..278c8df 100644 --- a/assets/js/command-palette.js +++ b/assets/js/command-palette.js @@ -5,8 +5,7 @@ // Store mutable state on window so duplicate script inclusion does not throw // on redeclaration before layout issues are fixed. -window.__commandPaletteSearchToken = - window.__commandPaletteSearchToken || 0; +window.__commandPaletteSearchToken = window.__commandPaletteSearchToken || 0; function nextSearchToken() { window.__commandPaletteSearchToken += 1; @@ -137,21 +136,26 @@ function renderSections(sections, container) { const cmdEl = document.createElement("div"); cmdEl.className = "command-palette-command"; - let cmdContent = ` -
${cmd.icon || ""}
-
${cmd.title}
- `; + const iconEl = document.createElement("div"); + iconEl.className = "command-palette-icon"; + iconEl.innerHTML = cmd.icon || ""; + cmdEl.appendChild(iconEl); + + const titleEl = document.createElement("div"); + titleEl.className = "command-palette-title"; + titleEl.textContent = cmd.title || ""; + cmdEl.appendChild(titleEl); // Add excerpt for search results if available if (cmd.excerpt) { - cmdContent += `
${cmd.excerpt.substring( - 0, - 120 - )}${cmd.excerpt.length > 120 ? "..." : ""}
`; + const excerptEl = document.createElement("div"); + excerptEl.className = "command-palette-excerpt"; + const excerpt = cmd.excerpt.substring(0, 120); + excerptEl.textContent = + excerpt + (cmd.excerpt.length > 120 ? "..." : ""); + cmdEl.appendChild(excerptEl); } - cmdEl.innerHTML = cmdContent; - cmdEl.addEventListener("click", function () { if (typeof cmd.handler === "function") { document.getElementById("simple-command-palette").style.display = diff --git a/tests/command-palette-stale-search.test.js b/tests/command-palette-stale-search.test.js index 88c40fb..ffba203 100644 --- a/tests/command-palette-stale-search.test.js +++ b/tests/command-palette-stale-search.test.js @@ -123,4 +123,24 @@ describe("command palette stale-search guard", () => { expect(resultsContainer.innerHTML).toBe(snapshotAfterCurrent); expect(resultsContainer.textContent).not.toContain("Late Stale Result"); }); + + it("renders title and excerpt as text for search results", async () => { + window.renderCommandResults("abc"); + + resolveFirst([ + { + title: "Unsafe", + excerpt: "Excerpt", + icon: "", + section: "Search Results", + }, + ]); + await Promise.resolve(); + await Promise.resolve(); + + expect(resultsContainer.querySelector("img")).toBeNull(); + expect(resultsContainer.querySelector("script")).toBeNull(); + expect(resultsContainer.textContent).toContain("Unsafe"); + expect(resultsContainer.textContent).toContain("Excerpt"); + }); }); From bfbd618d0b1db7276d5aaec15b8caf014dd98a56 Mon Sep 17 00:00:00 2001 From: Vatsal Sanjay Date: Wed, 1 Apr 2026 12:49:35 +0100 Subject: [PATCH 2/7] Pin Font Awesome stylesheet loading Replace runtime Font Awesome injection in each layout with a fixed CDN stylesheet reference protected by SRI. This removes the hostname-based script/css branching, avoids loading a kit script into the page head, and keeps icon rendering consistent across local and deployed pages. --- _layouts/default.html | 35 +++++-------------------- _layouts/history.html | 35 +++++-------------------- _layouts/join-us.html | 30 +++++---------------- _layouts/research.html | 35 +++++-------------------- _layouts/teaching-course.html | 49 ++++++++++------------------------- _layouts/teaching.html | 35 +++++-------------------- _layouts/team.html | 35 +++++-------------------- 7 files changed, 56 insertions(+), 198 deletions(-) diff --git a/_layouts/default.html b/_layouts/default.html index 224888c..ba6b29a 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -151,34 +151,13 @@ }); })(); - + diff --git a/_layouts/history.html b/_layouts/history.html index b9f13fb..db26b26 100644 --- a/_layouts/history.html +++ b/_layouts/history.html @@ -130,34 +130,13 @@ })(); - + diff --git a/_layouts/join-us.html b/_layouts/join-us.html index 22f3612..d96a825 100644 --- a/_layouts/join-us.html +++ b/_layouts/join-us.html @@ -93,29 +93,13 @@ }); })(); - +
diff --git a/_layouts/research.html b/_layouts/research.html index 2d93833..e6c492a 100644 --- a/_layouts/research.html +++ b/_layouts/research.html @@ -113,34 +113,13 @@ }); })(); - + diff --git a/_layouts/teaching-course.html b/_layouts/teaching-course.html index c5ba319..aad04da 100644 --- a/_layouts/teaching-course.html +++ b/_layouts/teaching-course.html @@ -5,7 +5,7 @@ - {% if page.title %}{{ page.title }} - {% endif %}{{ site.title }} + {% if page.title %}{{ page.title | escape_once }} - {% endif %}{{ site.title | escape_once }}