From 4b7b0068ae8cc6d4ca0d0e2d91b6fc06176c6682 Mon Sep 17 00:00:00 2001 From: RAJVEER42 Date: Sun, 17 May 2026 23:20:03 +0530 Subject: [PATCH] fix(search): restore working fetch and escape query param in HTML output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The fetch call added in 4b9c4e38 used mode:'no-cors', which returns an opaque response whose body is unreadable. Every call to res.json() threw, routing all queries to the error handler — search has been silently broken since June 2021. Reverts to the original plain fetch(url) call. Separately, the raw URL query string was interpolated directly into searchResultsHtml before being set via innerHTML. A crafted URL like ?q= would execute arbitrary JS in the visitor's browser once search was functional. Adds escapeHtml() and applies it to query before any HTML injection. These two fixes ship together: the XSS is latent while the fetch is broken, but becomes live the moment the fetch is restored. --- src/js/search-output.js | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/js/search-output.js b/src/js/search-output.js index 154595f08..eb97c3fb6 100644 --- a/src/js/search-output.js +++ b/src/js/search-output.js @@ -17,6 +17,15 @@ const SearchOutput = { const urlParams = new URLSearchParams(queryString); const query = urlParams.get('q'); + function escapeHtml(str) { + return str + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + } + async function initSearchIndex() { if (!urlParams.has('q')) return; @@ -24,13 +33,7 @@ const SearchOutput = { searchInput.value = query; const [searchIndex, searchOutput] = await Promise.all( - searchDataUrls.map(url => - fetch(url, { - method: 'GET', - credentials: 'include', - mode: 'no-cors', - }).then(res => res.json()) - ) + searchDataUrls.map(url => fetch(url).then(res => res.json())) ); const lunrIndex = lunr.Index.load(searchIndex); @@ -68,10 +71,12 @@ const SearchOutput = { if (!searchresultsContainer) return; + const safeQuery = escapeHtml(query); + if (!results.length) { - searchResultsHtml = `

${noResultsString} “${query}”

`; + searchResultsHtml = `

${noResultsString} “${safeQuery}”

`; } else { - searchResultsHtml = `

${searchedForString} “${query}”

+ searchResultsHtml = `

${searchedForString} “${safeQuery}”