{"record":{"id":"d69d21b7413c2cae","repo":"grpc/grpc-java","slug":"header-mutation-disallowed","errorCode":null,"errorMessage":"Header mutation disallowed","messagePattern":"Header mutation disallowed","errorType":"exception","errorClass":"HeaderMutationDisallowedException","httpStatus":null,"severity":"error","filePath":"xds/src/main/java/io/grpc/xds/internal/headermutations/HeaderMutationFilter.java","lineNumber":75,"sourceCode":"  /**\n   * A generic helper to filter a collection based on a predicate.\n   */\n  private <T> ImmutableList<T> filterCollection(Collection<T> items,\n      Predicate<T> isIgnoredPredicate, Predicate<T> isAllowedPredicate)\n      throws HeaderMutationDisallowedException {\n    ImmutableList.Builder<T> allowed = ImmutableList.builder();\n    for (T item : items) {\n      boolean isIgnored = isIgnoredPredicate.test(item);\n      boolean isAllowed = isAllowedPredicate.test(item);\n\n      // TODO(sauravzg): The specification is ambiguous regarding whether system headers\n      // should be silently ignored or trigger an error when disallowIsError is enabled.\n      // We default to triggering errors matching Envoy's implementation.\n      // Ref: https://github.com/grpc/proposal/pull/481#discussion_r3124453674\n      if (!isIgnored && isAllowed) {\n        allowed.add(item);\n      } else if (disallowIsError()) {\n        throw new HeaderMutationDisallowedException(\"Header mutation disallowed\");\n      }\n    }\n    return allowed.build();\n  }\n\n  private boolean isDisallowed(String key) {\n    return HeaderValueValidationUtils.isDisallowed(key);\n  }\n\n  private boolean isDisallowed(HeaderValueOption option) {\n    return HeaderValueValidationUtils.isDisallowed(option.header());\n  }\n\n  private boolean isHeaderMutationAllowed(HeaderValueOption option) {\n    return isHeaderMutationAllowed(option.header().key());\n  }\n\n  private boolean isHeaderMutationAllowed(String headerName) {","sourceCodeStart":57,"sourceCodeEnd":93,"githubUrl":"https://github.com/grpc/grpc-java/blob/64daddc1f3d1975670f769f3e97bde8b2ba32d25/xds/src/main/java/io/grpc/xds/internal/headermutations/HeaderMutationFilter.java#L57-L93","documentation":"The HeaderMutationFilter enforces xDS header mutation rules (per gRFC A48/Envoy behavior). When a header add/modify/remove entry is neither explicitly allowed by the rules nor marked as ignorable, and disallowIsError() is true (the default), it throws HeaderMutationDisallowedException. This matches Envoy's default of rejecting disallowed header mutations rather than silently dropping them.","triggerScenarios":"filterCollection() (invoked via allowedHeaders/allowedHeadersToRemove) encounters a header mutation whose key is disallowed (e.g., pseudo-headers like \":path\", or keys not matching allowed_regular_expressions) while disallow_is_error is enabled.","commonSituations":"xDS route config or HTTP filter config attempts to add/set a protected header (:authority, :path, host, etc.) or a header not matching allow rules; configs written for a proxy that silently ignores mutations being run against this stricter implementation.","solutions":["Remove the disallowed header mutation from the xDS configuration (pseudo-headers and protected keys cannot be mutated)","Bring the header key within the rules' allowed_regular_expressions if it should be permitted","Set disallow_is_error: false in the HeaderMutation rules so disallowed mutations are silently ignored instead of failing","Validate the header keys against the rules before deploying the config"],"exampleFix":"// before (xDS config JSON)\n{\"mutation\": {\"add\": {\"header\": \":path\", \"value\": \"/foo\"}}}\n// after: pseudo-headers cannot be mutated; mutate a custom header instead\n{\"mutation\": {\"add\": {\"header\": \"x-forwarded-path\", \"value\": \"/foo\"}}}","handlingStrategy":"try-catch","validationCode":"HeaderMutationFilter filter = HeaderMutationFilter.create(rules);\n// pre-check keys before submitting mutations\nfor (String key : mutationKeys) {\n  if (key.startsWith(\":\") || List.of(\"host\",\"content-length\").contains(key)) {\n    throw new ConfigException(\"Cannot mutate protected header: \" + key);\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  List<HeaderMutation> allowed = filter.allowedHeaders(mutations);\n} catch (HeaderMutationDisallowedException e) {\n  log.error(\"xDS header mutation not permitted by rules: {}\", e.getMessage());\n}","preventionTips":["Never attempt to mutate pseudo-headers (:path, :authority)","Match custom headers against allowed_regular_expressions before deploying","Set disallow_is_error: false only if silent dropping is acceptable","Validate configs against Envoy's documented header mutation restrictions"],"tags":["grpc","xds","header-mutations","config-validation"],"backgroundTag":"permission-denied","analyzedSha":"64daddc1f3d1975670f769f3e97bde8b2ba32d25","analyzedAt":"2026-09-08T06:14:57.704Z","contentChangedAt":"2026-09-08T06:14:57.704Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}