Repository navigation
fix(producer): render servers only load assets from inside the project - #7579
Conversation
The HTTP render handlers and the distributed planner now compile with outside-project imports off: markup that reaches a file beyond the project directory (through ../, an absolute path, a file: URL or a symlink) is no longer copied into the compiled directory, served to the page, probed or extracted. Local `hyperframes render`, `--start/--end` range renders, `hyperframes benchmark` and Studio renders pass allowOutsideProjectAssets and keep importing outside files exactly as before. Co-Authored-By: Claude Opus 5.5 <[email protected]>
There was a problem hiding this comment.
Verdict: REQUEST CHANGES · 3 blockers · 0 important · 0 nits · head 1442930
- Required
regressionis red (shard 2,render-symlinked-assets). That fixture'ssrc/shared -> ../sharedpins render/preview parity for a symlinked sibling folder, and the harness (regression-harness.ts:1160) renders without the opt-in. So/shared/brand.css404s and all 100 frames fail (PSNR 6.4). Either have the harness pass the opt-in for this suite (for example arenderConfigflag in meta.json) or re-scope the fixture. Fail-fast cancelled the other shards, so they still need a full run. - Required
Buildis red:@hyperframes/producer unpacked size 3205746 bytes is over its budget of 3165274 by more than 1%. Main passes at the merge base. Either raisescripts/package-size-budgets.jsonand give the reason in the body, or find what grew the tarball. - Sub-composition symlink escape, see the inline comment.
Scope
I read all 21 files. outsideProjectAssets.test.ts passes 3/3 at the head. With the producer sources reverted to main, the two default tests fail, which matches the body. The fileServer and probeStage bun suites pass (133). I checked the other createFileServer and compile callers: the chunk and frame-cost servers serve the dereferenced plan copy, and server.ts and planV2 get the default. Windows render verification, CodeQL, Comments and PR captures are green.
— Genesis
…l render The render-symlinked-assets fixture links src/shared to a sibling folder outside the project to keep local renders working like preview. The regression harness now reads renderConfig.allowOutsideProjectAssets from a fixture's meta.json and passes it to the in-process and distributed-simulated renders, and this fixture sets it. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…nside the project Without the trusted-local opt-in, a sub-composition or entry file that leads outside the project once symlinks resolve is refused, the same as a ../ sub-composition already is. Local media that resolves outside the project, compiled and download folders, including absolute paths, file: URLs and percent-encoded ../ paths, is reported as a missing source and never probed or extracted. Remote media fetched during the render keeps working. A missing file is no longer probed for the compile-time advice. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…r checks The producer tarball grows by about 12.5 KB unpacked: the new checks live in the compiler and render stages, which the index, public-server and distributed bundles each include. No test files ship in the package. Co-Authored-By: Claude Opus 5.5 <[email protected]>
There was a problem hiding this comment.
Verdict: APPROVE · 3 resolved, 0 open, 0 new · 1442930 → 6a92e3b
Checked
At this head, a scratch test refuses a symlinked scene and a scene inside a symlinked folder by default, and the opt-in still inlines both. outsideProjectAssets.test.ts and the producer compile, stage and fileServer suites pass. The only local failures are 2 HEIC cases, which fail on main in this sandbox too. CI, regression, Windows render verification and CodeQL are green.
— Genesis
… policy CI's packed-manifest check measured @hyperframes/producer at 3,244,665 bytes unpacked (budget 3,210,000) and @hyperframes/studio-server at 653,506 (budget 646,000), both just past the 1% tolerance. Both packages were already at the edge on main. Measured locally with npm pack --dry-run, main is 3,237,162 for producer and 652,502 for studio-server, so studio-server on main is already at its ceiling. This branch adds about 7.9 KB unpacked to producer, because the new checks sit in the compiler, render stages and file server, which each producer entry bundles. It adds about 5.5 KB to studio-server for the new outside-file entry and its declarations. No source maps, tests or assets were added to either tarball. The budgets go to 3,245,000 and 654,000, just above CI's measurements, the same way #7579 raised producer's budget for the project-folder checks. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…om outside the project (heygen-com#7647) * fix(producer): local renders bring in only media, font, CSS and JS from outside the project Local renders (`render`, range render, `benchmark`, the Studio server and the Vite Studio adapter) run with allowOutsideProjectAssets, which copied every existing file that markup named outside the project into compiled/hf-ext, of any type and from any folder, and served project links that lead outside without looking at where they lead. Publish already applies a policy here: outside references are kept, private locations are left out, and only image, video, audio, font, CSS and JS files are copied. Local renders now follow the same policy. The publish predicates (private location, copied file type, left-out wording) move from the CLI to a new `@hyperframes/studio-server/outside-file` subpath, since the producer cannot import the CLI and already depends on studio-server. The CLI re-exports them, so publish behaves as before. The media extension sets move with them because the copied-type list includes them. The producer decides with one predicate, `outsideAssetRefusal` in utils/paths.ts, and applies it wherever allowOutsideProjectAssets lets an outside file in, so no entry point can skip it: - collectExternalAssets: src/href/url() references, which covers <a href> and <link href> - dropMediaOutsideProject: video/audio/image sources in any spelling the media resolver accepts (file: URLs, percent-encoding), so nothing probes or extracts them - the render file server: project links that lead outside - the distributed plan's project copy: the same links (range render) - GIF/HEIC prep and sub-composition links: private locations only, since those inputs are converted or compiled rather than copied as they are Each left-out file logs the publish wording with "left out of the render". To reproduce on main: a project whose index.html references ../../.ssh/id_key through <link href>, <a href> and url(), with a project link to a file in .ssh, rendered locally, gets those files copied into compiled/hf-ext and served. Comment lines: the four in studio-server's outsideFile.ts moved verbatim from the CLI files they replace. Two are new, in utils/paths.ts. The one on outsideAssetRefusal states the policy once, so its six call sites carry no comment. The one on refusesOutsideLink says its boolean means "refused, and already warned", which the name alone does not say. Comment ratchet is clean. Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(cli): keep outsideFileType as a re-export of the shared module The base repository's deletion guard rejects removing a file that exists on main unless git pairs it as a rename. outsideFileType.ts moved into studio-server/outside-file with enough added around it that git no longer pairs the two, so CI's "Reject accidental file deletions" step failed. The file stays as a two-name re-export and publish keeps importing from it, so publishProject.ts and its file-type test are back to main. Co-Authored-By: Claude Opus 5.5 <[email protected]> * chore(producer): raise the unpacked size budgets for the outside-file policy CI's packed-manifest check measured @hyperframes/producer at 3,244,665 bytes unpacked (budget 3,210,000) and @hyperframes/studio-server at 653,506 (budget 646,000), both just past the 1% tolerance. Both packages were already at the edge on main. Measured locally with npm pack --dry-run, main is 3,237,162 for producer and 652,502 for studio-server, so studio-server on main is already at its ceiling. This branch adds about 7.9 KB unpacked to producer, because the new checks sit in the compiler, render stages and file server, which each producer entry bundles. It adds about 5.5 KB to studio-server for the new outside-file entry and its declarations. No source maps, tests or assets were added to either tarball. The budgets go to 3,245,000 and 654,000, just above CI's measurements, the same way heygen-com#7579 raised producer's budget for the project-folder checks. Co-Authored-By: Claude Opus 5.5 <[email protected]> * fix(producer): apply the outside-file policy to embedded local fonts A project font file that links outside the project is now left out of font embedding with the same warning the file server gives, instead of being inlined into the compiled HTML. Co-Authored-By: Claude Opus 5.5 <[email protected]> --------- Co-authored-by: Claude Opus 5.5 <[email protected]>
What
Render servers (the producer HTTP handlers) and the distributed planner now load assets only from inside the project they are asked to render. Local
hyperframes render,--start/--endrange renders,hyperframes benchmarkand Studio renders keep importing outside files such as../shared/logo.pngexactly as before.Why
When markup points at a file outside the project directory, the compiler copies it into the render and serves it to the page. That is the right behaviour for a local render of your own project, which often shares a logo or font folder with sibling projects. A render service, though, should only read the project it was handed, so the producer needs a way to tell the two apart, with the service path as the default.
Related work
None.
How
allowOutsideProjectAssets, onRenderConfig,CompileForRenderOptionsand the planner options (plan()/buildLocalExecutionPlan). It is off by default. It is deliberately not a field on the serializable render request orDistributedRenderConfig:renderConfigFromRequesttakes it as a runtime argument and sets it explicitly, so request JSON cannot turn it on.hf-ext/copies are recorded;../sub-composition already is. This covers the pre-flight check, the sub-composition compile and the inliner, and the entry read in the compile stage and incompileForRender;file:URLs,../and percent-encoded..%2Fpaths, and symlinks. A warning names each one as a missing source, and it never reaches ffprobe or ffmpeg. The frame extractor, audio mixer, media-type preflight, HDR probes and video-layer passthrough all read these filtered entries, and the check also covers sources a page script sets at runtime. Remote media fetched during the render still works;true: the CLI local render and range render,hyperframes benchmark, the CLI Studio server render and the Vite Studio adapter render. The HTTP handlers,planV2/planV2WithPublisherand the cloud adapters pass nothing.renderConfig.allowOutsideProjectAssetsfrom a fixture'smeta.json.render-symlinked-assetssets it, because that fixture'ssrc/sharedlink to a sibling folder is the trusted local case it exists to keep working.bundleToSingleHtml) already inlines only real-path project files and is unchanged.@hyperframes/producerunpacked budget goes from 3,165,274 to 3,210,000 bytes.main, all in shipped source. The new checks live in the compiler and render stages, and theindex,public-serveranddistributedbundles each include those, at about 3.5 KB per bundle, plus typings.mainitself was within 400 bytes of the 1% tolerance.No visible change
The one Studio line (
packages/studio/vite.adapter.ts) passes the new opt-in, so Studio renders keep importing outside files exactly as before. Nothing on screen changes.Test plan
outsideProjectAssets.test.ts. Everynode:child_processcall is recorded, and the media files outside the project are real clips, so a probe or extraction of one would succeed and be visible.<img src="../…">, a<style>url(), an inline-styleurl(), a project symlink, and video/audio sources given as an absolute path, afile:URL,../and..%2F.hf-ext/entry, and the file server returns 404 for each path.httpsvideo in the same composition is still downloaded and its frames extracted.hf-extor the outside image, no ffprobe/ffmpeg call names an outside file, the remote clip is published, and a materialized chunk's file server returns 404.index.htmlsymlinked the same way, are refused both in-process and by Plan v2.plan()still import outside files, and the symlinked sub-composition is compiled in.mainfails the four default tests, and the opt-in test still passes.fileServer.test.tssymlinked-directory test now passes the opt-in and also checks for a 404 by default. TheprobeStage.test.tsfixture uses an existing project dir.executeRenderJob, headless Chrome) of a composition whose only image is../shared/logo.png, a solid red PNG. With the opt-in, the output frame is red(253,0,0). Without it, the frame is black.render-symlinked-assetsregression fixture locally in-process and distributed-simulated.shared/brand.cssloads and the frame matches the golden's layout.mainscore 20.77 dB on this host because of system fonts. CI's image is the reference.mainhere, or pass when re-run alone.— Rames Jusso
🤖 Generated with Claude Code