{"record":{"id":"05323316d0ce81f0","repo":"grpc/grpc-go","slug":"header-mutation-disallowed-by-headermutationrules","errorCode":null,"errorMessage":"header mutation disallowed by headerMutationRules for header key %q","messagePattern":"header mutation disallowed by headerMutationRules for header key %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/xds/httpfilter/extconfig.go","lineNumber":158,"sourceCode":"\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 {\n\t\t\t\treturn fmt.Errorf(\"header mutation disallowed by headerMutationRules for header key %q\", key)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\n\t\t// Perform the mutation on output metadata using the append_action\n\t\t// field from the header value option.\n\t\tswitch hvo.GetAppendAction() {\n\t\tcase v3corepb.HeaderValueOption_APPEND_IF_EXISTS_OR_ADD:\n\t\t\tinput.Append(key, value)\n\t\tcase v3corepb.HeaderValueOption_ADD_IF_ABSENT:\n\t\t\tif input.Get(key) == nil {\n\t\t\t\tinput.Set(key, value)\n\t\t\t}\n\t\tcase v3corepb.HeaderValueOption_OVERWRITE_IF_EXISTS_OR_ADD:\n\t\t\tinput.Set(key, value)\n\t\tcase v3corepb.HeaderValueOption_OVERWRITE_IF_EXISTS:\n\t\t\tif input.Get(key) != nil {\n\t\t\t\tinput.Set(key, value)","sourceCodeStart":140,"sourceCodeEnd":176,"githubUrl":"https://github.com/grpc/grpc-go/blob/0c51461d27177d997e14c642fe18c11668fc09a3/internal/xds/httpfilter/extconfig.go#L140-L176","documentation":"Returned by ApplyAdditions when a header the ext_proc server wants to add/modify is rejected by the configured HeaderMutationRules (AllowExpr/DisallowExpr) AND DisallowIsError is true. Without DisallowIsError the mutation is silently skipped (continue); with it, the data-plane RPC fails with gRPC status Unknown. The allow() function returns false if the key matches DisallowExpr or does not match a configured AllowExpr.","triggerScenarios":"xDS config sets mutation_rules with disallow_is_error=true and either an allow_expression that does not match the key, or a disallow_expression that does match it. The ext_proc server then attempts to mutate that key, hitting extconfig.go:158.","commonSituations":"A security policy disallows mutating x-auth-* but the ext_proc server tries to set it; an allow list was tightened and the server was not updated; the regex was written for Envoy semantics and behaves differently under Go's RE2.","solutions":["Update the ext_proc server so it only mutates headers permitted by the configured allow/disallow regexes.","If the mutation is intended, broaden allow_expression (or narrow disallow_expression) in the xDS HeaderMutationRules.","If failing the RPC is too aggressive, set disallow_is_error=false to revert to silent skip behavior.","Verify the regex with the same RE2 engine the client uses (Go regexp) to avoid dialect differences."],"exampleFix":"// xDS EnvoyFilter/config - before\nmutation_rules:\n  allow_expression: { regex: \"^x-trace-.*$\" }\n  disallow_is_error: true\n// server sends \"x-auth-token\" -> RPC fails\n// after: allow both, or downgrade to skip\nmutation_rules:\n  allow_expression: { regex: \"^x-(trace|auth)-.*$\" }\n  disallow_is_error: true","handlingStrategy":"validation","validationCode":"// server-side: apply the same allow/disallow regexes before sending\nfunc allowed(key string, allow, disallow *regexp.Regexp) bool {\n  if disallow != nil && disallow.MatchString(key) { return false }\n  if allow != nil { return allow.MatchString(key) }\n  return true\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Make the server aware of the configured allow/disallow regexes.","Prefer disallow_is_error=false if silent skip fits your threat model.","Test regexes with Go's RE2 (no backreferences).","Tighten the server's emitted header set to match the policy."],"tags":["grpc","xds","extproc","header-mutation","policy","security"],"backgroundTag":null,"analyzedSha":"0c51461d27177d997e14c642fe18c11668fc09a3","analyzedAt":"2026-08-11T14:49:15.055Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}