Linux malloc 7.1.6-terminal #1 SMP PREEMPT_DYNAMIC x86_64 GNU/Linux
root@malloc:~$ ls
root@malloc:~/blog$ cat search-has-escaped
# Search Has Escaped | Jul 29, 2026

Search is supposed to live in the address bar.

It is long and ugly there, but it is contained. People may type into it and Search may return a document, or perhaps several million documents that do not contain the thing you asked for. This is the old arrangement.

On Tuesday, Search got into the page.

It happened on the results screen. When nothing matched, the application repeated the query back to the user so they would know exactly what had failed them.

const q = new URLSearchParams(location.search).get('q') ?? '';
message.innerHTML = `No results for <strong>${q}</strong>`;

The query was obtained from location.search. This is the place in JavaScript where Search can be observed in captivity. The application then placed it into innerHTML.

I do not know why.

Some people believe the <strong> element makes this safe. It does not. <strong> is a means of making letters thick. It has never stopped a crime and has no legal powers.

I sent the following URL:

/search?q=%3Cimg%20src%3Dx%20onerror%3Dalert(document.domain)%3E

The strange symbols are percent-encoding. They allow unpleasant characters to travel inside a URL wearing little cardboard boxes. URLSearchParams opens the boxes. What comes out is this:

<img src=x onerror=alert(document.domain)>

There is no image at x, which is normal because x is not a place. The image fails to load. Its onerror handler runs. Search now causes JavaScript to execute.

This is DOM XSS. The attacker-controlled value is exposed through a browser source, crosses the page’s JavaScript and reaches an HTML-parsing sink. The backend does not need to store it or render it. It may spend the entire incident doing nothing and will later receive several emails.

The alert is only a noise made by researchers so that nearby people will know a finding has occurred. If I send the crafted link to another user and they open it, my code runs in the application’s origin and their session. I can make authenticated requests, read responses exposed to the frontend, alter the page and capture information entered after the payload runs.

Search has become venomous.

There is no <script> tag in the payload. This troubles committees that have spent years preparing specifically for the arrival of a <script> tag. Event handlers execute JavaScript too. The browser has known this for a long time and did not think to mention it at the meeting.

The defect is not that the query contains an image, an event handler or the word alert. The defect is that a string controlled by the URL is handed to an HTML parser when the page only wanted to display text.

Search should not be parsed. Search has no markup to contribute.

const q = new URLSearchParams(location.search).get('q') ?? '';
const strong = document.createElement('strong');
strong.textContent = q;
message.replaceChildren('No results for ', strong);

textContent places the query in a text node. The angle brackets remain angle brackets. The image is not born. Its event handler receives no body and no opportunities.

The page still says there are no results.