Skip to content

fix(bridge): bind both loopback addresses (IPv4 + IPv6), never the wildcard - #15

Merged
thomhoffer-arch merged 1 commit into
mainfrom
claude/determined-euler-r1qtui
Jun 18, 2026
Merged

fix(bridge): bind both loopback addresses (IPv4 + IPv6), never the wildcard#15
thomhoffer-arch merged 1 commit into
mainfrom
claude/determined-euler-r1qtui

Conversation

@thomhoffer-arch

Copy link
Copy Markdown
Collaborator

Problem

The WebSocket bridge (19802) and REST API (19803) bound only to 127.0.0.1. On hosts where localhost resolves to [::1] first — common on Windows depending on DNS/hosts config — clients connecting over IPv6 loopback get an immediate ECONNREFUSED. A client-side fallback (try 127.0.0.1[::1]) papers over it, but the clean fix is server-side.

Fix

Bind two explicit loopback listeners per service — 127.0.0.1 (IPv4) and [::1] (IPv6) — so the bridge is reachable over both loopback stacks.

Deliberately not the 0.0.0.0 / :: wildcard: that would expose the unauthenticated bridge on every network interface (LAN / Wi-Fi / VPN), where anyone on the network could invoke ClashControl tools. The bridge is a local desktop tool and stays strictly localhost-only.

If one address family is unavailable (e.g. IPv6 disabled), the bind error is logged and the other listener keeps serving — no crash.

Applied across all three entrypoints:

  • index.js (MCP transport — WS)
  • rest-bridge.js (REST transport — WS + HTTP)
  • smart-bridge.js (combined — WS + HTTP)

WebSocket handling and the HTTP request handler were factored into shared helpers so both listeners reuse identical logic. The EADDRINUSE → connect-as-client fallback is guarded so it only fires once across the two listeners.

Verification

node --check passes on all three files. Ran rest-bridge.js locally:

  • http://127.0.0.1:19803/status200
  • IPv6 was disabled in the test sandbox (EAFNOSUPPORT on ::1); the bind failure was logged and the process kept serving on IPv4 — confirming graceful degradation.

Note

⚠️ This repo is marked superseded (frozen at v0.2.3) by its own README; active Smart Bridge code lives in the main ClashControl repo (smart-bridge-server.js + mcp-server.js). This same fix should be mirrored there — that's where production binaries are built. I couldn't reach that repo from this session's scope.

🤖 Generated with Claude Code

https://claude.ai/code/session_01FvB22Jk754Zw5EVApWCr2a


Generated by Claude Code

…ldcard

The WebSocket (19802) and REST (19803) servers bound only to 127.0.0.1.
On hosts where `localhost` resolves to [::1] first (common on Windows,
depending on DNS/hosts config), clients hit immediate ECONNREFUSED.

Bind two explicit loopback listeners per service — 127.0.0.1 and [::1] —
so the bridge is reachable over both IPv4 and IPv6 loopback. Deliberately
avoid the 0.0.0.0 / :: wildcard, which would expose the unauthenticated
bridge on every network interface (LAN/Wi-Fi/VPN); the bridge stays
strictly localhost-only.

If one address family is unavailable (e.g. IPv6 disabled), the bind error
is logged and the other listener keeps serving. Applies to all three
entrypoints (index.js, rest-bridge.js, smart-bridge.js).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FvB22Jk754Zw5EVApWCr2a
@thomhoffer-arch
thomhoffer-arch marked this pull request as ready for review June 18, 2026 09:34
@thomhoffer-arch
thomhoffer-arch merged commit 9915d27 into main Jun 18, 2026
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.

2 participants