t8y2/dbx · error

ZooKeeper sent an unexpected token after DIGEST-MD5 completi

Error message

ZooKeeper sent an unexpected token after DIGEST-MD5 completion

What it means

negotiateSASLDigest runs the DIGEST-MD5 challenge rounds. Once the SASL client reports Complete(), the protocol expects the server to stop issuing challenges; if another non-empty token arrives after completion, the driver aborts with 'ZooKeeper sent an unexpected token after DIGEST-MD5 completion' because the handshake deviates from RFC 2831/the ZooKeeper SASL exchange.

Source

Thrown at agents/drivers/zookeeper/sasl.go:101

		timeout = defaultConnectionTimeout
	}
	if err := connection.SetDeadline(time.Now().Add(timeout)); err != nil {
		return err
	}
	defer connection.SetDeadline(time.Time{})

	token, err := saslClient.Start()
	if err != nil {
		return fmt.Errorf("start ZooKeeper DIGEST-MD5 negotiation: %w", err)
	}
	for round := 0; round < zooKeeperSASLMaxRounds; round++ {
		challenge, err := zooKeeperSASLRound(connection, zooKeeperSASLXIDBase+int32(round), token)
		if err != nil {
			return fmt.Errorf("ZooKeeper SASL round %d: %w", round+1, err)
		}
		if saslClient.Complete() {
			if len(challenge) != 0 {
				return errors.New("ZooKeeper sent an unexpected token after DIGEST-MD5 completion")
			}
			return nil
		}
		token, err = saslClient.Step(challenge)
		if err != nil {
			return fmt.Errorf("continue ZooKeeper DIGEST-MD5 negotiation at round %d: %w", round+1, err)
		}
		if saslClient.Complete() {
			if len(token) != 0 {
				return errors.New("ZooKeeper DIGEST-MD5 completed with an unexpected client token")
			}
			return nil
		}
	}
	return fmt.Errorf("ZooKeeper DIGEST-MD5 negotiation exceeded %d rounds", zooKeeperSASLMaxRounds)
}

func zooKeeperSASLRound(connection net.Conn, xid int32, token []byte) ([]byte, error) {

View on GitHub (pinned to c0390bff16)

Solutions

  1. Check the ZooKeeper server version and SASL configuration (jaas.conf, digest-md5 settings) for known quirks; upgrade to a version with correct DIGEST-MD5 termination.
  2. Remove/inspect any proxy between client and ensemble that could inject extra frames.
  3. Verify the negotiated protection quality (qop) matches on both sides — enable/auth/integrity mismatches can produce extra tokens.
  4. Capture the handshake (tcpdump/Wireshark on port 2181) and compare token counts against a known-good zkCli SASL session.

Example fix

// server jaas.conf before
// ZooKeeper { org.apache.zookeeper.server.auth.DigestLoginModule required ... user_super="pw"; } // missing/broken entry causes odd SASL exchange
// after
ZooKeeper { org.apache.zookeeper.server.auth.DigestLoginModule required user_svc="correct-pw"; };
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-flight SASL sanity: confirm server advertises digest-md5 and credentials exist
// zkCli.sh with matching jaas.conf should authenticate without errors before app start

Try / catch

err := authenticateSASLDigest(conn, creds)
if err != nil {
    if strings.Contains(err.Error(), "unexpected token after DIGEST-MD5 completion") {
        // server/proxy misbehaving; log details and fall back or fail with context
        return fmt.Errorf("server sent extra SASL token; check ZooKeeper version/jaas.conf and any proxy: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: During authenticateSASLDigest, after saslClient.Complete() is true, zooKeeperSASLRound still returns a non-empty challenge — the server sent an extra token after the final client response. Raised in agents/drivers/zookeeper/sasl.go:101.

Common situations: Server-side SASL implementation or version behaving non-strictly (extra post-completion token); a proxy/middleware in front of ZooKeeper injecting an extra frame; server misconfigured with layered security (qop) producing an additional challenge; version mismatch between client library expectations and server SASL behavior.

Understand the failure class

Related errors


AI-assisted analysis of t8y2/dbx@c0390bff16 (2026-09-05). Data as JSON: /api/errors/8cd2c75442f54644. Report an issue: GitHub.