envoyproxy/envoy

Envoy `mcp_json_rest_bridge` filter injects unescaped path segments into the upstream request path (unauthenticated)

Open

#45 931 ouverte le 2 juil. 2026

Voir sur GitHub
 (1 commentaire) (0 réactions) (0 assignés)C++ (5 373 forks)batch import
area/mcpbughelp wanted

Métriques du dépôt

Stars
 (27 997 stars)
Métriques de merge PR
 (Merge moyen 8j) (378 PRs mergées en 30 j)

Description

Originally reported by @omkhar

Summary

The mcp_json_rest_bridge HTTP filter substitutes attacker-controlled tool-call arguments into the upstream request path template without escaping / or ., and the rewritten path is never re-normalized. An unauthenticated downstream client can inject .. traversal and extra path segments into the request Envoy forwards upstream. Confirmed present and unchanged at current upstream main HEAD 56af06f53954c5eb1a2b2073634ef4de33d9f5aa. I request coordinated disclosure consistent with Envoy's 90-day policy.

Description

Exact claim. The mcp_json_rest_bridge HTTP filter substitutes attacker-controlled tool-call arguments into the configured URL path template without escaping / (0x2F) or . (0x2E). A simple template variable such as {id} is meant to fill exactly one path segment, but a value like ../../admin/secrets passes through verbatim. The resulting string is installed as the upstream request :path via setPath() and is never re-normalized — Envoy's path sanitization (canonicalPath / mergeSlashes) ran earlier, on the original /mcp request line, before the filter chain executed. An unauthenticated downstream client therefore controls extra path segments and .. traversal in the request Envoy forwards to the backend.

Sink (at HEAD 56af06f5).

  • source/extensions/filters/http/mcp_json_rest_bridge/http_request_builder.h — the ReservedChars percent-encoding set deliberately omits / and ..
  • source/extensions/filters/http/mcp_json_rest_bridge/http_request_builder.cc:156-157constructBaseUrl() percent-encodes the template value with that ReservedChars set, so / and . survive unencoded.
  • source/extensions/filters/http/mcp_json_rest_bridge/mcp_json_rest_bridge_filter.cc:845 — the constructed URL is installed verbatim as the upstream :path (setPath), and the route cache is cleared at line 864 so route matching re-runs against the attacker-influenced path.

Attacker / trust boundary. An unauthenticated downstream client sending POST /mcp. This filter performs no authentication, and the tool-call params.arguments are fully attacker-controlled JSON that feed constructBaseUrl().

Honest scope caveat. Exploitability requires the operator to have deployed the mcp_json_rest_bridge filter with at least one tool whose template has a simple variable an attacker can drive — the documented, intended usage of the filter. Preserving / is deliberate for Google-API multi-segment templates such as {var=projects/*}; the defect is that simple variables (no =...* wildcard pattern) are not restricted to a single segment, so / and . leak through uniformly. End-to-end secret disclosure additionally depends on the operator's route table and the upstream server's own path handling — the reported flaw is the injection primitive itself.

Exact observable. With template /v1/users/{id}/profile and attacker argument id = "../../admin/secrets", the upstream request path becomes :path = /v1/users/../../admin/secrets/profile — the raw .. traversal is preserved. Negative controls confirm the encoder is otherwise working and only the ReservedChars set is escaped: a space becomes %20, while / and . are emitted literally. Consequences are (a) route-matching bypass — escaping the intended prefix to match a different, more-privileged Envoy route after the route cache is cleared and re-matched — and (b) backend path traversal, where a backend that normalizes .. resolves the injected path to an endpoint the tool template never intended to expose.

Minimal reproduction. Reproduce the encoding path directly: apply the real ReservedChars constant and constructBaseUrl() algorithm to the template /v1/users/{id}/profile with id = "../../admin/secrets", and observe the resulting upstream path is /v1/users/../../admin/secrets/profile — the / and . characters are not percent-encoded. As controls, confirm a space is encoded to %20 while a bare / and . pass through literally. (A self-contained harness exercising the verbatim PercentEncoding::encode / constructBaseUrl algorithm is available on request through a private channel.)

Concrete fix. For simple template variables (those without an =...* segment pattern), restrict the value to a single segment: encode with ReservedChars + "/" (and reject literal ..) unless the matched template token carries an explicit wildcard, or validate that the value matches [^/]+ and reject the tool call otherwise. As defense-in-depth, reject .. segments in the final constructed path before setPath(), since Envoy no longer re-normalizes the rewritten path.

Guide contributeur