Skip to content

fix(studio): check Host on the dev server's API routes and honor preview allowed hosts - #7493

Merged
jrusso1020 merged 2 commits into
mainfrom
fix/studio-dev-server-hosts
Nov 10, 2026
Merged

jrusso1020 merged 2 commits into
mainfrom
fix/studio-dev-server-hosts

Conversation

@jrusso1020

@jrusso1020 jrusso1020 commented Nov 10, 2026 •

Copy link
Copy Markdown
Collaborator

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, and HYPERFRAMES_PREVIEW_ALLOWED_HOSTS now reaches Vite's own server.allowedHosts.

Why

Consistency with #7490, which added a Host check to every route of the CLI preview server:

  1. The studio-dev-api plugin registers its middleware directly in configureServer, and Vite 6 runs those before its own host-check middleware. So /api/*, /__hyperframes_config and /api/runtime.js on the dev server answered any Host, while the page itself did not.
  2. HYPERFRAMES_PREVIEW_ALLOWED_HOSTS reached createStudioApi's write check but not Vite's server.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

  • One middleware, registered first in the plugin's configureServer, covers /__hyperframes_config and /api/*. It allows what hostHeaderAllowed allows (loopback, bind, allowed hosts) plus what Vite allows by default, read from Vite 6.4's isHostAllowedWithoutCache: IPv4 literals, bracketed IPv6 literals, and the configured server.host name. That keeps vite --host on a LAN address working. Refusals are the CLI's JSON 403, naming the host and the env var.
  • Other routes are left to Vite's own check, so this middleware never refuses a request Vite would serve.
  • server.allowedHosts is 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.
  • The helpers are imported from studio-server source, the same way vite.studioMotion.ts imports core source, because the package's node condition can resolve to a stale dist.

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.ts drives the plugin's registered middlewares in order with a stand-in studio-server module:

    • a foreign Host gets a JSON 403 naming the host and the env var on /api/projects, /__hyperframes_config and /api/runtime.js
    • localhost, 127.0.0.1, [::1], a LAN IPv4 and a bracketed IPv6 literal are served (200)
    • a host listed in HYPERFRAMES_PREVIEW_ALLOWED_HOSTS is served, and an unlisted one is refused
    • routes the plugin does not own are passed on to Vite
    • server.allowedHosts contains the env hosts

    On 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) with HYPERFRAMES_PREVIEW_ALLOWED_HOSTS=studio.tunnel.example and ran curl with different Host headers:

    Host /__hyperframes_config /api/projects /
    other.example:5291 (main) 200 200 403
    other.example:5291 (this PR) 403 403 403
    studio.tunnel.example (main) 200 200 403
    studio.tunnel.example (this PR) 200 200 200
    localhost:5291 (this PR) 200 200 200
    192.168.1.20:5291 (this PR) 200 200 200
  • Documentation updated (if applicable): not applicable

  • Comments follow CONTRIBUTING.md "Comments"

  • Studio tsc --noEmit, oxlint, oxfmt, lefthook pre-commit (fallow, typecheck), comment-ratchet.mjs and check-comment-citations.mjs all pass. The one citation warning, at vite.config.ts:48, was already there.

Before

The Studio dev server started with HYPERFRAMES_PREVIEW_ALLOWED_HOSTS=studio.tunnel.example, opened at http://studio.tunnel.example:5291/. Vite refuses the page, even though the variable names that host.

Before: Vite blocks the Studio page on the allowed tunnel host

After

The same server and URL. The Studio page loads on the listed host.

After: Studio loads on the allowed tunnel host

— Rames Jusso

🤖 Generated with Claude Code

…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]>
@github-actions

github-actions Bot commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

Edit accuracy: accurate 267 (base branch 267), smooth 77 of those

The gate passes.
Smoothness is reported in the artifact, not gated. A case fails only if it fails 2 of 3 runs.

Quarantined, measured but not gated (8)

SanHsien added a commit to SanHsien/hyperframes that referenced this pull request Nov 10, 2026
…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]>
SanHsien added a commit to SanHsien/hyperframes that referenced this pull request Nov 10, 2026
…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]>

@heygengenesis heygengenesis Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread packages/studio/vite.config.ts Outdated
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();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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, but isLoopbackHost only matches localhost.
  • HYPERFRAMES_PREVIEW_ALLOWED_HOSTS=.trycloudflare.com: line 385 passes this to server.allowedHosts, where a leading dot means any subdomain, but hostAllowed compares names exactly. Even tunnel.example is 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Fixed in ad1a4da.

… 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]>

@heygengenesis heygengenesis Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: APPROVE — 1 resolved, 0 open, 0 new · 98cfa71→ad1a4da · CI green

— Genesis

@jrusso1020
jrusso1020 added this pull request to the merge queue Nov 10, 2026
Merged via the queue into main with commit b083526 Nov 10, 2026
82 checks passed
@jrusso1020
jrusso1020 deleted the fix/studio-dev-server-hosts branch November 10, 2026 15:02
@miguel-heygen miguel-heygen mentioned this pull request Nov 10, 2026
3 of 4 tasks
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