Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Fixed a path-segment injection issue in the ``mcp_json_rest_bridge`` filter. A value substituted
into a *simple* path-template variable (e.g. ``{id}``) was percent-encoded with a set that left
``/`` and ``.`` unescaped, so an attacker-supplied value such as ``../../admin`` could inject
additional path segments or ``..`` traversal into the (non-re-normalized) upstream request path.
Simple template variables now reject a value that spans multiple path segments or is a ``.``/``..``
traversal segment; wildcard variables (e.g. ``{name=projects/*}``), whose values are intentionally
multi-segment resource names, are unchanged.
Original file line number Diff line number Diff line change
Expand Up @@ -153,10 +153,26 @@ absl::StatusOr<std::string> constructBaseUrl(absl::string_view pattern,
}
// Non-visible ASCII characters are always escaped by Http::Utility::PercentEncoding::encode,
// in addition to the specified reserved characters.
std::string value_str = Http::Utility::PercentEncoding::encode(
jsonValueToString(*template_value_json), ReservedChars);
std::string var_pattern = "\\{" + RE2::QuoteMeta(element) + "(?:=[^}]+)?\\}";
RE2::GlobalReplace(&base_url, var_pattern, value_str);
const std::string raw_value = jsonValueToString(*template_value_json);
const std::string quoted = RE2::QuoteMeta(element);
// Wildcard variables `{name=.../*}` bind a multi-segment value (a Google-API resource name),
// so `/` and `.` are preserved.
std::string wildcard_value = Http::Utility::PercentEncoding::encode(raw_value, ReservedChars);
RE2::GlobalReplace(&base_url, "\\{" + quoted + "=[^}]+\\}", wildcard_value);
// Simple variables `{id}` bind exactly one path segment. Reject a value that spans multiple
// segments or is a `.`/`..` traversal segment rather than silently rewriting the upstream
// path, which is not re-normalized before setPath() (issue #45931).
const RE2 simple_var("\\{" + quoted + "\\}");
if (RE2::PartialMatch(base_url, simple_var)) {
if (raw_value.find('/') != std::string::npos || raw_value == "." || raw_value == "..") {
return absl::InvalidArgumentError(
absl::StrCat("value for path-template variable '", element,
"' must be a single path segment (no '/', and not '.' or '..')"));
}
std::string single_segment_value =
Http::Utility::PercentEncoding::encode(raw_value, ReservedChars);
RE2::GlobalReplace(&base_url, simple_var, single_segment_value);
}
}
return base_url;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ namespace Extensions {
namespace HttpFilters {
namespace McpJsonRestBridge {

// Percent-encode all printable ASCII characters in URL query parameters except for
// Percent-encode all printable ASCII characters in URL path/query template values except for
// `[-_./0-9a-zA-Z]`, per the Google API path template syntax:
// https://cloud.google.com/service-infrastructure/docs/service-management/reference/rpc/google.api#path-template-syntax
// This set intentionally preserves `.` and `/` for wildcard/multi-segment template variables
// (e.g. `{name=projects/*}`), whose values are Google-API resource names that span path segments.
inline constexpr absl::string_view ReservedChars = R"( !"#$%&'()*+,:;<=>?@[\]^`{|}~)";

struct HttpRequest {
Expand Down
Loading