A vulnerability researcher known as Arusekk published a writeup on September 23 describing CVE-2026-92973, a cross-site scripting flaw in the ansi2html library that let anyone who could inject text into a SourceHut build log take over the accounts of people who viewed it. The bug had been sitting in the wild for close to four and a half years.

How the bug works

ansi2html is a Python library that converts terminal escape sequences into HTML. SourceHut’s CI service, builds.sr.ht, uses it to render job output in the browser. Among the sequences it handles are OSC 8 hyperlinks, the terminal escape codes that let shell programs emit clickable links. The handler failed to validate or escape the URL target, which produced two distinct attack paths.

The first path lets the crafted sequence break out of the anchor tag. A log line shaped like a terminated href followed by injected attributes produced output like this, straight from the researcher’s proof of concept:

<a href="https://example.com/"/autofocus/tabindex="1"/onfocus="alert`xss`">Nothing to see here</a>

The second path is even simpler. The converter happily emitted a javascript: URL as the href, which every browser will execute when the link is clicked, and some when the anchor is merely focused given the autofocus injection above.

Getting the payload in

The interesting part is that an attacker did not need to control the build worker or even have an account. SourceHut runs CI on patches submitted to public mailing lists, so mailing a patch whose diff prints a crafted escape sequence was enough. Alternatively, any remote resource a build fetched and echoed to the log, such as a file the attacker controlled on their own server, would carry the payload. That last route is what made the bug wormable: a victim’s browser session could resubmit a build containing the same payload, and anyone who viewed the new job log would be infected in turn.

Once the JavaScript ran, the damage was limited mostly by imagination. The build log page already contains the CSRF token, so the script could read it and submit the existing “Resubmit build” form under the victim’s identity, creating jobs as the victim. Jobs can also carry secrets. A successful attack against an administrator’s browser would expose deploy keys, including, on the flagship instance, keys used by SourceHut’s own services.

Severity: why this is more than a medium

The researcher devotes part of the writeup to how the bug should be scored, and the answer is contested. CVSS 4.0 at least distinguishes the vulnerable system from the subsequent one, and in this case the vulnerable system is the CI service while the actual damage lands in the victim’s browser and then bounces back into the service: high confidentiality impact (secrets exposed), high integrity impact (jobs submitted as the victim, with deploy key access), and automatable, since each victim can attack further victims. The researcher argues for high or critical rather than the medium some scanners assign, and it is hard to argue with the logic when a single admin visit to a log page could expose SourceHut’s own deploy keys.

The timeline is the lesson

The researcher’s disclosure timeline is worth reading closely, because it shows how long a dependency vulnerability can hide inside someone else’s product:

That is 4.5 years of exposure from a library whose maintainer had gone quiet, embedded by a project that assumed the library’s output was safe. Arusekk makes the uncomfortable point that even a careful audit of ansi2html would not have saved SourceHut permanently, because the safety guarantee would have to be re-verified on every version bump. SourceHut’s actual fix was to sanitize the HTML ansi2html produces inside builds.sr.ht itself, which is the right call: never trust a dependency’s escaping across a trust boundary.

Who is affected and what to do

The affected ranges are ansi2html 1.7.0 through 1.9.3 and builds.sr.ht 0.40.0 through 0.105.0. Upgrading to ansi2html 1.9.4 or later and builds.sr.ht 0.105.1 or later fixes new output. Several distributions have already moved: Arch updated on September 5, and Gentoo removed the vulnerable ebuilds entirely.

Old logs are the residual risk. Sanitization at the rendering layer protects viewers, but if you run an instance that only upgraded the library, historical logs may still hold crafted OSC 8 sequences. The researcher suggests grepping raw logs for escape sequences containing a quote character or a javascript: scheme before treating them as safe.

The broader takeaway applies well beyond SourceHut. Terminal output looks like text to a build system, but the moment it is rendered in a browser it is markup, and escape sequence converters are handling structured input, not plain text. Any service that displays command output, be it CI logs, web terminals, or chat bots piping tail -f, should apply the same scrutiny to those bytes as it would to user-supplied HTML. Content-Security-Policy would have blunted this too, though SourceHut’s reliance on inline scripts for the log page’s scrolling made removing unsafe-inline a bigger job than inserting one header.

Leave a Reply

Your email address will not be published. Required fields are marked *