fix: reject path-traversal sequences in HttpClient::resolveUrl#379
fix: reject path-traversal sequences in HttpClient::resolveUrl#379gjtorikian wants to merge 3 commits intomainfrom
Conversation
WorkOS service methods interpolate caller-supplied IDs directly into the
request path with PHP string interpolation (e.g. "connections/{$id}") without
URL-encoding. libcurl normalizes "../" before transmission, so a value like
"../webhook_endpoints/wh_target" causes the SDK to issue a request against a
different WorkOS endpoint while still presenting the application's API key.
This adds a defense-in-depth runtime guard. The structural fix (per-segment
rawurlencode in the generator) lands in oagen-emitters separately; this guard
remains valuable even after that ships, and protects existing PHP SDK users
on the current generator output.
Reject paths whose segments are "." or "..", and paths containing "?", "#",
or any \\x00-\\x1f control character. Throw \\InvalidArgumentException
without echoing the offending path to avoid log injection.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Greptile SummaryThis PR adds a defense-in-depth path-traversal guard to The implementation correctly addresses the two bypass vectors raised in previous review threads — single-encoded ( Confidence Score: 4/5Safe to merge — the guard is correctly implemented and all previously flagged encoding bypass vectors are resolved. No P0 or P1 findings. The security-sensitive nature of the change and the absence of a positive test for percent-encoded-but-safe paths keep this at 4 rather than 5, but the implementation logic is sound. No files require special attention beyond normal review of the guard logic in lib/HttpClient.php. Important Files Changed
|
| foreach (explode('/', $path) as $segment) { | ||
| if ($segment === '.' || $segment === '..') { | ||
| throw new \InvalidArgumentException( | ||
| 'WorkOS request path contains a relative segment ("." or ".."). Refusing to send the request to avoid cross-resource redirection.', | ||
| ); | ||
| } | ||
| } |
There was a problem hiding this comment.
Percent-encoded dot-segment bypass
The guard splits on / and checks for literal '.' and '..', but RFC 3986-compliant HTTP clients (including libcurl, which Guzzle uses by default) normalize percent-encoded equivalents: %2e → . and %2E%2E → ... An attacker who controls the ID can pass %2e%2e and produce connections/%2e%2e/webhook_endpoints/wh_target, which satisfies this check but is resolved by libcurl to webhook_endpoints/wh_target — the same cross-resource redirect the guard is meant to prevent.
Decoding the path before the segment check closes the gap:
foreach (explode('/', $path) as $segment) {
$decoded = rawurldecode($segment);
if ($decoded === '.' || $decoded === '..') {
throw new \InvalidArgumentException(
'WorkOS request path contains a relative segment ("." or ".."). Refusing to send the request to avoid cross-resource redirection.',
);
}
}Encoded variants like %2e%2e and %2f could bypass the literal "."/".." and "?"/"#"/CRLF checks if a downstream proxy or the receiving server normalized them. Decode the path once with rawurldecode() before both checks so encoded dots, slashes, query/fragment markers, and control characters are all caught. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace single rawurldecode() with a decode-until-stable loop so that double-encoded sequences like %252e%252e cannot slip past the assertSafePath check. Add test cases for double-encoded traversal, slash, query character, and null byte variants.
|
@greptile review |
Summary
Defense-in-depth runtime guard against a path-traversal / cross-resource-API-call vulnerability.
WorkOS service methods interpolate caller-supplied ids directly into request paths with PHP string interpolation (e.g.
"connections/{$id}") withoutrawurlencode-ing them. libcurl normalizes../segments before transmission, so a value like../webhook_endpoints/wh_targetcauses the SDK to issue a request against a different WorkOS endpoint while still authenticated with the application's API key — i.e. forged cross-resource API requests under the application's bearer token.This PR hardens
HttpClient::resolveUrl()to reject paths containing:.or..segments (between/boundaries) — blocksconnections/../webhook_endpoints/...-style cross-resource redirection?or#— blocks query/fragment injection (queries go through the$queryargument)\x00–\x1fcontrol characters — blocks CRLF / null-byte injectionThrows
\InvalidArgumentExceptionwithout echoing the offending path back, to avoid log injection from CRLF-laden inputs.Why this lands separately from the structural fix
The structural fix (per-segment
rawurlencodeat code-generation time) lands inworkos/oagen-emittersand will require a regen of this SDK. This runtime guard is valuable independently:RequestOptions::baseUrloverrides as well.lib/HttpClient.php,tests/HttpClientTest.php) carry@oagen-ignore-file, so the upcoming regen won't clobber the guard.Versioning
Patch release. Public API surface (method signatures, return types, exception types) is unchanged. The new exception path only fires on inputs that were either being exploited or already producing wrong/404 behavior — no legitimate caller passes
..or control characters as a WorkOS id.Test plan
vendor/bin/phpunit— 272 passed, including 17 new tests for the guard (8 unsafe-input cases × 2 entrypoints + 1 positive test that legitimate ids with dots inside a segment still work)vendor/bin/phpstan analyseon changed files — clean🤖 Generated with Claude Code