{"record":{"id":"0a95b4133d97ce65","repo":"grpc/grpc-go","slug":"clienthandshake-is-not-supported-for-server-cred","errorCode":null,"errorMessage":"ClientHandshake() is not supported for server credentials","messagePattern":"ClientHandshake\\(\\) is not supported for server credentials","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"credentials/xds/xds.go","lineNumber":96,"sourceCode":"\n// credsImpl is an implementation of the credentials.TransportCredentials\n// interface which uses xDS APIs to fetch its security configuration.\ntype credsImpl struct {\n\tisClient bool\n\tfallback credentials.TransportCredentials\n}\n\n// ClientHandshake performs the TLS handshake on the client-side.\n//\n// It looks for the presence of a HandshakeInfo value in the passed in context\n// (added using a call to NewContextWithHandshakeInfo()), and retrieves identity\n// and root certificates from there. It also retrieves a list of acceptable SANs\n// and uses a custom verification function to validate the certificate presented\n// by the peer. It uses fallback credentials if no HandshakeInfo is present in\n// the passed in context.\nfunc (c *credsImpl) ClientHandshake(ctx context.Context, authority string, rawConn net.Conn) (net.Conn, credentials.AuthInfo, error) {\n\tif !c.isClient {\n\t\treturn nil, nil, errors.New(\"ClientHandshake() is not supported for server credentials\")\n\t}\n\n\t// The clusterimpl balancer constructs a new HandshakeInfo using a call to\n\t// NewHandshakeInfo(), and then adds it to the attributes field of the\n\t// resolver.Address when handling calls to NewSubConn(). The transport layer\n\t// takes care of shipping these attributes in the context to this handshake\n\t// function. We first read the credentials.ClientHandshakeInfo type from the\n\t// context, which contains the attributes added by the clusterimpl balancer.\n\t// We then read the HandshakeInfo from the attributes to get to the actual\n\t// data that we need here for the handshake.\n\tchi := credentials.ClientHandshakeInfoFromContext(ctx)\n\t// If there are no attributes in the received context or the attributes does\n\t// not contain a HandshakeInfo, it could either mean that the user did not\n\t// specify an `xds` scheme in their dial target or that the xDS server did\n\t// not provide any security configuration. In both of these cases, we use\n\t// the fallback credentials specified by the user.\n\tif chi.Attributes == nil {\n\t\treturn c.fallback.ClientHandshake(ctx, authority, rawConn)","sourceCodeStart":78,"sourceCodeEnd":114,"githubUrl":"https://github.com/grpc/grpc-go/blob/03255a9237b6eb32710f6bc4f2de9a675b99fe36/credentials/xds/xds.go#L78-L114","documentation":"Returned by credsImpl.ClientHandshake (credentials/xds/xds.go:96) when c.isClient is false. The xDS credsImpl is shared for both client and server and carries an isClient flag (set in NewClientCredentials/NewServerCredentials); calling the client-side handshake method on a server-credentials instance is a misuse, so it is rejected explicitly rather than attempting a wrong-role TLS handshake.","triggerScenarios":"Creating credentials with xds.NewServerCredentials(...) and then handing them to a gRPC client via grpc.WithTransportCredentials (or otherwise invoking ClientHandshake on server creds). The handshake is attempted on the first outbound RPC.","commonSituations":"Swapping client and server credential construction in a full-duplex service; sharing one creds variable across both roles; copy-paste between client and server setup code.","solutions":["Use xds.NewClientCredentials for client-side channels and xds.NewServerCredentials for servers — never interchange them.","Audit the code path: ensure the creds passed to grpc.Dial came from NewClientCredentials and those passed to grpc.NewServer came from NewServerCredentials.","Name credential variables by role (clientXdsCreds vs serverXdsCreds) to make the mix-up visually obvious."],"exampleFix":"// before\ncreds, _ := xds.NewServerCredentials(xds.ServerOptions{FallbackCreds: insecure.NewCredentials()})\nconn, _ := grpc.Dial(addr, grpc.WithTransportCredentials(creds)) // ClientHandshake fails\n\n// after\ncreds, _ := xds.NewClientCredentials(xds.ClientOptions{FallbackCreds: insecure.NewCredentials()})\nconn, _ := grpc.Dial(addr, grpc.WithTransportCredentials(creds))","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"// Ensure the creds came from the client constructor before dialing.\n// credsImpl is unexported, so guard by construction: tag your own wrapper.\ntype clientXDS struct{ credentials.TransportCredentials }\n\nfunc mustClientXDS(c credentials.TransportCredentials, isClient bool) clientXDS {\n    if !isClient { panic(\"server creds passed to client\") }\n    return clientXDS{c}\n}","tryCatchPattern":"// ClientHandshake errors are surfaced on the first RPC; detect at startup by\n// performing a no-op connectivity check, or simply keep client/server creds\n// in separately-named variables to prevent the mix-up.","preventionTips":["Name variables by role: clientXdsCreds vs serverXdsCreds.","Construct client and server creds in separate, clearly-labeled functions.","Never reuse one creds variable for both grpc.Dial and grpc.NewServer."],"tags":["go","grpc","security","xds","credentials","misuse"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}