grpc/grpc-go · error
rbac: header matcher for %q is %q
Error message
rbac: header matcher for %q is %q
What it means
normalizeHeaderMatcher (rbac.go:173) rejects a header matcher whose name is ":scheme". Per gRFC A41, RBAC policies must not match on the :scheme pseudo-header because grpc-go does not expose it as a mutable header.
Source
Thrown at internal/xds/httpfilter/rbac/rbac.go:174
}
case *v3rbacpb.Principal_OrIds:
for _, id := range p.OrIds.GetIds() {
if err := normalizePrincipalHeaders(id); err != nil {
return err
}
}
case *v3rbacpb.Principal_NotId:
return normalizePrincipalHeaders(p.NotId)
}
return nil
}
// normalizeHeaderMatcher rejects header matchers that A41 forbids (:scheme or a
// grpc- prefixed name) and rewrites a "host" matcher to ":authority".
func normalizeHeaderMatcher(header *v3routepb.HeaderMatcher) error {
name := header.GetName()
if name == ":scheme" {
return fmt.Errorf("rbac: header matcher for %q is %q", name, ":scheme")
}
if strings.HasPrefix(name, "grpc-") {
return fmt.Errorf("rbac: header matcher for %q starts with %q", name, "grpc-")
}
if name == "host" {
header.Name = ":authority"
}
return nil
}
func (builder) ParseFilterConfig(cfg proto.Message) (httpfilter.FilterConfig, error) {
if cfg == nil {
return nil, fmt.Errorf("rbac: nil configuration message provided")
}
m, ok := cfg.(*anypb.Any)
if !ok {
return nil, fmt.Errorf("rbac: error parsing config %v: unknown type %T", cfg, cfg)
}View on GitHub (pinned to 0c51461d27)
Solutions
- Remove the ":scheme" header matcher from the RBAC policy.
- If routing on scheme is required, handle it at the listener/LDS layer rather than in RBAC.
- Review the policy for any pseudo-header (:path, :method, :scheme, :authority) usage against A41 rules.
Example fix
// before
principals: [{ identifier: { header: { name: ":scheme", exact_match: "https" } } }]
// after
// (matcher removed; scheme handled at the listener layer) Defensive patterns
Strategy: validation
Validate before calling
func validatePolicyHeaders(p *v3rbacpb.Policy) error {
for _, pr := range p.GetPrincipals() {
if err := walkHeaders(pr, func(h *v3routepb.HeaderMatcher) error {
if h.GetName() == ":scheme" { return errors.New(":scheme forbidden by A41") }
return nil
}); err != nil { return err }
}
return nil
} Try / catch
if err != nil && strings.Contains(err.Error(), "header matcher for") {
// remove the :scheme matcher from the policy
} Prevention
- Run policy authoring lint that rejects :scheme and other pseudo-headers.
- Do not copy Envoy RBAC configs verbatim; audit them against gRFC A41.
When it happens
Trigger: Any policy permission or principal (including those nested inside and/or/not rules) contains a HeaderMatcher with name == ":scheme".
Common situations: Operator copies an Envoy RBAC config that matches on :scheme; policy templating that adds pseudo-headers generically.
Related errors
- rbac: header matcher for %q starts with %q
- server-side auth info is not of type alts.AuthInfo
- rbac: error constructing matching engine: %v
- no SubConn is available
- all SubConns are in TransientFailure
AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11).
Data as JSON: /api/errors/d3a3c9093359273a.
Report an issue: GitHub.