chenhg5/cc-connect · error

unauthorized

Error message

unauthorized

What it means

The bridge WebSocket endpoint rejects HTTP requests whose authentication check fails. `handleWS` calls `bs.authenticate(r)` before upgrading the connection; on failure it responds 401 with the plain body "unauthorized" and never upgrades. This protects the local bridge from unauthenticated clients connecting to the agent.

Source

Thrown at core/bridge.go:737

	// No CORS configured - require same-host (origin must match host)
	host := r.Host
	if host == "" {
		host = r.URL.Host
	}
	// Parse origin to get host
	if idx := strings.Index(origin, "://"); idx > 0 {
		originHost := origin[idx+3:]
		if originHost == host {
			return true
		}
	}
	slog.Warn("bridge: websocket origin mismatch", "origin", origin, "host", host)
	return false
}

func (bs *BridgeServer) handleWS(w http.ResponseWriter, r *http.Request) {
	if !bs.authenticate(r) {
		http.Error(w, "unauthorized", http.StatusUnauthorized)
		return
	}

	// Use a custom upgrader with origin checking
	upgrader := websocket.Upgrader{
		CheckOrigin: bs.checkOrigin,
	}

	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		slog.Error("bridge: websocket upgrade failed", "error", err)
		return
	}

	slog.Info("bridge: new connection", "remote", conn.RemoteAddr())
	bs.handleConnection(conn)
}

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Set the exact bridge token/secret configured on the BridgeServer in your client's Authorization header (or token query parameter, per authenticate()'s expected location).
  2. Re-read the token from config.toml after any rotation and restart the client.
  3. If behind a proxy, ensure the Authorization header is forwarded (e.g. proxy_set_header Authorization $http_authorization).
  4. Enable debug logs on the bridge to see which auth path failed (missing vs mismatched credential).

Example fix

// before
ws := websocket.Dialer{}
conn, _, err := ws.Dial("ws://127.0.0.1:8848/ws", nil)
// after
hdr := http.Header{"Authorization": []string{"Bearer " + cfg.BridgeToken}}
conn, resp, err := ws.Dial("ws://127.0.0.1:8848/ws", hdr)
if resp != nil && resp.StatusCode == http.StatusUnauthorized {
    log.Fatal("bridge token mismatch — update BridgeToken")
}
Defensive patterns

Strategy: validation

Validate before calling

func wsURL(u string, token string) error {
    if u == "" || token == "" { return errors.New("bridge url and token required") }
    parsed, err := url.Parse(u)
    if err != nil || (parsed.Scheme != "ws" && parsed.Scheme != "wss") { return errors.New("invalid ws url") }
    return nil
}

Type guard

func isUnauthorized(resp *http.Response) bool { return resp != nil && resp.StatusCode == http.StatusUnauthorized }

Try / catch

conn, resp, err := dialer.Dial(url, authHeader)
if err != nil {
    if resp != nil && resp.StatusCode == 401 {
        return fmt.Errorf("bridge auth failed: reload BridgeToken")
    }
    return err
}

Prevention

When it happens

Trigger: Opening a WebSocket to the bridge server without the required auth credential, with a wrong or malformed token (e.g. missing Authorization header or query token), or with a token that does not match the configured bridge secret.

Common situations: Client configured with an outdated token after the bridge secret rotated; client omitting the token entirely; reverse proxy stripping the Authorization header; connecting a second tool to the bridge with a guessed/default token.

Understand the failure class

Related errors


AI-assisted analysis of chenhg5/cc-connect@4000b2338a (2026-09-06). Data as JSON: /api/errors/3090a7254ff4fa46. Report an issue: GitHub.