Summary
In build mode, Vite DevTools disables its client-auth gate entirely, so every accepted WebSocket client is marked trusted with no OTP or token. This is defense-in-depth hardening: I'd like build mode to require a per-process capability token instead of trusting all comers.
Context: the primary cross-origin bypass in this area — devframe's loopback WS Origin classifier accepting attacker-controlled DNS names beginning with 127. (e.g. 127.attacker.example) — has a fix in devframe (devframes/devframe#319). Once DevTools resolves to a patched devframe, the public-website exploit path is closed. This issue is about the second link in that chain, which lives here in DevTools.
Where
packages/core/src/node/auth-handler.ts — isClientAuthDisabled(context) returns true for context.mode === 'build' (alongside the explicit clientAuth === false opt-out and the VITE_DEVTOOLS_DISABLE_CLIENT_AUTH env escape hatch).
packages/core/src/node/server.ts (~L57/L80) — createDevToolsHub then passes auth: authDisabled ? false : getAuthHandler(context) to initHub. auth: false engages devframe's auto-trust shim, so any accepted connection is trusted.
packages/core/src/node/context.ts (~L80) — the same guard skips registering the interactive-auth RPC functions; both call sites must stay in agreement.
Residual risk (after the devframe origin fix)
The public-web chain is closed, but build mode still auto-trusts:
- any browser page served from a loopback origin — cross-port
localhost / *.localhost, e.g. another local dev tool the developer happens to have open; and
- any
Origin-less local process (the WS transport allows those by design).
A trusted client can then drive the built-in terminals RPC (devframes:plugin:terminals:spawn → default shell, then :write), i.e. command execution as the Vite/Node user. So the build-mode "disable auth" posture is the remaining weak point once the origin classifier is fixed.
Proposed hardening
In the implicit mode === 'build' branch, keep the zero-prompt UX but stop trusting arbitrary clients — require a per-process, unguessable capability token that only a client able to read the locally-served connection metadata can present:
- Generate a random per-process token.
- Install the real auth handler with that token in
clientAuthTokens (instead of auth: false), and register the interactive-auth RPC functions for this branch too. Don't print the OTP banner — trust comes purely from the token.
- Inject the same token into the emitted connection metadata's
authToken so the locally-served build viewer picks it up automatically (a same-origin viewer reads __connection.json; a cross-origin page can't, so it never learns the token).
The primitives already exist in devframe: createInteractiveAuth({ clientAuthTokens }) treats those tokens as always-trusted, and the browser client auto-sends ConnectionMeta.authToken in its anonymous:devframe:auth handshake.
The explicit clientAuth: false and VITE_DEVTOOLS_DISABLE_CLIENT_AUTH=true should keep fully disabling the gate — those are deliberate user choices; only the implicit build-mode branch should change.
References
Filed with the help of an agent.
Summary
In build mode, Vite DevTools disables its client-auth gate entirely, so every accepted WebSocket client is marked trusted with no OTP or token. This is defense-in-depth hardening: I'd like build mode to require a per-process capability token instead of trusting all comers.
Context: the primary cross-origin bypass in this area — devframe's loopback WS
Originclassifier accepting attacker-controlled DNS names beginning with127.(e.g.127.attacker.example) — has a fix in devframe (devframes/devframe#319). Once DevTools resolves to a patched devframe, the public-website exploit path is closed. This issue is about the second link in that chain, which lives here in DevTools.Where
packages/core/src/node/auth-handler.ts—isClientAuthDisabled(context)returnstrueforcontext.mode === 'build'(alongside the explicitclientAuth === falseopt-out and theVITE_DEVTOOLS_DISABLE_CLIENT_AUTHenv escape hatch).packages/core/src/node/server.ts(~L57/L80) —createDevToolsHubthen passesauth: authDisabled ? false : getAuthHandler(context)toinitHub.auth: falseengages devframe's auto-trust shim, so any accepted connection is trusted.packages/core/src/node/context.ts(~L80) — the same guard skips registering the interactive-auth RPC functions; both call sites must stay in agreement.Residual risk (after the devframe origin fix)
The public-web chain is closed, but build mode still auto-trusts:
localhost/*.localhost, e.g. another local dev tool the developer happens to have open; andOrigin-less local process (the WS transport allows those by design).A trusted client can then drive the built-in terminals RPC (
devframes:plugin:terminals:spawn→ default shell, then:write), i.e. command execution as the Vite/Node user. So the build-mode "disable auth" posture is the remaining weak point once the origin classifier is fixed.Proposed hardening
In the implicit
mode === 'build'branch, keep the zero-prompt UX but stop trusting arbitrary clients — require a per-process, unguessable capability token that only a client able to read the locally-served connection metadata can present:clientAuthTokens(instead ofauth: false), and register the interactive-auth RPC functions for this branch too. Don't print the OTP banner — trust comes purely from the token.authTokenso the locally-served build viewer picks it up automatically (a same-origin viewer reads__connection.json; a cross-origin page can't, so it never learns the token).The primitives already exist in devframe:
createInteractiveAuth({ clientAuthTokens })treats those tokens as always-trusted, and the browser client auto-sendsConnectionMeta.authTokenin itsanonymous:devframe:authhandshake.The explicit
clientAuth: falseandVITE_DEVTOOLS_DISABLE_CLIENT_AUTH=trueshould keep fully disabling the gate — those are deliberate user choices; only the implicit build-mode branch should change.References
Filed with the help of an agent.