{"record":{"id":"48dbd8e633153202","repo":"grpc/grpc-go","slug":"unable-to-transfer-sts-perrpccredentials-v","errorCode":null,"errorMessage":"unable to transfer STS PerRPCCredentials: %v","messagePattern":"unable to transfer STS PerRPCCredentials: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"credentials/sts/sts.go","lineNumber":155,"sourceCode":"// callCreds provides the implementation of call credentials based on an STS\n// token exchange.\ntype callCreds struct {\n\topts   Options\n\tclient httpDoer\n\n\t// Cached accessToken to avoid an STS token exchange for every call to\n\t// GetRequestMetadata.\n\tmu            sync.Mutex\n\ttokenMetadata map[string]string\n\ttokenExpiry   time.Time\n}\n\n// GetRequestMetadata returns the cached accessToken, if available and valid, or\n// fetches a new one by performing an STS token exchange.\nfunc (c *callCreds) 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(\"unable to transfer STS PerRPCCredentials: %v\", err)\n\t}\n\n\t// Holding the lock for the whole duration of the STS request and response\n\t// processing ensures that concurrent RPCs don't end up in multiple\n\t// requests being made.\n\tc.mu.Lock()\n\tdefer c.mu.Unlock()\n\n\tif md := c.cachedMetadata(); md != nil {\n\t\treturn md, nil\n\t}\n\treq, err := constructRequest(ctx, c.opts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\trespBody, err := sendRequest(c.client, req)\n\tif err != nil {\n\t\treturn nil, err","sourceCodeStart":137,"sourceCodeEnd":173,"githubUrl":"https://github.com/grpc/grpc-go/blob/03255a9237b6eb32710f6bc4f2de9a675b99fe36/credentials/sts/sts.go#L137-L173","documentation":"Thrown from callCreds.GetRequestMetadata in sts/sts.go:155 when CheckSecurityLevel fails. STS-exchanged tokens are bearer credentials, so gRPC refuses to attach them on a connection below PrivacyAndIntegrity. RequireTransportSecurity() returns true, and the STS HTTP exchange itself also uses TLS to the token endpoint.","triggerScenarios":"Using sts.NewCredentials(opts) as per-RPC credentials on a channel created with insecure.NewCredentials() or with no transport credentials.","commonSituations":"Workload-identity / token-broker setups wired onto a plaintext dev channel; dropping TLS during debugging; xDS bootstrap pointing STS at the gRPC channel but the channel itself is insecure.","solutions":["Provide TLS transport credentials on the same dial as the STS per-RPC credentials.","Use alts credentials on Google infrastructure to satisfy the security-level check.","Remove the STS per-RPC credentials if the channel is intentionally plaintext."],"exampleFix":"// before\nstsCreds, _ := sts.NewCredentials(opts)\nconn, _ := grpc.NewClient(addr,\n    grpc.WithTransportCredentials(insecure.NewCredentials()),\n    grpc.WithPerRPCCredentials(stsCreds),\n)\n\n// after\nstsCreds, _ := sts.NewCredentials(opts)\nconn, _ := grpc.NewClient(addr,\n    grpc.WithTransportCredentials(credentials.NewClientTLSFromCert(caPool, \"\")),\n    grpc.WithPerRPCCredentials(stsCreds),\n)","handlingStrategy":"validation","validationCode":"stsCreds, err := sts.NewCredentials(opts)\nif err != nil { return err }\ntlsCreds := credentials.NewClientTLSFromCert(caPool, \"\")\nconn, err := grpc.NewClient(addr,\n    grpc.WithTransportCredentials(tlsCreds),\n    grpc.WithPerRPCCredentials(stsCreds),\n)","typeGuard":null,"tryCatchPattern":"if st, ok := status.FromError(err); ok && st.Code() == codes.Unavailable {\n    if strings.Contains(st.Message(), \"STS PerRPCCredentials\") {\n        // channel is insecure; re-dial with TLS\n    }\n}","preventionTips":["STS-exchanged tokens are bearer secrets: TLS-only.","Validate sts.Options at bootstrap so construction errors surface before dial.","Use alts on Google Cloud to satisfy the security-level requirement without certs."],"tags":["sts","security","transport","call-credentials","go","oauth"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}