Repository navigation
fix(engine): hold the render page to the asset download address rules - #7748
Conversation
With PRODUCER_RESTRICT_PAGE_NETWORK=true the render page reaches only its own project server and public addresses, checked by host and by resolved address, the rule asset downloads already follow. Unset, nothing changes for local renders.
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Automations to automatically generate PRs for you. |
Edit accuracy: accurate 267 (base branch 267), smooth 116 of thoseThe gate passes. Quarantined, measured but not gated (8)
Unstable (1)
|
There was a problem hiding this comment.
Verdict: APPROVE · 0 blocker · 0 important · 1 nit · head a6037e14
The setting is off by default, so local renders are unchanged. All CI on this head is green, Windows render verification included.
Scope
Read all 7 files. Checked that both composition pages (frameCapture.ts:1682, :4143) install the filter before goto, and that the page origin equals serverUrl (navigation uses ${serverUrl}/index.html). Checked that videoFrameTransport's frame URLs sit on the page's own origin, so the second Fetch session lets them through, and that the render browser launches without site isolation (browserManager.ts:1189), so iframes stay on the page target. Ran both new test files locally against chrome-headless-shell 157, twice: 9/9 pass. With restrictPageNetwork turned into a no-op, 6 fail (5 of 7 + 1 of 2), which matches the fails-without-the-fix line.
| let verdict = verdicts.get(target.hostname); | ||
| if (!verdict) { | ||
| verdict = isPublicHost(target.hostname, resolveHost); | ||
| verdicts.set(target.hostname, verdict); |
There was a problem hiding this comment.
nit: a rejected lookup is cached as a refusal for the whole page. One DNS blip on a public CDN (fonts, a remote video) would then block every later request to that host for the rest of a long render, which Chrome alone would retry. Consider dropping the cache entry when resolveHost throws, so only refusals of resolved addresses stick.
What
A render service can now hold the render page to the same address rules that asset downloads follow. With
PRODUCER_RESTRICT_PAGE_NETWORK=true, the page reaches only its own project server and public addresses. Thecheck covers the literal host and every address a name resolves to. Unset (the default), nothing changes, so
local CLI and Studio renders that load from your own machine or network keep working.
Why
Asset downloads already refuse loopback, private, link-local and other special-use addresses, checking resolved
names as well (
packages/engine/src/utils/networkPolicy.ts,urlDownloader.ts). The render page itself had nosuch rule: anything the page requested went straight out from the render host. A service that renders other
people's projects needs the page to follow the same list.
Related work
Refs the shared special-use address list from #6853. A follow-up applies the rule browser-wide (see "Not covered
yet").
How
services/pageNetworkPolicy.ts: a CDPFetchsession on the page, the same patternvideoFrameTransport.tsuses. Each paused request continues when it targets the page's own origin, or a host that is public both as
written and as resolved. Anything else fails with
BlockedByClient. Verdicts are cached per host for the page.A host that does not resolve is refused.
config.ts:resolveRestrictPageNetwork()readsPRODUCER_RESTRICT_PAGE_NETWORK, besideresolvePageNavigationTimeout(). It describes the host, not one render, so it stays out ofEngineConfigsnapshots: plans made by an older version still validate.
frameCapture.ts: both pages that load a composition (the capture session page and the static verificationpage) install the filter before they navigate. Distributed chunks and the reference-background step use the
capture session, so they are covered too.
newPage()sites are not composition pages in the render path:hdrCapture.tsand the producer'sverifyStaticPage.tshave no render-path caller, andaudioFxRender.tsloads the engine's ownfile://host page.net::ERR_BLOCKED_BY_CLIENT, like any failed asset.docs/packages/producer.mdx: one paragraph under "Build a render service".Not covered yet
This slice filters requests made on the page's own target: the document, its frames (the render browser runs
frames in-process), dedicated workers, redirects,
fetch/XHR and every subresource. These are not covered yetand move to the browser-wide follow-up:
Chrome also does its own name lookup after this check, so a name whose answer changes between the two lookups is
not covered here. The browser-wide follow-up dials the checked address itself.
Test plan
pageNetworkPolicy.browser.test.ts(real headless Chrome): a page served from one local server tries to reach asecond local server by address, by a name that resolves to it, from an iframe, from a dedicated worker and
through a redirect. The second server records every request it receives and must record none.
169.254.169.254and a name resolving to it are refused within the test's time bound. A name that resolves to a public address
goes through. The page's own server goes through.
frameCapture-pageNetwork.test.ts: with the setting on, both composition pages install the filter before theynavigate; with it off, neither does.
Fails without the fix:
pageNetworkPolicy.browser.test.tswithrestrictPageNetworkreplaced by a no-op fails5 of 7.
frameCapture-pageNetwork.test.tswith the guard replaced by a no-op fails 1 of 2.Both new test files pass 3 runs in a row; the full engine suite,
tsc,oxlintandoxfmt --checkpass.Real renders from this branch on Linux, CLI from source, two runs each:
iframe-render-compatrender-video-overlay-stretch(video frames through the page)A copy of
iframe-render-compatwhose page fetcheshttp://169.254.169.254/renders normally with the settingon; the fetch fails with
net::ERR_BLOCKED_BY_CLIENTand shows in the browser diagnostics.Not exercised: Windows and macOS hosts, and the distributed chunk path end to end (it uses the same capture
session page, covered by the wiring test).