{"record":{"id":"cb874c98bab1d3d3","repo":"t8y2/dbx","slug":"zookeeper-sent-an-unexpected-token-after-gssapi-co-cb874c","errorCode":null,"errorMessage":"ZooKeeper sent an unexpected token after GSSAPI completion","messagePattern":"ZooKeeper sent an unexpected token after GSSAPI completion","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"agents/drivers/hive-go/zookeeper_protocol.go","lineNumber":220,"sourceCode":"\tfor round := 0; round < zooKeeperMaxSASLRounds; round++ {\n\t\tresponse, requestErr := client.request(zooKeeperOpSASL, func(encoder *zooKeeperEncoder) {\n\t\t\tif token == nil {\n\t\t\t\tencoder.bytes([]byte{})\n\t\t\t\treturn\n\t\t\t}\n\t\t\tencoder.bytes(token)\n\t\t})\n\t\tif requestErr != nil {\n\t\t\treturn fmt.Errorf(\"ZooKeeper SASL round %d: %w\", round+1, requestErr)\n\t\t}\n\t\tdecoder := newZooKeeperDecoder(response)\n\t\tchallenge, decodeErr := decoder.bytes()\n\t\tif decodeErr != nil {\n\t\t\treturn fmt.Errorf(\"decode ZooKeeper SASL round %d: %w\", round+1, decodeErr)\n\t\t}\n\t\tif saslClient.Complete() {\n\t\t\tif len(challenge) != 0 {\n\t\t\t\treturn errors.New(\"ZooKeeper sent an unexpected token after GSSAPI completion\")\n\t\t\t}\n\t\t\treturn nil\n\t\t}\n\t\ttoken, err = saslClient.Step(challenge)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"continue ZooKeeper GSSAPI negotiation at round %d: %w\", round+1, err)\n\t\t}\n\t}\n\treturn fmt.Errorf(\"ZooKeeper GSSAPI negotiation exceeded %d rounds\", zooKeeperMaxSASLRounds)\n}\n\nfunc (client *protocolZooKeeperClient) AddAuth(scheme string, auth []byte) error {\n\t_, err := client.request(zooKeeperOpSetAuth, func(encoder *zooKeeperEncoder) {\n\t\tencoder.int32(0)\n\t\tencoder.string(scheme)\n\t\tencoder.bytes(auth)\n\t})\n\treturn err","sourceCodeStart":202,"sourceCodeEnd":238,"githubUrl":"https://github.com/t8y2/dbx/blob/c0390bff16418b651f4728520d99adf8ce48829a/agents/drivers/hive-go/zookeeper_protocol.go#L202-L238","documentation":"During the ZooKeeper SASL/GSSAPI handshake the client tracks saslClient.Complete(). Once the client considers negotiation finished, the server should send an empty (zero-length) challenge as acknowledgment. A non-empty token after completion means the server is speaking an unexpected/extra SASL round or a different mechanism, so the driver aborts rather than ignoring the data.","triggerScenarios":"Server sends an additional SASL response frame after the client's GSSAPI negotiation reports Complete(); mechanism mismatch (server continuing DIGEST-MD5-style rounds while client uses GSSAPI); a misbehaving proxy inserting extra frames.","commonSituations":"ZooKeeper server with a SASL provider/version that emits a final token the client does not expect; custom SaslServer callback returning data on last round; mixed ensemble versions behind a load balancer.","solutions":["Verify client and server agree on the same SASL mechanism (GSSAPI/Kerberos) and versions are compatible","Check the server's SASL callback implementation for a stray final response token","Test against a single known-good ensemble member to rule out version-skew behind the proxy/ELB","Capture a wire trace of the SASL rounds to see what extra token is being sent"],"exampleFix":"// before\nif saslClient.Complete() {\n    if len(challenge) != 0 {\n        return errors.New(\"ZooKeeper sent an unexpected token after GSSAPI completion\")\n    }\n    return nil\n}\n// after (server-side fix: last round must return empty token)\nif isFinalRound {\n    return []byte{}, nil // emit empty challenge to acknowledge completion\n}","handlingStrategy":"retry","validationCode":"// Go: confirm server SASL provider is GSSAPI-compatible before connecting\n// e.g. assert ensemble supports Kerberos SASL (zoo.cfg: quorum.auth.provider or jaas GSSAPI)","typeGuard":null,"tryCatchPattern":"err := client.authenticateSASL(sasl)\nif err != nil && strings.Contains(err.Error(), \"unexpected token after GSSAPI completion\") {\n    if attempt < 2 {\n        return reconnectAndAuthenticate(ctx) // once; else surface config/bug\n    }\n    return fmt.Errorf(\"server sent extra SASL round; check mechanism mismatch: %w\", err)\n}","preventionTips":["Keep client and ensemble ZooKeeper versions aligned","Use a standard GSSAPI JaasServer/Client config, not custom SASL callbacks","Avoid proxies between client and ensemble that transform frames"],"tags":["zookeeper","sasl","gssapi","protocol"],"backgroundTag":"sasl-handshake-failed","analyzedSha":"c0390bff16418b651f4728520d99adf8ce48829a","analyzedAt":"2026-09-05T23:05:10.900Z","contentChangedAt":"2026-09-05T23:05:10.900Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}