wavetermdev/waveterm · error

error decoding jwt public key: %v

Error message

error decoding jwt public key: %v

What it means

Returned by serverRunRouter when base64.StdEncoding.DecodeString fails on the JWT public key returned by the connserver. The key must be standard base64; a malformed payload means the server returned corrupt or unexpected data. Startup aborts because tokens cannot be validated without a valid key.

Source

Thrown at cmd/wsh/cmd/wshcmd-connserver.go:255

	// setup the connserver rpc client first
	client, bareRouteId, err := setupConnServerRpcClientWithRouter(router, sockName)
	if err != nil {
		return fmt.Errorf("error setting up connserver rpc client: %v", err)
	}
	wshfs.RpcClient = client
	wshfs.RpcClientRouteId = bareRouteId

	log.Printf("trying to get JWT public key")

	// fetch and set JWT public key
	jwtPublicKeyB64, err := wshclient.GetJwtPublicKeyCommand(client, nil)
	if err != nil {
		return fmt.Errorf("error getting jwt public key: %v", err)
	}
	jwtPublicKeyBytes, err := base64.StdEncoding.DecodeString(jwtPublicKeyB64)
	if err != nil {
		return fmt.Errorf("error decoding jwt public key: %v", err)
	}
	err = wavejwt.SetPublicKey(jwtPublicKeyBytes)
	if err != nil {
		return fmt.Errorf("error setting jwt public key: %v", err)
	}

	log.Printf("got JWT public key")

	// now set up the domain socket
	unixListener, err := MakeRemoteUnixListener()
	if err != nil {
		return fmt.Errorf("cannot create unix listener: %v", err)
	}
	log.Printf("unix listener started")
	go func() {
		defer func() {
			panichandler.PanicHandler("serverRunRouter:runListener", recover())
		}()

View on GitHub (pinned to a4447c1563)

Solutions

  1. Check what the connserver actually returns for the JWT public key and fix its key material/source
  2. Ensure client and server wsh versions match (payload encoding contract changed between versions)
  3. Regenerate the server's JWT key pair so a clean base64 key is served
  4. As a defensive measure, tolerate whitespace/newlines by trimming or using base64.StdEncoding.DecodeString(strings.TrimSpace(...)) before failing

Example fix

// before
jwtPublicKeyBytes, err := base64.StdEncoding.DecodeString(jwtPublicKeyB64)
if err != nil {
    return fmt.Errorf("error decoding jwt public key: %v", err)
}
// after
jwtPublicKeyBytes, err := base64.StdEncoding.DecodeString(strings.TrimSpace(jwtPublicKeyB64))
if err != nil {
    return fmt.Errorf("error decoding jwt public key: %v", err)
}
Defensive patterns

Strategy: type-guard

Validate before calling

trimmed := strings.TrimSpace(jwtPublicKeyB64)
if trimmed == "" {
    return fmt.Errorf("connserver returned empty jwt public key")
}
if _, err := base64.StdEncoding.DecodeString(trimmed); err != nil {
    return fmt.Errorf("jwt public key is not valid std base64: %w", err)
}

Type guard

func isStdBase64(s string) bool {
    _, err := base64.StdEncoding.DecodeString(strings.TrimSpace(s))
    return err == nil && s != ""
}

Try / catch

jwtPublicKeyBytes, err := base64.StdEncoding.DecodeString(jwtPublicKeyB64)
if err != nil {
    // fallback: try raw-pem or url-safe encodings before giving up
    if pemBytes, _ := pem.Decode([]byte(jwtPublicKeyB64)); pemBytes != nil {
        jwtPublicKeyBytes = pemBytes.Bytes
    } else {
        return fmt.Errorf("error decoding jwt public key: %v", err)
    }
}

Prevention

When it happens

Trigger: The connserver returns a non-base64 or whitespace-padded string for jwtPublicKeyB64 — corrupted server state, a proxy mangling the response, or a client/server version mismatch changing the payload format (e.g. raw PEM instead of base64).

Common situations: Version skew where an older server sends a different encoding, manually edited or corrupted server key files, middleware/interceptors transforming the RPC payload, copy-pasted keys with newlines in test setups.

Related errors


AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01). Data as JSON: /api/errors/ec190964a09603a7. Report an issue: GitHub.