{"record":{"id":"7fd28dde71cdc937","repo":"grpc/grpc-go","slug":"rbac-header-matcher-for-q-starts-with-q","errorCode":null,"errorMessage":"rbac: header matcher for %q starts with %q","messagePattern":"rbac: header matcher for %q starts with %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/xds/httpfilter/rbac/rbac.go","lineNumber":177,"sourceCode":"\t\t\tif err := normalizePrincipalHeaders(id); err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\tcase *v3rbacpb.Principal_NotId:\n\t\treturn normalizePrincipalHeaders(p.NotId)\n\t}\n\treturn nil\n}\n\n// normalizeHeaderMatcher rejects header matchers that A41 forbids (:scheme or a\n// grpc- prefixed name) and rewrites a \"host\" matcher to \":authority\".\nfunc normalizeHeaderMatcher(header *v3routepb.HeaderMatcher) error {\n\tname := header.GetName()\n\tif name == \":scheme\" {\n\t\treturn fmt.Errorf(\"rbac: header matcher for %q is %q\", name, \":scheme\")\n\t}\n\tif strings.HasPrefix(name, \"grpc-\") {\n\t\treturn fmt.Errorf(\"rbac: header matcher for %q starts with %q\", name, \"grpc-\")\n\t}\n\tif name == \"host\" {\n\t\theader.Name = \":authority\"\n\t}\n\treturn nil\n}\n\nfunc (builder) ParseFilterConfig(cfg proto.Message) (httpfilter.FilterConfig, error) {\n\tif cfg == nil {\n\t\treturn nil, fmt.Errorf(\"rbac: nil configuration message provided\")\n\t}\n\tm, ok := cfg.(*anypb.Any)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"rbac: error parsing config %v: unknown type %T\", cfg, cfg)\n\t}\n\tmsg := new(rpb.RBAC)\n\tif err := m.UnmarshalTo(msg); err != nil {\n\t\treturn nil, fmt.Errorf(\"rbac: error parsing config %v: %v\", cfg, err)","sourceCodeStart":159,"sourceCodeEnd":195,"githubUrl":"https://github.com/grpc/grpc-go/blob/0c51461d27177d997e14c642fe18c11668fc09a3/internal/xds/httpfilter/rbac/rbac.go#L159-L195","documentation":"normalizeHeaderMatcher (rbac.go:176) rejects a header matcher whose name starts with \"grpc-\". Per gRFC A41, these headers are reserved for the gRPC transport and must not be matched by RBAC policies.","triggerScenarios":"Any policy permission or principal (including nested and/or/not rules) contains a HeaderMatcher with a name beginning with \"grpc-\" (e.g., grpc-trace-bin, grpc-status, grpc-encoding).","commonSituations":"Operator copies an Envoy policy that filters on grpc-trace-bin; legacy RBAC config predating A41; policy generator that enumerates all known headers.","solutions":["Remove the grpc-* header matcher from the policy.","If you need to gate on tracing metadata, do it in the application or via a different filter.","Audit all HeaderMatcher entries for the grpc- prefix during policy authoring."],"exampleFix":"// before\npermissions: [{ rule: { header: { name: \"grpc-trace-bin\", present_match: true } } }]\n\n// after\n// (matcher removed)","handlingStrategy":"validation","validationCode":"func validateNoGrpcHeaders(p *v3rbacpb.Policy) error {\n    check := func(h *v3routepb.HeaderMatcher) error {\n        if strings.HasPrefix(h.GetName(), \"grpc-\") { return fmt.Errorf(\"grpc-* header %q forbidden by A41\", h.GetName()) }\n        return nil\n    }\n    /* walk principals+permissions as the filter does */\n    return nil\n}","typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"starts with\") {\n    // remove the grpc-* header matcher from the policy\n}","preventionTips":["Lint RBAC policies for the grpc- prefix before publishing.","Handle tracing/auth metadata outside RBAC."],"tags":["rbac","header-matcher","policy","xds","grpc"],"backgroundTag":null,"analyzedSha":"0c51461d27177d997e14c642fe18c11668fc09a3","analyzedAt":"2026-08-11T14:49:15.055Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}