grpc/grpc-go · error
"headers" : unsupported "key
Error message
"headers" %d: unsupported "key" %s
What it means
Returned by parseHeaders (rbac_translator.go:237) when a header key is not matchable: keys starting with ':' (pseudo-headers), keys with the 'grpc-' prefix, or keys in the unsupportedHeaders set {host, connection, keep-alive, proxy-authenticate, proxy-authorization, te, trailer, transfer-encoding, upgrade} (defined at line 213). gRPC forbids RBAC matching on these because they are hop-by-hop, transport-managed, or reserved.
Solutions
- Remove the unsupported header from the rule, or match on an allowed custom metadata key instead (a non-reserved, non-grpc-prefixed header).
- If you need caller identity, use source.principals (mTLS peer) rather than a header.
- Re-read the unsupportedHeaders list and pseudo-header rules and audit all header keys in the policy.
Example fix
// before
"headers": [ { "key": "host", "values": ["api.example.com"] } ]
// after
"headers": [ { "key": "x-envoy-original-host", "values": ["api.example.com"] } ]
// or remove the header matcher and match on request.paths / source.principals Defensive patterns
Strategy: validation
Validate before calling
var unsupportedHeaders = map[string]bool{
"host": true, "connection": true, "keep-alive": true,
"proxy-authenticate": true, "proxy-authorization": true,
"te": true, "trailer": true, "transfer-encoding": true, "upgrade": true,
}
func allowedHeader(key string) bool {
k := strings.ToLower(key)
if k == "" || k[0] == ':' || strings.HasPrefix(k, "grpc-") {
return false
}
return !unsupportedHeaders[k]
} Try / catch
interceptor, err := authz.NewStatic(policyJSON)
if err != nil {
if strings.Contains(err.Error(), `unsupported "key"`) {
// remove or replace the reserved header in the policy
}
} Prevention
- Do not match on host, connection-style, or grpc-* headers in RBAC policies.
- Prefer source.principals (mTLS identity) over reserved headers.
- Keep the unsupportedHeaders list handy when authoring policies.
When it happens
Trigger: A policy rule whose request.headers[].key is one of the reserved/unsupported names (case-insensitive), e.g. "host", ":path", "grpc-trace-bin", "connection".
Common situations: Trying to authorize on the Host header; matching on grpc-* metadata; copying Envoy HTTP route logic into a gRPC policy without adjusting for reserved headers.
Related errors
AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11).
Data as JSON: /api/errors/19a1b345d3923026.
Report an issue: GitHub.
Appendix: source
Thrown at authz/rbac_translator.go:237
"te": true,
"trailer": true,
"transfer-encoding": true,
"upgrade": true,
}
func unsupportedHeader(key string) bool {
return key[0] == ':' || strings.HasPrefix(key, "grpc-") || unsupportedHeaders[key]
}
func parseHeaders(headers []header) ([]*v3rbacpb.Permission, error) {
hs := make([]*v3rbacpb.Permission, 0, len(headers))
for i, header := range headers {
if header.Key == "" {
return nil, fmt.Errorf(`"headers" %d: "key" is not present`, i)
}
header.Key = strings.ToLower(header.Key)
if unsupportedHeader(header.Key) {
return nil, fmt.Errorf(`"headers" %d: unsupported "key" %s`, i, header.Key)
}
if len(header.Values) == 0 {
return nil, fmt.Errorf(`"headers" %d: "values" is not present`, i)
}
values := parseHeaderValues(header.Key, header.Values)
hs = append(hs, permissionOr(values))
}
return hs, nil
}
func parseRequest(request request) (*v3rbacpb.Permission, error) {
var and []*v3rbacpb.Permission
if len(request.Paths) > 0 {
and = append(and, permissionOr(parsePaths(request.Paths)))
}
if len(request.Headers) > 0 {
headers, err := parseHeaders(request.Headers)
if err != nil {View on GitHub (pinned to 0c51461d27)