{"record":{"id":"7f479b9ca2361c2b","repo":"grpc/grpc-go","slug":"rbac-permission-header-matcher-for-v-is-scheme","errorCode":null,"errorMessage":"rbac: permission header matcher for %v is :scheme or starts with grpc","messagePattern":"rbac: permission header matcher for (.+?) is :scheme or starts with grpc","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/xds/httpfilter/rbac/rbac.go","lineNumber":81,"sourceCode":"\t\tif policy.Condition != nil {\n\t\t\treturn nil, errors.New(\"rbac: Policy.condition is present\")\n\t\t}\n\t\tif policy.CheckedCondition != nil {\n\t\t\treturn nil, errors.New(\"rbac: policy.CheckedCondition is present\")\n\t\t}\n\n\t\t// \"It is also a validation failure if Permission or Principal has a\n\t\t// header matcher for a grpc- prefixed header name or :scheme.\" - A41\n\t\tfor _, principal := range policy.Principals {\n\t\t\tname := principal.GetHeader().GetName()\n\t\t\tif name == \":scheme\" || strings.HasPrefix(name, \"grpc-\") {\n\t\t\t\treturn nil, fmt.Errorf(\"rbac: principal header matcher for %v is :scheme or starts with grpc\", name)\n\t\t\t}\n\t\t}\n\t\tfor _, permission := range policy.Permissions {\n\t\t\tname := permission.GetHeader().GetName()\n\t\t\tif name == \":scheme\" || strings.HasPrefix(name, \"grpc-\") {\n\t\t\t\treturn nil, fmt.Errorf(\"rbac: permission header matcher for %v is :scheme or starts with grpc\", name)\n\t\t\t}\n\t\t}\n\t}\n\n\t// \"Envoy aliases :authority and Host in its header map implementation, so\n\t// they should be treated equivalent for the RBAC matchers; there must be no\n\t// behavior change depending on which of the two header names is used in the\n\t// RBAC policy.\" - A41. Loop through config's principals and policies, change\n\t// any header matcher with value \"host\" to :authority\", as that is what\n\t// grpc-go shifts both headers to in transport layer.\n\tfor _, policy := range rbacCfg.GetRules().GetPolicies() {\n\t\tfor _, principal := range policy.Principals {\n\t\t\tif principal.GetHeader().GetName() == \"host\" {\n\t\t\t\tprincipal.GetHeader().Name = \":authority\"\n\t\t\t}\n\t\t}\n\t\tfor _, permission := range policy.Permissions {\n\t\t\tif permission.GetHeader().GetName() == \"host\" {","sourceCodeStart":63,"sourceCodeEnd":99,"githubUrl":"https://github.com/grpc/grpc-go/blob/03255a9237b6eb32710f6bc4f2de9a675b99fe36/internal/xds/httpfilter/rbac/rbac.go#L63-L99","documentation":"Raised by the RBAC HTTP filter's config validation (gRFC A41). A policy Permission is rejected if it contains a header matcher whose name is the \":scheme\" pseudo-header or starts with \"grpc-\", because those headers are transport-managed in gRPC and cannot be meaningfully matched. The check happens in parseConfig while iterating policy.Permissions.","triggerScenarios":"An xDS RBAC policy delivered via LDS whose Permission list contains a header matcher with name \":scheme\" or any name beginning with \"grpc-\" (e.g. grpc-status, grpc-encoding, grpc-trace-bin).","commonSituations":"Porting an Envoy HTTP RBAC rule verbatim into a gRPC xDS setup; Istio/Envoy authoring rules that attempt to match on gRPC reserved metadata; control-plane upgrade that emits previously-tolerated matchers.","solutions":["Remove the offending header matcher from the Permission in the RBAC policy.","If you need to gate on gRPC metadata, use a Principal-based metadata matcher or a non-header matcher (path, method, destination_ip) instead of matching \":scheme\" or grpc-* headers.","Redeploy the control-plane LDS resource and confirm the new config reaches the server."],"exampleFix":"// before (Envoy RBAC policy fragment)\n// permissions:\n//   - header:\n//       name: grpc-status\n//       safe_regex_match: { regex: \".*\" }\n//\n// after\n// permissions:\n//   - url_header:\n//       name: :path\n//       safe_regex_match: { regex: \"/pkg.Service/Method\" }","handlingStrategy":"validation","validationCode":"// Validate an RBAC policy before sending it from a control plane.\nfunc validateRBACPolicy(rbac *rbacpb.RBAC) error {\n\tfor _, policy := range rbac.GetRules().GetPolicies() {\n\t\tfor _, p := range policy.Permissions {\n\t\t\tname := p.GetHeader().GetName()\n\t\t\tif name == \":scheme\" || strings.HasPrefix(name, \"grpc-\") {\n\t\t\t\treturn fmt.Errorf(\"permission header matcher %q is reserved\", name)\n\t\t\t}\n\t\t}\n\t\tfor _, pr := range policy.Principals {\n\t\t\tname := pr.GetHeader().GetName()\n\t\t\tif name == \":scheme\" || strings.HasPrefix(name, \"grpc-\") {\n\t\t\t\treturn fmt.Errorf(\"principal header matcher %q is reserved\", name)\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Lint RBAC policies on the control plane before serving them (reject \":scheme\" and grpc-* header matchers).","Do not port Envoy HTTP RBAC rules into gRPC verbatim; re-audit header matcher names."],"tags":["rbac","xds","grpc","envoy","config-validation","security"],"analyzedSha":"03255a9237b6eb32710f6bc4f2de9a675b99fe36","analyzedAt":"2026-08-07T00:29:34.215Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}