VYPR
Critical severityOSV Advisory· Published Apr 25, 2019· Updated Aug 4, 2024

CVE-2019-9901

CVE-2019-9901

Description

Envoy 1.9.0 and before does not normalize HTTP URL paths. A remote attacker may craft a relative path, e.g., something/../admin, to bypass access control, e.g., a block on /admin. A backend server could then interpret the non-normalized path and provide an attacker access beyond the scope provided for by the access control policy.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Envoy 1.9.0 and before does not normalize HTTP URL paths, allowing path traversal to bypass access controls like RBAC, routing, and ext_authz.

What is the vulnerability?

CVE-2019-9901 is a path normalization bypass in Envoy proxy versions 1.9.0 and earlier. Envoy does not normalize HTTP URL paths before performing routing or L7 data plane processing. This means that a request containing a relative path such as /info/../admin is not collapsed to /admin before Envoy evaluates access control rules. The root cause is a mismatch between the path used for authorization decisions and the path interpreted by the backend server, violating RFC 3986 normalization principles [1][2][4].

How is it exploited?

An unauthenticated remote attacker can craft an HTTP request with a relative path component (e.g., ../) to bypass Envoy's access control mechanisms. For example, if an RBAC filter is configured to allow prefix /info/, an attacker can send a request to /info/../private. Envoy's RBAC filter permits the request based on the unnormalized prefix match, while the backend server normalizes the path per RFC 3986 and serves /private. This bypass applies to all L7 filters that rely on path matching, including RBAC, HTTP router, external authorization (ext_authz), and rate limiting service [3][4]. No authentication or privileged network position is required; the attack is delivered as a standard HTTP request.

Impact

Successful exploitation allows an attacker to bypass intended access control policies and gain unauthorized access to backend resources. The attacker may reach paths that should have been blocked, such as admin interfaces, configuration endpoints, or other sensitive data. In the context of a service mesh or API gateway, this could lead to information disclosure, privilege escalation, or further compromise of backend services. The CVSS 3.0 score is 8.3 (High), reflecting the ease of exploitation and potential for partial confidentiality, integrity, and availability impacts across network boundaries [3][4].

Mitigation

Envoy 1.9.1 (released April 5, 2019) includes a fix that normalizes HTTP paths prior to routing or L7 processing. The fix is disabled by default for backward compatibility and must be enabled by setting the HTTP connection manager's normalize_path option to true or by using the envoy.reloadable_features.http_path_normalization runtime flag. Users running affected versions should upgrade to 1.9.1 or later and enable path normalization. No workaround exists for earlier versions other than restricting access at a higher level (e.g., a reverse proxy or WAF) [1][2].

AI Insight generated on May 22, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/envoyproxy/envoyGo
< 1.9.11.9.1

Affected products

2

Patches

1
e668e669677e

HCM: add compile option of path normalization

https://github.com/envoyproxy/envoyYuchen DaiApr 9, 2019via ghsa
4 files changed · +46 8
  • bazel/BUILD+5 0 modified
    @@ -105,6 +105,11 @@ config_setting(
         values = {"define": "google_grpc=disabled"},
     )
     
    +config_setting(
    +    name = "enable_path_normalization_by_default",
    +    values = {"define": "path_normalization_by_default=true"},
    +)
    +
     cc_proto_library(
         name = "grpc_health_proto",
         deps = ["@com_github_grpc_grpc//src/proto/grpc/health/v1:_health_proto_only"],
    
  • bazel/envoy_build_system.bzl+9 1 modified
    @@ -83,7 +83,8 @@ def envoy_copts(repository, test = False):
                    "//conditions:default": [],
                }) + envoy_select_hot_restart(["-DENVOY_HOT_RESTART"], repository) + \
                envoy_select_perf_annotation(["-DENVOY_PERF_ANNOTATION"]) + \
    -           envoy_select_google_grpc(["-DENVOY_GOOGLE_GRPC"], repository)
    +           envoy_select_google_grpc(["-DENVOY_GOOGLE_GRPC"], repository) + \
    +           envoy_select_path_normalization_by_default(["-DENVOY_NORMALIZE_PATH_BY_DEFAULT"], repository)
     
     def envoy_static_link_libstdcpp_linkopts():
         return envoy_select_force_libcpp(
    @@ -648,6 +649,13 @@ def envoy_select_hot_restart(xs, repository = ""):
             "//conditions:default": xs,
         })
     
    +# Select the given values if default path normalization is on in the current build.
    +def envoy_select_path_normalization_by_default(xs, repository = ""):
    +    return select({
    +        repository + "//bazel:enable_path_normalization_by_default": xs,
    +        "//conditions:default": [],
    +    })
    +
     def envoy_select_perf_annotation(xs):
         return select({
             "@envoy//bazel:enable_perf_annotation": xs,
    
  • source/extensions/filters/network/http_connection_manager/config.cc+12 7 modified
    @@ -151,13 +151,18 @@ HttpConnectionManagerConfig::HttpConnectionManagerConfig(
                                                                              context_.listenerScope())),
           proxy_100_continue_(config.proxy_100_continue()),
           delayed_close_timeout_(PROTOBUF_GET_MS_OR_DEFAULT(config, delayed_close_timeout, 1000)),
    -      normalize_path_(
    -          PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, normalize_path,
    -                                          // TODO(htuch): we should have a
    -                                          // boolean variant of featureEnabled()
    -                                          // here.
    -                                          context.runtime().snapshot().featureEnabled(
    -                                              "http_connection_manager.normalize_path", 0))) {
    +      normalize_path_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(config, normalize_path,
    +#ifdef ENVOY_NORMALIZE_PATH_BY_DEFAULT
    +                                                      true
    +#else
    +                                                      // TODO(htuch): we should have a
    +                                                      // boolean variant of featureEnabled()
    +                                                      // here.
    +                                                      context.runtime().snapshot().featureEnabled(
    +                                                          "http_connection_manager.normalize_path",
    +                                                          0)
    +#endif
    +                                                      )) {
     
       route_config_provider_ = Router::RouteConfigProviderUtil::create(config, context_, stats_prefix_,
                                                                        route_config_provider_manager_);
    
  • test/extensions/filters/network/http_connection_manager/config_test.cc+20 0 modified
    @@ -186,6 +186,25 @@ TEST_F(HttpConnectionManagerConfigTest, DisabledStreamIdleTimeout) {
       EXPECT_EQ(0, config.streamIdleTimeout().count());
     }
     
    +#ifdef ENVOY_NORMALIZE_PATH_BY_DEFAULT
    +// Validated that we normalize paths compiled with enable-by-default
    +TEST_F(HttpConnectionManagerConfigTest, NormalizePathCompileConfig) {
    +  const std::string yaml_string = R"EOF(
    +  stat_prefix: ingress_http
    +  route_config:
    +    name: local_route
    +  http_filters:
    +  - name: envoy.router
    +  )EOF";
    +
    +  EXPECT_CALL(context_.runtime_loader_.snapshot_,
    +              featureEnabled("http_connection_manager.normalize_path", 0))
    +      .Times(0);
    +  HttpConnectionManagerConfig config(parseHttpConnectionManagerFromV2Yaml(yaml_string), context_,
    +                                     date_provider_, route_config_provider_manager_);
    +  EXPECT_TRUE(config.shouldNormalizePath());
    +}
    +#else
     // Validated that by default we don't normalize paths
     TEST_F(HttpConnectionManagerConfigTest, NormalizePathDefault) {
       const std::string yaml_string = R"EOF(
    @@ -218,6 +237,7 @@ TEST_F(HttpConnectionManagerConfigTest, NormalizePathRuntime) {
                                          date_provider_, route_config_provider_manager_);
       EXPECT_TRUE(config.shouldNormalizePath());
     }
    +#endif
     
     // Validated that when configured, we normalize paths, ignoring runtime.
     TEST_F(HttpConnectionManagerConfigTest, NormalizePathTrue) {
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

9

News mentions

0

No linked articles in our index yet.