Skip to content

fix(devframe): reject non-loopback DNS origins beginning with 127. - #319

Merged
antfu merged 1 commit into
mainfrom
fix/loopback-origin-dns-rebinding
Sep 1, 2026
Merged

fix(devframe): reject non-loopback DNS origins beginning with 127.#319
antfu merged 1 commit into
mainfrom
fix/loopback-origin-dns-rebinding

Conversation

@antfubot

Copy link
Copy Markdown
Collaborator

Summary

Devframe's WebSocket/SSE/MCP origin gate treated any hostname beginning with 127. as loopback, so an attacker-controlled DNS name such as 127.attacker.example or 127.0.0.1.attacker.example slipped past the loopback trust boundary. Because every transport (ws-server, ws-bun, ws-deno, sse-server, the MCP fetch handler) and the external-viewer origin registry all funnel their origin check through isLoopbackHostname, a cross-origin browser page could defeat the DNS-rebinding / cross-site WebSocket-hijacking mitigation and reach the privileged RPC surface (e.g. build-mode DevTools where accepted clients are auto-trusted, then the terminals RPC → shell command execution).

The root cause was a prefix test:

h.startsWith('127.')

which never verified the hostname was an IPv4 literal.

Fix

Classify the IPv4 loopback case structurally: the whole hostname must be a canonical dotted-decimal literal in 127.0.0.0/8. Genuine loopback addresses (127.0.0.1, 127.5.5.5) stay allowed while 127.* DNS names are rejected. The localhost / *.localhost / ::1 branches are unchanged.

The fix lives in the single shared classifier, so it closes the bypass for the WS Node/Bun/Deno transports, the SSE transport, the route-based MCP endpoint, and the origin registry at once.

Tests

Extended the origin-check unit test with regression cases:

Origin hostname Result
localhost, foo.localhost, 127.0.0.1, 127.5.5.5, ::1 allow
127.attacker.example, 127.0.0.1.attacker.example, 127.0.0, 127.0.0.256, 1270.0.0.1, evil.example reject

isAllowedOrigin is likewise checked to accept http://127.0.0.1:5173 and reject http://127.attacker.example.

Audit

Confirmed isLoopbackHostname is the only place this classification is implemented; all other origin gates delegate to it. The public API surface is unchanged (only private helpers were added), so the tsnapi snapshot is unaffected.

  • CWE-346: Origin Validation Error
  • CWE-1385: Missing Origin Validation in WebSockets

This PR was created with the help of an agent.

isLoopbackHostname classified any hostname starting with '127.' as
loopback, so an attacker-controlled DNS name like 127.attacker.example
passed the loopback origin gate that guards the RPC/MCP surface. Because
every WS/SSE/MCP transport and the origin registry funnel through this
check, a cross-origin browser page could defeat the DNS-rebinding /
cross-site WebSocket-hijacking mitigation and reach privileged RPC.

Match the IPv4 loopback case structurally instead: the whole hostname
must be a canonical dotted-decimal literal in 127.0.0.0/8. Genuine
loopback addresses (127.0.0.1, 127.5.5.5) stay allowed; 127.* DNS names
are rejected.

CWE-346, CWE-1385
@vercel

vercel Bot commented Aug 31, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
devframe Skipped Skipped Aug 31, 2026 5:25am
@antfu
antfu merged commit 0972847 into main Sep 1, 2026
11 checks passed
@antfu
antfu deleted the fix/loopback-origin-dns-rebinding branch September 1, 2026 02:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants