grpc/grpc-go · error
"headers" %d: unsupported "key" %s
Error message
"headers" %d: unsupported "key" %s
What it means
Returned by parseHeaders (rbac_translator.go:237) when a header matcher's key is unsupported: pseudo-headers (starting with ':'), 'grpc-' prefixed headers, or reserved HTTP/2 headers in the unsupportedHeaders set (connection, keep-alive, transfer-encoding, etc.). These headers cannot be matched by RBAC policy, so the translator rejects them.
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 03255a9237)
Solutions
- Remove or replace the unsupported header key with a permitted custom/application header.
- To match the request path or method, use the dedicated "paths" policy field rather than the ":path"/":method" pseudo-headers.
- Cross-check the key against the unsupportedHeaders list and the ':' / 'grpc-' rules.
- Use custom metadata (non-reserved) headers for authorization conditions.
Example fix
// before
{"headers":[{"key":":path","values":["/svc/M"]}]}
// after - use the paths field instead
{"paths":[{"path":"/svc/M"}]} Defensive patterns
Strategy: validation
Validate before calling
func isAllowedHeaderKey(k string) bool {
k = strings.ToLower(k)
return k[0] != ':' && !strings.HasPrefix(k, "grpc-") && !unsupportedHeaders[k]
} Try / catch
interceptor, err := authz.NewStatic(policyJSON)
if err != nil && strings.Contains(err.Error(), "unsupported") {
return fmt.Errorf("policy uses a reserved header: %w", err)
} Prevention
- Avoid pseudo-headers and grpc-* headers in matchers.
- Use the paths field for path/method matching.
- Keep a reference of unsupportedHeaders and screen keys against it.
When it happens
Trigger: Writing a policy that tries to match on ":path", ":method", "grpc-status", "content-length", "te", "host", or any connection-specific header listed at rbac_translator.go:210-223.
Common situations: Attempting to route/authorize by pseudo-headers or gRPC metadata reserved names; copy-pasting Envoy header matchers that reference reserved headers.
Related errors
- "headers" %d: "key" is not present
- "headers" %d: "values" is not present
- authz: authorization policy file path is empty
- authz: requires refresh interval(%v) greater than 0s
- policyFile(%s) read failed: %v
AI-assisted analysis of grpc/grpc-go@03255a9237 (2026-08-07).
Data as JSON: /api/errors/19a1b345d3923026.
Report an issue: GitHub.