Skip to content

fix(search): restore working fetch and escape query param against XSS#1091

Open
RAJVEER42 wants to merge 1 commit into
ceph:mainfrom
RAJVEER42:fix/search-cors-and-xss
Open

fix(search): restore working fetch and escape query param against XSS#1091
RAJVEER42 wants to merge 1 commit into
ceph:mainfrom
RAJVEER42:fix/search-cors-and-xss

Conversation

@RAJVEER42
Copy link
Copy Markdown

What this fixes

Two bugs in src/js/search-output.js that together have left blog search broken since June 2021.

Bug 1 — mode: 'no-cors' makes search permanently non-functional

Commit 4b9c4e3 added mode: 'no-cors' to the fetch calls while debugging a CORS issue. The Fetch spec defines no-cors as returning an opaque response — the body is inaccessible and calling .json() on it always throws. The catch block intercepts this and shows the "couldn't complete your search" error on every single query.

The search-index and search-output files are same-origin static assets. They need no special fetch mode. This reverts to the original plain fetch(url) call from before that commit.

Bug 2 — Reflected XSS via unescaped q URL parameter

The raw query value from urlParams.get('q') was interpolated directly into searchResultsHtml and then written via innerHTML. A crafted URL such as:

/en/news/blog/search/?q=<img src=x onerror=alert(document.cookie)>

would execute arbitrary JavaScript in the visitor's browser.

This was latent while Bug 1 was present (fetch always failed, renderResults was never reached). Fixing Bug 1 without this patch would immediately expose the XSS — so both are shipped together.

The fix adds a small escapeHtml helper and applies it to query before any HTML interpolation.

What changed

  • Removed method: 'GET', credentials: 'include', mode: 'no-cors' from the fetch options
  • Added escapeHtml() function scoped inside init()
  • Applied escapeHtml(query)safeQuery in both the no-results and results-found branches

What was considered and rejected

  • Using textContent for just the query display node: would require splitting the template into separate DOM operations, larger diff than needed
  • Sanitizing with a library (DOMPurify): heavyweight for a single string that only needs entity-escaping

The fetch call added in 4b9c4e3 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=<img src=x onerror=...> 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.
@RAJVEER42 RAJVEER42 requested a review from a team as a code owner May 17, 2026 17:53
@ceph-jenkins
Copy link
Copy Markdown

Thank you for your contribution. Since you, the author, are not a member of the Ceph GitHub Org yet, our CI will not automatically run. Any member of the Ceph Org may comment "ok - to - test" (without the dashes) to allow the Jenkins jobs to run.

@ceph-jenkins
Copy link
Copy Markdown

Site built/updated successfully! https://fix/search-cors-and-xss.ceph.io

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants