grpc/grpc-go · error

rbac: header matcher for %q starts with %q

Error message

rbac: header matcher for %q starts with %q

What it means

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.

Source

Thrown at internal/xds/httpfilter/rbac/rbac.go:177

			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)
	}
	msg := new(rpb.RBAC)
	if err := m.UnmarshalTo(msg); err != nil {
		return nil, fmt.Errorf("rbac: error parsing config %v: %v", cfg, err)

View on GitHub (pinned to 0c51461d27)

Solutions

  1. Remove the grpc-* header matcher from the policy.
  2. If you need to gate on tracing metadata, do it in the application or via a different filter.
  3. Audit all HeaderMatcher entries for the grpc- prefix during policy authoring.

Example fix

// before
permissions: [{ rule: { header: { name: "grpc-trace-bin", present_match: true } } }]

// after
// (matcher removed)
Defensive patterns

Strategy: validation

Validate before calling

func validateNoGrpcHeaders(p *v3rbacpb.Policy) error {
    check := func(h *v3routepb.HeaderMatcher) error {
        if strings.HasPrefix(h.GetName(), "grpc-") { return fmt.Errorf("grpc-* header %q forbidden by A41", h.GetName()) }
        return nil
    }
    /* walk principals+permissions as the filter does */
    return nil
}

Try / catch

if err != nil && strings.Contains(err.Error(), "starts with") {
    // remove the grpc-* header matcher from the policy
}

Prevention

When it happens

Trigger: 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).

Common situations: Operator copies an Envoy policy that filters on grpc-trace-bin; legacy RBAC config predating A41; policy generator that enumerates all known headers.

Related errors


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