Skip to content

Harden build-mode DevTools auth: require a capability token instead of disabling client auth #552

Description

@antfubot

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.tsisClientAuthDisabled(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:

  1. Generate a random per-process token.
  2. 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.
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions