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.

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.

Appendix: 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)