{"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":"exception","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/03255a9237b6eb32710f6bc4f2de9a675b99fe36/credentials/google/gcp_service_account_identity_credentials.go#L112-L148","documentation":"In GetRequestMetadata the credential calls CheckSecurityLevel(ri.AuthInfo, PrivacyAndIntegrity) and wraps any failure (gcp_service_account_identity_credentials.go:129-131). Because RequireTransportSecurity() returns true, sending the bearer token over anything below TLS/ALTS is refused. The inner %v is typically the [165] 'requires SecurityLevel' message.","triggerScenarios":"Dialing a gRPC server with grpc.WithTransportCredentials(insecure.NewCredentials()) (or an integrity-only transport) while attaching these per-RPC credentials. The first RPC triggers GetRequestMetadata, which sees NoSecurity AuthInfo and aborts before sending the token.","commonSituations":"Dev/staging channel configured plaintext 'just to test', forgetting to flip insecure→TLS when promoting; a custom transport whose AuthInfo.SecurityLevel defaults to zero; or a misconfigured ALTS bundle that fell back to plaintext.","solutions":["Use TLS or ALTS transport credentials on the channel so the connection is PrivacyAndIntegrity.","Do not attach these credentials to an insecure dial; drop grpc.WithPerRPCCredentials for plaintext testing.","Verify RequireTransportSecurity()==true is honored by your channel construction (gRPC refuses insecure+secure-creds only if you let it).","Confirm the transport credentials' AuthInfo sets SecurityLevel correctly (see [165])."],"exampleFix":"// before\nconn, _ := grpc.NewClient(addr,\n    grpc.WithTransportCredentials(insecure.NewCredentials()),\n    grpc.WithPerRPCCreds(grpcCredentials), // triggers insecure-connection error\n)\n\n// after\nconn, _ := grpc.NewClient(addr,\n    grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{ServerName: addr})),\n    grpc.WithPerRPCCreds(grpcCredentials),\n)","handlingStrategy":"validation","validationCode":"// Ensure the channel is TLS/ALTS before attaching these creds.\nif rpcCreds.RequireTransportSecurity() && transportIsInsecure(dialOpts) {\n    return errors.New(\"cannot use secure per-RPC creds over insecure transport\")\n}","typeGuard":null,"tryCatchPattern":"md, err := rpcCreds.GetRequestMetadata(ctx)\nif err != nil && strings.Contains(err.Error(), \"insecure connection\") {\n    // configuration error: switch the channel to TLS/ALTS.\n    return nil, fmt.Errorf(\"channel transport must be secured: %w\", err)\n}","preventionTips":["Always use TLS or ALTS transport credentials when per-RPC creds require security.","Do not attach RequireTransportSecurity()=true creds to an insecure dev channel.","Promote insecure dev channels to TLS before adding per-RPC credentials.","Verify AuthInfo.SecurityLevel is PrivacyAndIntegrity."],"tags":["grpc","credentials","transport-security","configuration","security"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}