{"record":{"id":"644c014078564dbe","repo":"grpc/grpc-go","slug":"credentials-cannot-send-secure-credentials-on-an","errorCode":null,"errorMessage":"credentials: cannot send secure credentials on an insecure connection: %v","messagePattern":"credentials: cannot send secure credentials on an insecure connection: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"credentials/google/gcp_service_account_identity_credentials.go","lineNumber":130,"sourceCode":"\t\tctx:      ctx,\n\t\taudience: audience,\n\t\tcreds:    creds,\n\t\tbackoff:  internal.BackoffStrategy,\n\t}, nil\n}\n\n// GetRequestMetadata gets the current request metadata, refreshing tokens if\n// required. This implementation follows the PerRPCCredentials interface.\n//\n// It guarantees that only one underlying token fetch will be executed\n// concurrently. If a valid token is cached, it is returned immediately. If\n// a fetch recently failed, the cached error is returned until the backoff\n// interval expires. Otherwise, it initiates a new token fetch or blocks\n// waiting for an already-in-progress fetch to complete.\nfunc (c *gcpServiceAccountIdentityCallCreds) GetRequestMetadata(ctx context.Context, _ ...string) (map[string]string, error) {\n\tri, _ := credentials.RequestInfoFromContext(ctx)\n\tif err := credentials.CheckSecurityLevel(ri.AuthInfo, credentials.PrivacyAndIntegrity); err != nil {\n\t\treturn nil, fmt.Errorf(\"credentials: cannot send secure credentials on an insecure connection: %v\", err)\n\t}\n\n\tif md, err := c.cachedRequestMetadata(true); md != nil || err != nil {\n\t\treturn md, err\n\t}\n\n\tc.mu.Lock()\n\t// Now that we have the lock, did someone else finish the fetch while we\n\t// were waiting for the lock?\n\tmd, err := c.cachedRequestMetadataLocked(false)\n\tif md != nil || err != nil {\n\t\tc.mu.Unlock()\n\t\treturn md, err\n\t}\n\n\t// If no one is fetching, start it.\n\tif c.fetching == nil {\n\t\tc.fetching = make(chan struct{})","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/grpc/grpc-go/blob/0c51461d27177d997e14c642fe18c11668fc09a3/credentials/google/gcp_service_account_identity_credentials.go#L112-L148","documentation":"Returned from gcpServiceAccountIdentityCallCreds.GetRequestMetadata when credentials.CheckSecurityLevel reports the connection has not reached PrivacyAndIntegrity. The credential carries a bearer JWT, so gRPC refuses to attach it to a plaintext/insecure channel. The %v holds the underlying security-level check error.","triggerScenarios":"Dialing the server with grpc.WithInsecure() or grpc.WithTransportCredentials(insecure.NewCredentials()) while using NewServiceAccountIdentityCredentials as the per-RPC credential; using a credentials.Bundle whose transport credentials negotiate a level below PrivacyAndIntegrity.","commonSituations":"Local development with TLS disabled for convenience; misconfigured ALTS-only bundle falling back to a plaintext transport; proxy or load balancer terminating TLS and forwarding plaintext to the backend where the credential runs.","solutions":["Dial with credentials.NewTLS(nil) or a bundle that guarantees TLS (e.g. google.NewDefaultCredentials()).","For local testing, use a self-signed TLS transport credential instead of insecure.NewCredentials().","Ensure RequireTransportSecurity() returning true is honored: do not override the bundle to skip transport security.","If behind a TLS-terminating proxy, move the per-RPC credential to the hop that actually has a secure transport."],"exampleFix":"// before\nconn, _ := grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithPerRPCCredentials(creds))\n// after\nconn, _ := grpc.Dial(addr, grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{})), grpc.WithPerRPCCredentials(creds))","handlingStrategy":"validation","validationCode":"// Ensure the dial uses TLS before attaching the credential.\nfunc secureDialOpts() grpc.DialOption {\n    return grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{}))\n}\n// Use secureDialOpt() instead of grpc.WithTransportCredentials(insecure.NewCredentials()).","typeGuard":null,"tryCatchPattern":"// GetRequestMetadata errors are returned from the RPC as status; surface them:\nif status.Code(err) == codes.Unauthenticated || status.Code(err) == codes.FailedPrecondition {\n    log.Fatal(\"secure credential refused insecure transport; switch the dial to TLS\")\n}","preventionTips":["Never pair these credentials with insecure.NewCredentials().","Add a unit test asserting RequireTransportSecurity()==true is reflected by the dial.","Use a self-signed TLS credential for local development."],"tags":["grpc","tls","security","credentials","authentication"],"backgroundTag":null,"analyzedSha":"0c51461d27177d997e14c642fe18c11668fc09a3","analyzedAt":"2026-08-11T14:49:15.055Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}