Skip to content

feat: Options.Fetch and html2pdf.Offline (render with no network access) - #47

Merged
tannevaled merged 1 commit into
mainfrom
offline-fetcher
Nov 1, 2026
Merged

tannevaled merged 1 commit into
mainfrom
offline-fetcher

Conversation

@tannevaled

Copy link
Copy Markdown
Contributor

What

Options.Fetch (a Fetcher, the engine's Engine.Fetcher hook) replaces the network client for every http(s) sub-resource; html2pdf.Offline refuses all of them. Options.OnRefused(url) is called for each refusal, so callers can count them. CLI: --offline.

A server that renders HTML built from untrusted text (the llmflow gateway's decision report, go-llmflow/gateway#123) no longer depends on HTML escaping alone to stay out of its own network.

Fetch sites (go-webengine/engine v0.18.1, as pinned)

Reached by Export: <link rel=stylesheet> and @import (engine.go:787-806, via e.get -> Fetcher at engine.go:234-243), <img> incl. svg (images.go:307, fetchImageBytes images.go:578-611 -> fetchBytes engine.go:248-257).
Not reached: JS/fetch/XHR (no script engine in Export), @font-face (LoadFontFaces not called), iframe/object/embed/video (never loaded), CSS background images (loadOneBackground images.go:482 is not on the LoadImageSet path; measured: zero hits).
file: URLs: refused by the engine unless FileRoot is set (fileurl.go:53-56); Export pins it empty.
Redirects: followed by the default client (Go limit of 10, any host); documented on Options.Fetch. Offline sends nothing.

Tests

Loopback httptest recorder over 23 document shapes: zero hits under Offline; the control (default options) sees a hit for every kind the engine loads and fails if a kind not expected to load starts to. data: images render byte-identically; a file: BaseURL reads nothing; hostile URL table (userinfo, IPv6-mapped, decimal/hex hosts, //, backslashes, 70 kB URL, NUL, redirect); FuzzOfflineMakesNoRequest (20 s, no finding); CLI --offline test.

🤖 Generated with Claude Code

Export fetched stylesheets (<link>, @import) and images (<img>) with the
engine's default client: redirects followed to any host, no allow-list. A
server rendering HTML built from untrusted text then depended on its HTML
escaping alone to stay out of its own network (go-llmflow/gateway#123).

Options.Fetch is the engine's Engine.Fetcher hook (engine.go:68-74), which
every http(s) sub-resource goes through (engine.go:234-256). Offline refuses
all of them with ErrOffline; a refused resource is treated as missing and
Options.OnRefused is told its URL. data: URLs are decoded by the engine
before the hook, file: URLs are refused by the engine (FileRoot is empty and
Export now pins it so). CLI: --offline.

Tests: a loopback server records hits for every kind of sub-resource (zero
under Offline, a control that sees a hit without it), data: images still
render, a file: BaseURL reads nothing, hostile URL table, fuzz target.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tannevaled
tannevaled merged commit 287668d into main Nov 1, 2026
1 check passed
@tannevaled
tannevaled deleted the offline-fetcher branch November 1, 2026 07:57
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