Repository navigation
fix(studio-server): buildSubCompositionHtml reads only compositions inside the project - #7482
Conversation
…nside the project The helper joined projectDir with compPath and read the result, trusting every caller to have confined the path first. Studio's own preview route does; an embedder that forgets would serve any readable file. The helper now resolves compPath with core's resolveWithinProject and returns null for a path outside the project, the same answer as a missing file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Edit accuracy: accurate 267 (base branch 267), smooth 105 of thoseThe gate passes. Quarantined, measured but not gated (8)
Unstable (2)
|
jerrai-bot-heygen
left a comment
There was a problem hiding this comment.
Security re-review at 036c976: the new resolveWithinProject guard confines the requested composition file, including an existing outside-pointing symlink. Hosted support tests exercised the new spec (26 passing); no local test or baseline run by me.
Residual read path: packages/studio-server/src/helpers/subComposition.ts:336–339 separately reads the project's index.html via join/readFileSync, without applying resolveWithinProject. If index.html is a symlink to a file outside the project, a valid in-project composition passes the new check, yet the helper incorporates the outside file's <head> content in its returned HTML. This was present before this PR, but the stated defense-in-depth guarantee that the helper only reads within the project remains incomplete. Please confine that secondary read too, and cover an existing outside-pointing symlink with a test. I have source-verified the call chain but not run this case. Also the new sibling-path test builds a path using outside.split('/'); on Windows it may not construct the actual sibling path, so its green result does not prove the old escape was tested there.
Findings-only COMMENT, not an approval or merge. — Jerrai
… inside the project Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jerrai-bot-heygen
left a comment
There was a problem hiding this comment.
Re-review at 28b52977: the prior secondary-read finding is addressed. buildSubCompositionHtml now resolves both the requested composition and the separate project index.html through resolveWithinProject before either file read. The new regression uses a valid in-project scene plus an outside-pointing index.html symlink, asserts the scene remains present and the external head marker is absent, and all 27 helper tests executed successfully in the exact-head hosted support job. I found no new code blocker in this incremental fix. The guard checks existing paths/symlinks but is not an atomic open against concurrent symlink replacement; no such attacker-controlled race was established here. No deployed test or local test by me. Findings-only COMMENT, not approval or merge. — Jerrai
There was a problem hiding this comment.
Verdict: APPROVE · 0 blocker · 0 important · 0 nits · 28b52977
Both reads in buildSubCompositionHtml (the composition and the root index.html head) now go through resolveWithinProject, so .., absolute paths and outside-pointing symlinks return null or no head. Studio's own preview route already confines compPath the same way, so local Studio behaves the same apart from the symlinked-index.html case the body already describes. Required CI is green at this head, and the new tests ran in Test (support) (27 pass).
Scope
I read subComposition.ts, its test, core/src/safePath.ts and the only in-repo caller (routes/preview.ts). Jerrai's earlier finding about the secondary index.html read is fixed at this head. Out of scope: assetExists still probes join(projectDir, …) for asset refs. That only checks whether a file exists and doesn't return its contents, and it was there before this PR.
— Genesis
…ject (upstream heygen-com#7482) buildSubCompositionHtml resolved the composition path and the root index.html with a bare join, so a path with .. segments or an absolute path, or a symlinked index.html, could read a file outside the project into the preview. Both now go through resolveWithinProject and return null (or leave the head out) when the path leaves the project. Ported from upstream 724ab39 (James Russo) by hand because the file imports differ. Fork changes to the upstream test: the escape path is built with relative()/sep because the upstream split("/") never forms a traversal on Windows (the traversal case passed before the fix there), and the symlink case is skipped when the process cannot create symlinks (EPERM without Developer Mode). The traversal case failed before the source change and passes after it: subComposition.test.ts 22 passed, 1 skipped. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…nce watermark Seventeen commits, PRs 7469-7493, issues 7473 and 7486 and twelve pin moves are recorded with their evidence in the continued ledger, DECISIONS, UPSTREAM and ROADMAP. Adopted: heygen-com#7466, heygen-com#7479, heygen-com#7482, heygen-com#7490 and the heygen-com#7441 and heygen-com#7440 docs. Deferred: heygen-com#7469, heygen-com#7471, heygen-com#7476, heygen-com#7480, heygen-com#7468, heygen-com#7487, heygen-com#7491. Not applicable: heygen-com#7470, heygen-com#7351, heygen-com#7475, heygen-com#7483. The watermark moves to 61e720b, PR heygen-com#7493, issue heygen-com#7486; thirteen new PRs are pinned. FORK.md records the subComposition test differences. The core packed-size roadmap row trigger is not met. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…opped in e591201 The manual port of upstream heygen-com#7482 removed the existing test that loads the default gsap version when the project head has no gsap. Restored unchanged; subComposition.test.ts is 23 passed, 1 skipped. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
What
buildSubCompositionHtmlreturnsnullfor acompPaththat resolves outsideprojectDir, the same answer it gives for a missing file. It also leaves out the projectindex.htmlhead when that file resolves outside the project, for example through a symlink.Why
The helper is exported from
@hyperframes/studio-server(and re-exported by@hyperframes/core/studio-api), and it readjoin(projectDir, compPath)without a containment check, trusting each caller to confine the path first. Studio's own preview route does (resolveWithinProjectinroutes/preview.ts). An embedder that forgets would serve any file the process can read. Moving the check into the helper means no caller has to remember it.Related work
Defense in depth for an embedder-side fix that landed separately; no public issue.
How
compFilecomes from core'sresolveWithinProject(@hyperframes/core/safe-path), the helper the rest of Studio uses, which also follows symlinks before the containment check. Anullresult returnsnullbefore anyexistsSyncorreadFileSync.Test plan
New tests in
subComposition.test.ts: a../<sibling>/secret.txtpath and an absolute path both returnnull(fails onmain); anindex.htmlsymlinked to a file outside the project contributes no head content (fails at the previous head). All 27 pass here.Behavior change for local Studio: a project whose
index.htmlis a symlink to a file outside the project loses that head in sub-composition previews, matching how the preview route already treats a composition file symlinked outside.vitest run src/routes/previewin studio-server: 159 pass (186 with the helper tests). That route already confines its own path, so its behavior is unchanged.oxlintandoxfmt --checkare clean on both files, andcheck:package-subpathspasses.Unit tests added/updated
Manual testing performed
Documentation updated (if applicable)
Comments follow CONTRIBUTING.md "Comments"
— Rames Jusso
🤖 Generated with Claude Code