Skip to content

Web and desktop clients cannot authenticate with Unicode server credentials #46224

Open

Description

@sdivyanshu90

Description

The web and desktop clients pass username:password directly to btoa() when creating a Basic-auth token. btoa() accepts only binary strings, so credentials containing characters outside the one-byte range throw InvalidCharacterError. Characters such as ä do not throw, but are encoded as a single byte instead of UTF-8, so the server decodes a different password and rejects authentication.

This affects server health checks, protocol detection, API clients, and legacy terminal authentication because they share the same credential encoder. The server already generates and decodes Basic-auth credentials as UTF-8. The app should convert the credential string to UTF-8 bytes before applying standard Base64 encoding.

Plugins

N/A

OpenCode version

N/A (reproduced in the shared client authentication helper)

Steps to reproduce

  1. Start an OpenCode server with OPENCODE_SERVER_PASSWORD='秘密🔐'.
  2. Open the web or desktop client and add that server.
  3. Enter the same password and attempt to connect.
  4. Observe that encoding throws InvalidCharacterError and the connection cannot be established.

A second case is päss: encoding does not throw, but the server receives invalid UTF-8 and authentication fails.

Expected behavior: Unicode usernames and passwords should be encoded as UTF-8 bytes before Base64 encoding and should authenticate successfully.

Screenshot and/or share link

Not provided; the credential helper provides a deterministic reproduction.

Operating System

All

Terminal

Web and desktop clients

Activity

  1. sdivyanshu90 commented on Sep 19, 2026

    @sdivyanshu90
    ContributorAuthor

    Could you please review this issue?

  2. opencodejr commented on Oct 28, 2026

    @opencodejr
    Contributor

    Thanks for the clear report. I reproduced this on v2.2.0, the latest release, since no version was given. It still happens on the latest dev (06d3c060).

    I started opencode serve with OPENCODE_SERVER_PASSWORD set to päss, 秘密🔐 and secret. Then I called /api/info with a token from the app's encoder (btoa("opencode:" + password)) and with a UTF-8 token:

    • päss: the app's token gets 401; the UTF-8 token gets 200.
    • 秘密🔐: the app's encoder throws InvalidCharacterError: The string contains invalid characters.; the UTF-8 token gets 200.
    • secret: both get 200.

    The server expects UTF-8 credentials, so the client-side encoder in packages/app/src/runtime/server/api.ts (authTokenFromCredentials) is the mismatch. The script below reproduces it. Until it's fixed, an ASCII-only server password avoids the problem.

    Reproduction script (repro.sh)
    #!/usr/bin/env bash
    # Reproduces anomalyco/opencode#46224: the web/desktop app's Basic-auth encoder
    # (authTokenFromCredentials in packages/app/src/runtime/server/api.ts) uses btoa()
    # directly, so Unicode server passwords either throw or are sent as Latin-1.
    # Usage: OPENCODE="opencode" ./repro.sh   (defaults to `opencode` on PATH)
    set -u
    OPENCODE=${OPENCODE:-opencode}
    WORK=$(mktemp -d)
    cd "$WORK"
    
    start() { # port password
      # separate data dirs so the three servers don't race on the same database
      mkdir -p "$WORK/h$1"
      XDG_DATA_HOME="$WORK/h$1/data" XDG_STATE_HOME="$WORK/h$1/state" XDG_CONFIG_HOME="$WORK/h$1/config" XDG_CACHE_HOME="$WORK/h$1/cache" \
      OPENCODE_SERVER_PASSWORD="$2" $OPENCODE serve --hostname 127.0.0.1 --port "$1" >"$WORK/s$1.log" 2>&1 &
      echo $!
    }
    P1=$(start 47101 'päss')
    P2=$(start 47102 '秘密🔐')
    P3=$(start 47103 'secret')
    trap 'kill $P1 $P2 $P3 2>/dev/null' EXIT
    for p in 47101 47102 47103; do
      for i in $(seq 60); do curl -s -o /dev/null "http://127.0.0.1:$p/" && break; sleep 0.5; done
    done
    
    cat >"$WORK/check.ts" <<'EOF'
    // Same encoder as the app's authTokenFromCredentials (v2.2.0 and dev)
    const appToken = (password: string) => btoa(`opencode:${password}`)
    const utf8Token = (password: string) => Buffer.from(`opencode:${password}`, "utf8").toString("base64")
    
    async function probe(port: number, password: string) {
      const url = `http://127.0.0.1:${port}/api/info`
      let app: string
      try {
        const r = await fetch(url, { headers: { authorization: `Basic ${appToken(password)}` } })
        app = `HTTP ${r.status}`
      } catch (e: any) {
        app = `throws ${e?.name}: ${e?.message}`
      }
      const u = await fetch(url, { headers: { authorization: `Basic ${utf8Token(password)}` } })
      console.log(`password ${JSON.stringify(password)}: app encoder -> ${app}; UTF-8 encoder -> HTTP ${u.status}`)
    }
    await probe(47101, "päss")
    await probe(47102, "秘密🔐")
    await probe(47103, "secret")
    EOF
    bun "$WORK/check.ts"

    Run it: bash repro.sh

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions