Skip to content

0.55.1 — inline image preview no longer fetches private addresses - #203

Merged
VickyXAI merged 4 commits into
mainfrom
fix/inline-image-ssrf
Nov 3, 2026
Merged

VickyXAI merged 4 commits into
mainfrom
fix/inline-image-ssrf

Conversation

@VickyXAI

@VickyXAI VickyXAI commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

Refs #194.

buildInlineImageBlock (src/utils/inline-image.ts) fetched the gateway-returned image URL with a plain fetch(), so there was no private-address check and redirects were followed automatically. With inline previews enabled (off by default), a malicious or compromised gateway could make the user's machine request localhost, cloud metadata, or private-network hosts.

Fix. The DNS-resolved, per-hop SSRF guard already existed inline in resolveImageRef. It moves into fetchPublicUrl in src/utils/ssrf.ts, and both callers now use it:

  • only http(s) is allowed
  • every redirect hop is resolved and re-checked
  • redirect bodies are released before the next hop

data:image URIs from image2image routes still inline locally, with no network request.

Video and music poll URLs were already origin-pinned through resolveGatewayUrl/same-origin checks. This was the only gateway-URL fetch without a guard.

Tests. New cases in test/inline-image.test.ts:

  • private, loopback and metadata URLs are refused with zero requests made
  • a public URL that 302s into metadata never reaches the target. The mock follows 3xx like real fetch, so this case fails on the old code.
  • public-to-public redirects still inline
  • file: URLs are refused
  • data URIs still work

Three of the new cases fail on the old code. The ssrf.js module stubs in 5 test files gained a fetchPublicUrl passthrough. Full suite: 1429/1429 pass. tsc --noEmit is clean.

buildInlineImageBlock fetched whatever image URL the gateway returned with
a plain fetch(): no private-address check and automatic redirects. With
inline previews on, a malicious or compromised gateway (or an upstream
URL it passes through) could make the user's machine GET localhost,
169.254.169.254, or the private network.

The per-hop, DNS-resolved guard already existed inline in resolveImageRef.
Lift it into fetchPublicUrl in utils/ssrf.ts (http(s) only, every redirect
hop re-checked, redirect bodies released) and route both callers through
it. data:image URIs from image2image routes still inline without a
network round-trip.

Tests: private/loopback/metadata URLs are refused with zero requests made,
a public URL 302ing into metadata never reaches the target, public-to-public
redirects still work, file: is refused. The redirect case uses a mock that
follows 3xx like real fetch, and fails on the old code.

Refs #194
@VickyXAI VickyXAI changed the title fix(image): SSRF-guard the inline preview fetch of gateway URLs 0.54.8 — inline image preview no longer fetches private addresses Nov 3, 2026
resolveImageRef and buildInlineImageBlock checked Content-Length, then
buffered the whole body with arrayBuffer() and checked its length. A
chunked response has no Content-Length, and fetch() decodes gzip/br
transparently, so a few-KB brotli bomb advertised its compressed size and
inflated in memory until the abort. The comment claimed this prevented an
OOM in the stdio process; it only did for an honest header.

readCappedBody (utils/read-capped.ts) counts decoded bytes as they arrive
and cancels the stream at the cap. Both callers use it. Tests: an endless
body with no Content-Length is cancelled within one chunk of the cap.
@VickyXAI VickyXAI changed the title 0.54.8 — inline image preview no longer fetches private addresses 0.55.1 — inline image preview no longer fetches private addresses Nov 3, 2026
@VickyXAI
VickyXAI merged commit e21cdd3 into main Nov 3, 2026
3 checks passed
@VickyXAI
VickyXAI deleted the fix/inline-image-ssrf branch November 3, 2026 01:47
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.

1 participant