output/sink scanner
0 requests

LLM Output Sink Scanner

Every prompt injection tool checks what goes into the model. This one checks what your app does with what comes out. Paste a model response, declare where you send it, and get per-sink proof of what would happen. It never fires a request; it shows you the request your renderer would have fired.

1. Paste the model response

Nothing leaves your browser. The text is neutralized as a string before any parser touches it, which is why loading a hostile response here cannot leak anything. Escaped control sequences such as \x1b, \u001b, \033, \e, \r and \t are understood as well as real control bytes, so a response copied out of a JSON log still analyses correctly.

0 characters

2. Declare the sinks

An allowlist is not a boundary. If an allowlisted host will fetch a URL on your behalf, it is a pass-through, so this tool never marks an allowlisted destination as safe. See the case receipt under the render findings.

Turn this off if your response legitimately contains literal backslash sequences.

Findings

No response loaded yet Paste a model response above, or press Load sample to see a hostile one that exercises all three sinks at once.

Reference: what actually fires a request

The tables below are a dated measurement, not a live computation. They exist so you can see the evidence behind the classifications above, and they are kept visually separate so that a row going stale can never make the scanner's own logic look wrong. The scanner's findings are computed from your input every time you press Scan.

Measured snapshot

Method: a local HTTP server that logs every path it is asked for, one page containing each construct pointing at a unique path, loaded once in a real browser. Measured 2026-08-09 in Microsoft Edge 151.0.4129.72 (Chromium) on Windows 10. Only the constructs listed were tested.

Constructs that fired a request

Constructs measured to fire a network request
ConstructShapeNote
Image sourceimg srcThe baseline case.
Image candidate setimg srcsetFires even with no src present.
Picture sourcesource srcsetSelected candidate is fetched; the fallback img was not.
Media sourcevideo src, audio src, source srcAudio fired with preload set to auto.
Video postervideo posterFetched before any play.
Object dataobject data
Embed sourceembed src
Image inputinput type=image srcOnly for type image.
Inline frameiframe src
Inline frame documentiframe srcdocAn img nested inside the srcdoc value fired too.
Preloadlink rel=preload as=imageAlso fired with as=script, as=style and as=font.
Stylesheetlink rel=stylesheet
Faviconlink rel=icon
Legacy faviconlink rel="shortcut icon"
Prefetchlink rel=prefetch
Prerenderlink rel=prerenderThe prerendered document was requested.
Module preloadlink rel=modulepreload
Web app manifestlink rel=manifestThe manifest JSON was requested.
Style attributestyle="background:url(...)"Also fired on <div/style= with a slash separator.
Style element rule<style> ... url(...)Fires when a matching element exists.
Style element import<style> @import url(...)Fired when the at-rule preceded the other rules. Placed after a rule it was ignored, which is the CSS ordering requirement. The scanner reports it either way.
Web font@font-face srcFired once the family was actually used by a rule.
Other CSS URL propertiesborder-image-source, cursor, list-style-image, mask-imageAll four fired.
SVG raster imagesvg image href
SVG legacy linksvg image xlink:hrefUppercase XLINK:HREF fired too.
SVG external usesvg use href="file.svg#id"See the note below this table.
SVG filter imagesvg feImage href
Legacy background attributetable backgroundFired on a table. Did not fire on a div.
Meta refreshmeta http-equiv=refreshThe navigation itself is a GET.
Detached elementcreateElement("img") + setAttribute("src", ...)Never inserted into the document. Fired anyway.
Detached innerHTMLdiv.innerHTML = "<img src=...>" (div not in the document)Never inserted into the document. Fired anyway.
Separator quirksimg/src=, LF CR TAB FF before the name, space or newline before =, unquoted value, SRC, SrCAll fired. This is why the neutralizer does not assume a single space.

Constructs that did not fire a request

Constructs measured not to fire a network request
ConstructResultNote
@import inside a style attributeno requestAn at-rule is not valid in a style attribute.
DOMParser parseFromString, text/htmlno requestTested with img, iframe, link stylesheet and a style attribute URL.
DOMParser parseFromString, image/svg+xmlno requestTested with an svg image href.
template.innerHTMLno requestTested with img and iframe. Content stayed in the template fragment.
createHTMLDocument + body.innerHTMLno requestTested with img and iframe.
a hrefno requestNeeds a click. Still reported, at lower severity.
form actionno requestNeeds a submit.
link rel=dns-prefetchno resource GETName resolution only; no path was requested.
link rel=preconnectno resource GETConnection setup only.
link rel=preload with no asno requestThe same href fetched once as was set. This is why the tool checks for it.
link rel=apple-touch-iconno requestWidely assumed to fetch. It did not, so it is reported without an auto-fetch claim.
link rel=canonical, link rel=alternateno request
div backgroundno requestThe legacy attribute is not honoured on a div.
&#115;rc= (entity in an attribute name)no requestCharacter references are not decoded in attribute names.
img alt=a/src=Uno requestThe slash is inside the unquoted alt value, so no src attribute exists.

Two places where the measurement changed the answer

External SVG use. It is widely repeated that external references in use are same-document-only in Chromium and WebKit. Whatever is true of rendering, the request fired: the external .svg file was requested. For an exfiltration sink only the request matters, because the data has already left by the time the renderer decides whether to draw anything. MDN notes separately that browsers may apply the same-origin policy to use and may refuse to load a cross-origin URL, which is about using the result, not about whether the GET happens. So this tool treats external use as an auto-fetch sink.

Detached nodes still fetch. The obvious safe-looking implementation for a tool like this one is "parse it into a div I never insert, then walk the DOM". Measured: a detached div assigned hostile innerHTML fired the request, and so did an img built with createElement and setAttribute. Being out of the document is not inertness.

Vendor documentation, quoted 2026-08-09

Why the neutralization pass runs before any parser

MDN documents the inert-document escape hatch explicitly. On DOMParser.parseFromString():

"While the document can download resources specified in <iframe> and <img> elements, it is essentially inert."

That is precisely the construct this tool exists to detect. My own measurement above says the request did not fire in Edge 151 today. Both statements can be true: documentation describes what is permitted, a measurement describes one engine on one day, and browsers change. The tool is built so the answer does not matter. Every dangerous attribute name and every resource-bearing element name is rewritten on the raw string, before a parser of any kind is handed the text. Nothing that could fetch survives to reach a parser, so the guarantee does not depend on this table still being correct next year.

How the zero-request guarantee works

The pipeline, in order

  1. Mask code regions. Fenced blocks, indented blocks, code spans and HTML comments are blanked out with spaces, preserving every offset so line numbers stay honest. An image inside a fenced code block is not a finding.
  2. Neutralize the string. A single pass rewrites src, srcset, srcdoc, href, xlink:href, data, poster, background, style and a dozen more to inert data-sink-* names, and renames style, link, base and meta elements to inert custom elements. The separator class is space, tab, line feed, form feed, carriage return and slash, because all six were measured to work.
  3. Then parse. Only the neutralized string is parsed, by a pure tokenizer that runs anywhere, plus an optional cross-check with the browser's own parser on the same neutralized string.
  4. Resolve markdown structure. Escaping HTML is not a fix, because a markdown image contains no HTML. Inline, full reference, collapsed and shortcut forms are all resolved against definitions collected from the whole document, so a definition forty lines away from its use is still found.
  5. Render results as text only. Found URLs go into the page as text nodes. The stylesheet contains no url() token anywhere, so no rule applied to a finding can become a request either.

The neutralizer was tested the same way the reference tables were: every construct measured to fire was run through it and the result was inserted into a live, attached div in a real page. Zero requests.

Sources