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

  1. Remove the ":scheme" header matcher from the RBAC policy.
  2. If routing on scheme is required, handle it at the listener/LDS layer rather than in RBAC.
  3. 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

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


AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11). Data as JSON: /api/errors/d3a3c9093359273a. Report an issue: GitHub.