Repository navigation
fix(studio): check Host on the dev server's API routes and honor preview allowed hosts - #7493
Conversation
…iew allowed hosts The Studio Vite dev server now applies the same Host rule as the CLI preview server to the routes its plugin serves (/api/*, /__hyperframes_config), registered ahead of the plugin's other middleware, and also accepts the IP-literal and configured hosts Vite allows by default. HYPERFRAMES_PREVIEW_ALLOWED_HOSTS is now fed into Vite's server.allowedHosts, so a host listed there loads the Studio page as well as passing the API's checks. Co-Authored-By: Claude Opus 5.5 <[email protected]>
…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 <[email protected]>
…heygen-com#7493) The Vite dev server now refuses a foreign Host on the plugin's /api/* and /__hyperframes_config routes and honors HYPERFRAMES_PREVIEW_ALLOWED_HOSTS, closing the DNS-rebinding read of project files in dev mode. Adopted from the still-open upstream PR heygen-com#7493 (head 98cfa71) unchanged, so it stays pinned in monitored to compare once merged. The new test failed before the change (3 failed) and passes after it (5 passed); studio typecheck exit 0. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
There was a problem hiding this comment.
Verdict: APPROVE · 0 blocker · 1 important · 0 nits · head 98cfa71
The rebinding gap is closed: the guard runs before Vite's host check and covers every route the plugin serves. The one finding is a non-blocking mismatch that fails closed. ↓ inline
Scope
Read vite.config.ts and the test end to end, plus requestIdentity.ts and Vite 6.4.2's isHostAllowedWithoutCache and middleware order. A separate adversarial pass found no reachable route: the guard's predicate is a superset of the handlers' (?query, absolute-form, //api, /API reach neither), and WS/HMR, /@fs and static files stay behind Vite's own check. CLI dev/local modes pass --host 127.0.0.1, so server.config.server.host is the string the guard expects. Required CI is green on this head; the one red PR captures run was superseded by a green rerun.
— Genesis
| const url = req.url ?? ""; | ||
| if (url !== "/__hyperframes_config" && !url.startsWith("/api/")) return next(); | ||
| const host = req.headers.host ?? ""; | ||
| if (hostHeaderAllowed(host, allowedHosts) || isIpLiteralHost(host)) return next(); |
There was a problem hiding this comment.
important (non-blocking): the guard is stricter than the Vite rule it sits in front of, so the page loads and then every /api/* call returns 403. Two cases:
Host: studio.localhost:5190: Vite allows*.localhost, butisLoopbackHostonly matcheslocalhost.HYPERFRAMES_PREVIEW_ALLOWED_HOSTS=.trycloudflare.com: line 385 passes this toserver.allowedHosts, where a leading dot means any subdomain, buthostAllowedcompares names exactly. Eventunnel.exampleis refused against.tunnel.example.
Fix: accept .localhost suffixes and leading-dot entries in the guard (or in hostAllowed), so the page and its API agree on which hosts are allowed. A test with a leading-dot entry would pin it.
… on the API routes too The dev server's API Host check was stricter than Vite's own rule, so a page Vite served on studio.localhost or under a leading-dot allowed host got a 403 from every /api call. The check now applies Vite's rule alongside the CLI's. Co-Authored-By: Claude Opus 5.5 <[email protected]>
There was a problem hiding this comment.
Verdict: APPROVE — 1 resolved, 0 open, 0 new · 98cfa71→ad1a4da · CI green
— Genesis
What
The Studio Vite dev server (
packages/studio/vite.config.ts) now applies the same Host rule as the CLI preview server to the routes its plugin serves, andHYPERFRAMES_PREVIEW_ALLOWED_HOSTSnow reaches Vite's ownserver.allowedHosts.Why
Consistency with #7490, which added a Host check to every route of the CLI preview server:
studio-dev-apiplugin registers its middleware directly inconfigureServer, and Vite 6 runs those before its own host-check middleware. So/api/*,/__hyperframes_configand/api/runtime.json the dev server answered any Host, while the page itself did not.HYPERFRAMES_PREVIEW_ALLOWED_HOSTSreachedcreateStudioApi's write check but not Vite'sserver.allowedHosts. On a tunnel or Codespaces name, Vite still refused the Studio page, and the API's 403 pointed at a variable that could not fix it.Related work
Refs #7490
How
configureServer, covers/__hyperframes_configand/api/*. It allows whathostHeaderAllowedallows (loopback, bind, allowed hosts) plus what Vite allows by default, read from Vite 6.4'sisHostAllowedWithoutCache: IPv4 literals, bracketed IPv6 literals, and the configuredserver.hostname. That keepsvite --hoston a LAN address working. Refusals are the CLI's JSON 403, naming the host and the env var.server.allowedHostsis set to the env list. Vite's default is an empty list, and its implicit rules (localhost, IP literals,server.host) still apply on top of it.vite.studioMotion.tsimports core source, because the package'snodecondition can resolve to a staledist.Not matched: Vite also allows
*.localhost, which the CLI preview server does not. The dev server follows the CLI here.Test plan
Unit tests added:
packages/studio/vite.config.hosts.test.tsdrives the plugin's registered middlewares in order with a stand-in studio-server module:/api/projects,/__hyperframes_configand/api/runtime.jslocalhost,127.0.0.1,[::1], a LAN IPv4 and a bracketed IPv6 literal are served (200)HYPERFRAMES_PREVIEW_ALLOWED_HOSTSis served, and an unlisted one is refusedserver.allowedHostscontains the env hostsOn
origin/main, 3 of the 5 fail (foreign host 403, env-listed host,server.allowedHosts). All 5 pass on this branch. The full studio suite passes: 737 files, 8419 tests.Manual testing performed: started the dev server (
bun --bun vite --host 127.0.0.1 --port 5291) withHYPERFRAMES_PREVIEW_ALLOWED_HOSTS=studio.tunnel.exampleand ran curl with different Host headers:/__hyperframes_config/api/projects/other.example:5291(main)other.example:5291(this PR)studio.tunnel.example(main)studio.tunnel.example(this PR)localhost:5291(this PR)192.168.1.20:5291(this PR)Documentation updated (if applicable): not applicable
Comments follow CONTRIBUTING.md "Comments"
Studio
tsc --noEmit, oxlint, oxfmt, lefthook pre-commit (fallow, typecheck),comment-ratchet.mjsandcheck-comment-citations.mjsall pass. The one citation warning, atvite.config.ts:48, was already there.Before
The Studio dev server started with
HYPERFRAMES_PREVIEW_ALLOWED_HOSTS=studio.tunnel.example, opened athttp://studio.tunnel.example:5291/. Vite refuses the page, even though the variable names that host.After
The same server and URL. The Studio page loads on the listed host.
— Rames Jusso
🤖 Generated with Claude Code