Apigee allows developers to define conditional behaviour with operators like MatchesPath
, Matches
, JavaRegex
, ==
, and others, enabling them to control which flows or policies apply based on specific request variables like the request path. Each operator has unique behaviour: while Matches
lets you use *
as a wildcard character, MatchesPath
allows matching for specific path parts, and JavaRegex
applies regular expressions.
Of all these operators, MatchesPath
stands out to me, particularly for its behaviour with URL path parameter separators like commas, semicolons, and equal signs
URL Path Parameter Separators?
You might be wondering, what exactly are these separators? Well, URL path parameter separators
such as semicolons (;
), commas (,
), and equals signs (=
) are reserved characters used to encode extra details about a path segment. According to RFC 3986, these characters can indicate parameters or subcomponents relevant to a given segment.
For example, name;v=1.1
might refer to version 1.1 of "name," while name,1.1
could represent a similar concept. The use and interpretation of these separators often depend on the URI scheme or the implementation handling the URI, making them flexible yet potentially controversial in specific contexts.
So, these separators can make URLs more descriptive, but they also introduce some ambiguity, which is especially relevant when securing API paths in Apigee.
How Apigee Handles Path Parameter Separators
Let's dive into how Apigee behaves when working with path parameters and separators. We'll use an ExtractVariables policy to see how Apigee extracts values from different types of requests.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ExtractVariables continueOnError="false" enabled="true" name="EV-1">
<DisplayName>EV-1</DisplayName>
<Properties/>
<URIPath>
<Pattern ignoreCase="true">/a/{id}</Pattern>
</URIPath>
<IgnoreUnresolvedVariables>true</IgnoreUnresolvedVariables>
<Source clearPayload="false">request</Source>
<VariablePrefix>apigee</VariablePrefix>
</ExtractVariables>
This policy aims to capture the id
from the path /a/{id}
and store it in apigee.id
. Now, let’s test various request formats to see how Apigee extracts the value!
1️⃣ Request to /a/before
- the policy correctly extracted the value of id as before
.
2️⃣ Request to /a/before;after
- despite the additional ;after
, the policy still extracted id as before
.
3️⃣ Request to /a/before=after
- once again, only before
was considered for the id extraction.
4️⃣ Request to /a/before,after
- the result remained consistent, with id extracted as before
.
It seems Apigee consistently ignores any parameters after the path separators when extracting variables with this policy. In each test case, the ExtractVariables policy only considered the portion of the path before the separator, effectively discarding everything following the semicolon, equals sign, or comma.
This behaviour demonstrates that Apigee treats path parameter separators as non-essential when extracting variables using patterns. While this can simplify certain use cases, it also introduces risks, especially when security decisions rely on the full path being validated. This oversight could allow attackers to manipulate path parameters to bypass validations or access restricted resources.
But... We'll dive deeper into the security implications later on. For now, let’s first talk about path-matching operators, specifically, about MatchesPath
.
What About the MatchesPath?
Let's explore how the MatchesPath
operator behaves when handling paths with separators.
... [MatchesPath] operator looks at a path as a series of parts. Therefore, if the path is:
/animals/cats/wild
you can think of the path as consisting of the parts:/animals
,/cats
, and/wild
. The MatchesPath operator lets you use two wildcard notations: a single asterisk (*) and a double asterisk (**). The single asterisk matches one path element. The double asterisk matches one or many path elements...
So, the MatchesPath operator evaluates whether the request path matches a specified pattern, and it interprets the path as a sequence of parts. For example, if you use the condition proxy.pathsuffix MatchesPath "/path/*"
the goal is to match any single segment immediately following /path/
.
<Flow name="/path/*">
<Description/>
<Request/>
<Response/>
<Condition>proxy.pathsuffix MatchesPath "/path/*"</Condition>
</Flow>
Here’s how it should work and what actually is observed:
1️⃣ Request: /path/aaa
- Expected Result:
true
- Actual Result:
true
The path /path/aaa
matches the pattern because aaa
is a straightforward segment following /path/
, which is what the *
wildcard is designed to catch. However, complications arise when separators are present:
2️⃣ Request: /path;bbbb/aaa
- Expected Result:
false
- Actual Result:
true
The path /path;bbbb/aaa
should not match because the semicolon introduces an additional component to the first part of the path. The intent of /path/*
is to match a clean /path/
and anything after, but instead, MatchesPath
disregards the semicolon and everything after it and still evaluates it as a match.
What does this mean? Well, it means that requests sent to the target system could retain unexpected path details like ;bbbb
. In the case of ExtractVariables, only part of the path is extracted and validated.
Doesn’t sound too dangerous? Next, I’ll dive into real vulnerabilities that were found during security assessments of Apigee proxies in the wild that MatchesPath
can introduce and show how seemingly benign behaviour can lead to critical security issues.
Example 1: Misconfigured Flow and RouteRule Handling
Let’s explore a scenario where both MatchesPath
and exact equality (==
) are used to control API routing, but with different effects due to path parameter separators.
In one production Apigee setup, I found a Flow that used MatchesPath
for matching /recommendations
and a RouteRule that matched the same path using strict equality (==
). Here’s what was configured:
<Flow name="fetchRecommendations">
<Condition>proxy.pathsuffix MatchesPath "/recommendations"</Condition>
</Flow>
...
<RouteRule name="DigitalLayer">
<TargetEndpoint>DigitalLayer</TargetEndpoint>
<Condition>proxy.pathsuffix == "/recommendations"</Condition>
</RouteRule>
<RouteRule name="k8smicroservice">
<TargetEndpoint>k8smicroservice</TargetEndpoint>
</RouteRule>
The issue? While /recommendations
was routed to the correct endpoint, a URL with /recommendations;other-info
triggered the Flow condition but not the RouteRule.
Consequently, the request was directed to the fallback k8smicroservice
endpoint. The major problem? The second backend accepted paths with the pattern /*
, treating the request as valid and unintentionally exposing data from a different user!
Example 2: Bypassing Access Control in JavaScript
In this scenario, I encountered an Apigee proxy configuration where a JavaScript-based function performed access control by using regex to block user access to specific paths. This setup had the validateAuthentication
function run in the PreFlow, filtering requests before they reached specific flows. These flows, however, used conditional checks like proxy.pathsuffix MatchesPath "/adult-content-lock"
.
<PreFlow name="PreFlow">
<Request>
...
<Step>JavaScript.validateAuthentication</Step>
...
</Request>
</PreFlow>
...
<Flows>
<Flow name="adultContentLock">
...
<Condition>proxy.pathsuffix MatchesPath "/adult-content-lock"</Condition>
</Flow>
...
</Flows>
Here’s a closer look at the JavaScript file where validateAuthentication
is defined:
function validateAuthentication () {
var pathSuffix = context.getVariable("proxy.pathsuffix");
var accessContext = context.getVariable("accesstoken.context"); // get context to determine if user or system calling our endpoint
var blockRegexArray = [
new RegExp("^/adult-content-lock$"),
new RegExp("^/contracts$"),
"..."
];
if (accessContext === "USER") { // if user calling
for (i = 0; i < blockRegexArray.length; i++) {
// we match with each regex to determine if path should not be accessed by user
var match = blockRegexArray[i].test(pathSuffix);
if (match) {
// throw an error and stop flow execution
throw new Error("Access denied");
}
}
}
}
The function first checks if the request is coming from a "USER
" (as defined by accessContext
). It then iterates over blockRegexArray
, where each entry is a regex pattern targeting sensitive paths (like /adult-content-lock
or /contracts
). If any regex pattern matches pathSuffix
, an error is thrown to prevent further execution.
Because MatchesPath
allowed /adult-content-lock;extra
as valid, while the regex in validateAuthentication
expected an exact match (due to $
at the end), appending a path separator bypassed the JavaScript validation in PreFlow. This bypass let unauthorised requests reach sensitive flows, creating a critical security vulnerability in the routing setup.
Example 3: Faulty Validation
In this example, another critical oversight stems from the use of path separators in routing and validation. Here, the validation logic is applied in the PreFlow through a JavaScript policy, and it checks the request path for specific matches before allowing it to proceed. The JavaScript function matches the proxy.pathsuffix
to predefined paths for sensitive resources, with the expectation of exact matches.
var path = context.getVariable("proxy.pathsuffix");
if ((path === "/available-offers" || path === "/current-offers") && requestVerb === "GET") {
var accountNumberRegex = new RegExp(/^10\d{8,10}$/);
var accountNumber = context.getVariable("request.header.x-account-number");
if (!accountNumberRegex.test(accountNumber)) {
throw new Error("Invalid account number");
}
}
However, the flows themselves use proxy.pathsuffix MatchesPath
conditions, like. This conditional allows paths with URL path parameter separators, while the JavaScript regex expects exact strings.
<PreFlow name="PreFlow">
<Request>
...
<Step>JavaScript.ValidateRequestParameters</Step>
...
</Request>
</PreFlow>
...
<Flows>
<Flow name="getCurrentOffers">
...
<Condition>proxy.pathsuffix MatchesPath "/current-offers" and request.verb == "GET"</Condition>
</Flow>
...
</Flows>
Due to this inconsistency, adding a separator and extra data to the path bypasses the JavaScript-based check, effectively letting invalidated requests reach target endpoints.
Example 4: AWS as a Target System
In one Apigee configuration, a proxy had an AWS-hosted target system, with paths forwarded directly from Apigee to AWS. Apigee matched paths using MatchesPath
, allowing path segments like /service;extra
to pass through. While Apigee saw /service;extra
as a match, AWS didn’t, sending the request to a default handler instead of /service
endpoint.
The crucial problem? Apigee authenticated with JWT token in the Authorization header, but AWS, expecting AWS Signature Version 4 authentication, returned a JSON error that included the entire JWT! This error message exposed sensitive JWT tokens to the client, enabling direct access to AWS services without Apigee mediation, creating a significant security vulnerability.
Conclusion and Recommendations
The primary risk with Apigee’s MatchesPath
stems from a common misunderstanding of how it actually handles path separators.
In my experience, this pattern of risky MatchesPath
usage appears in nearly every other production proxy - and, notably, even in Apigee’s own examples.
This widespread reliance on MatchesPath with static conditions highlights the need for more secure configuration standards across Apigee APIs.
So, how can developers better secure their configurations?
Use of Precise Matching Approaches
Whenever possible, prefer strict equality (==
) for static paths to eliminate ambiguity. Alternatively, leverage carefully crafted regular expressions to ensure that paths are evaluated precisely, particularly in cases involving sensitive endpoints or data access.
Sandbox Testing
Conduct path manipulation testing in non-production environments to spot potential mismatches before deployment. This practice will help identify configurations that are vulnerable to unexpected path interpretation, enabling more secure configurations to be applied in production environments.
Leverage Tools for Detection
Tools like CodeSent can detect when MatchesPath
is applied to static paths, helping to quickly identify and fix vulnerabilities, allowing teams to secure configurations by catching potential problems early on!
Dealing with Path Parameters