{"record":{"id":"561c00583f9c6e77","repo":"grpc/grpc-go","slug":"invalid-header-mutation-v","errorCode":null,"errorMessage":"invalid header mutation: %v","messagePattern":"invalid header mutation: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/xds/httpfilter/extconfig.go","lineNumber":139,"sourceCode":"// ignored.\n//\n// The input metadata must not be nil.\nfunc (hmr *HeaderMutationRules) ApplyAdditions(hvos []*v3corepb.HeaderValueOption, input metadata.MD) error {\n\tif hmr == nil {\n\t\thmr = &HeaderMutationRules{}\n\t}\n\tif input == nil {\n\t\treturn fmt.Errorf(\"input metadata is nil\")\n\t}\n\tif hmr.DisallowAll {\n\t\treturn nil\n\t}\n\n\tfor _, hvo := range hvos {\n\t\theader := hvo.GetHeader()\n\t\tkey := header.GetKey()\n\t\tif err := validateHeaderKey(key); err != nil {\n\t\t\treturn fmt.Errorf(\"invalid header mutation: %v\", err)\n\t\t}\n\n\t\tvalue := header.GetValue()\n\t\tif strings.HasSuffix(key, \"-bin\") {\n\t\t\tvalue = string(header.GetRawValue())\n\t\t}\n\t\tif len(value) > maxHeaderSize {\n\t\t\treturn fmt.Errorf(\"invalid header mutation: value for header key %q exceeds the maximum length of %d bytes\", key, maxHeaderSize)\n\t\t}\n\t\t// ValidatePair rejects values carrying bytes outside %x20-%x7E. It\n\t\t// skips the value check for \"-bin\" keys, whose values the transport\n\t\t// base64 encodes.\n\t\tif err := imetadata.ValidatePair(key, value); err != nil {\n\t\t\treturn fmt.Errorf(\"invalid header mutation: %v\", err)\n\t\t}\n\n\t\tif !hmr.allow(key) {\n\t\t\tif hmr.DisallowIsError {","sourceCodeStart":121,"sourceCodeEnd":157,"githubUrl":"https://github.com/grpc/grpc-go/blob/0c51461d27177d997e14c642fe18c11668fc09a3/internal/xds/httpfilter/extconfig.go#L121-L157","documentation":"A header mutation received from an external processing server has an invalid header key (extconfig.go:137-139). validateHeaderKey rejects keys that are empty, pseudo-headers (starting with ':'), 'host', start with 'grpc-', are not all lowercase, or exceed 16384 bytes. This is a data-plane error, not a configuration error.","triggerScenarios":"ApplyAdditions iterates over HeaderValueOption entries from an external authorization server's response, and validateHeaderKey returns an error for one of the header keys. The error wraps the specific validation failure (e.g. 'header key \"Content-Type\" is not lowercase').","commonSituations":"External auth server tries to set ':path' or ':authority' (pseudo-headers); server sends 'Content-Type' or 'Authorization' (uppercase) instead of 'content-type'; server sends an empty key; server sends a 'grpc-status' header (reserved grpc- prefix); server sends a header key longer than 16KB.","solutions":["Fix the external processing server to only send valid, lowercase, non-reserved header keys","Ensure header keys do not start with ':', are not 'host', and do not start with 'grpc-'","Convert all header keys to lowercase before sending mutations","Validate header key length does not exceed 16384 bytes"],"exampleFix":"// before — external auth server sends uppercase and reserved headers\nresp := &extprocpb.HeaderMutation{\n    Set: []HeaderValueOption{\n        {Header: &HeaderValue{Key: \"Content-Type\", Value: \"application/json\"}},\n        {Header: &HeaderValue{Key: \":path\", Value: \"/new-path\"}},\n    },\n}\n\n// after — lowercase, non-reserved keys only\nresp := &extprocpb.HeaderMutation{\n    Set: []HeaderValueOption{\n        {Header: &HeaderValue{Key: \"content-type\", Value: \"application/json\"}},\n        // ':path' removed — pseudo-headers cannot be mutated\n    },\n}","handlingStrategy":"validation","validationCode":"// Validate header keys before applying mutations from an external server.\nfunc validateMutationKeys(hvos []*v3corepb.HeaderValueOption) error {\n    for _, hvo := range hvos {\n        key := hvo.GetHeader().GetKey()\n        if key == \"\" || key[0] == ':' || key == \"host\" ||\n            strings.HasPrefix(key, \"grpc-\") || key != strings.ToLower(key) {\n            return fmt.Errorf(\"invalid header key %q from external server\", key)\n        }\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"// In the external processing/auth server response handler:\nerr := hmr.ApplyAdditions(hvos, md)\nif err != nil && strings.Contains(err.Error(), \"invalid header mutation\") {\n    // Log and drop the invalid mutation rather than failing the RPC.\n    log.Printf(\"dropping invalid header mutation from external server: %v\", err)\n    err = nil\n}","preventionTips":["External processing servers should only send lowercase, non-reserved header keys","Sanitize header keys on the external server side before returning mutations","Reject pseudo-headers, 'host', and 'grpc-' prefixed keys in the external server's mutation logic","Add integration tests that verify the external server's mutations pass validateHeaderKey"],"tags":["http-filter","header-mutation","ext-authz","data-plane","validation"],"backgroundTag":null,"analyzedSha":"0c51461d27177d997e14c642fe18c11668fc09a3","analyzedAt":"2026-08-11T14:49:15.055Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}