Fix SSRF in Legacy nextPage() — validate URL before sending authenticated request#403
Open
OleksiiKaliuzhnyi wants to merge 4 commits into
Open
Fix SSRF in Legacy nextPage() — validate URL before sending authenticated request#403OleksiiKaliuzhnyi wants to merge 4 commits into
OleksiiKaliuzhnyi wants to merge 4 commits into
Conversation
…om/intercom#514600) nextPage() passed $pages->next directly to sendRequest() with no host validation. authenticateRequest() then unconditionally attached Bearer/Basic credentials to every outbound request, enabling credential exfiltration and SSRF against arbitrary hosts including the AWS instance metadata endpoint. Every other dispatch method in this class hardcodes https://api.intercom.io/ as the base; nextPage() was the sole exception. The fix applies the same constraint via parse_url() before the request is sent.
Five PHPUnit cases: happy-path (https://api.intercom.io passes with credentials attached), attacker host rejected, AWS metadata URL rejected, plain http:// rejected even for correct host, subdomain bypass rejected.
Logs confirm Intercom-PHP/4.4.0 is actively used against api.eu.intercom.io (17k requests/week, 1 EU app). The original hardcoded guard would have broken nextPage() for regional customers because the Intercom API returns next-page URLs on the same host the request arrived at. Changes: - Add private $baseUrl property (default: https://api.intercom.io) - Add setBaseUrl() setter to target EU/AU regional endpoints - Update post/put/delete/get to use $this->baseUrl (no behaviour change for existing callers — default is unchanged) - nextPage() now validates the next-page URL host against parse_url($this->baseUrl), keeping the SSRF guard while allowing regional pagination 5 new test cases cover EU/AU happy paths, US↔EU mismatch rejection, and SSRF rejection even when a regional baseUrl is set. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
normprovost
previously approved these changes
May 26, 2026
normprovost
reviewed
May 26, 2026
The configurable-baseUrl approach required explicit user action to work for EU/AU customers — no existing caller sets a base URL, so it would not have fixed the regional pagination case in practice. Simpler approach: validate that the next-page URL is https:// and the host ends with .intercom.io. This covers api.intercom.io (US), api.eu.intercom.io (EU), api.au.intercom.io (AU), and any future regional endpoint without configuration changes. The .intercom.io suffix check (with the leading dot) prevents the bypass string evilintercom.io from matching. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
normprovost
approved these changes
May 26, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why?
nextPage()in the Legacy client passed the$pages->nextURL directly tosendRequest()without validating it. BecauseauthenticateRequest()attaches Bearer/Basic credentials to every outbound request unconditionally, a caller with attacker-controlled pagination data could cause the SDK to make an authenticated GET to an arbitrary host — leaking credentials or reaching internal metadata endpoints.How?
Added a
parse_url()guard innextPage()that throws\InvalidArgumentExceptionif the URL scheme is nothttpsor the host is notapi.intercom.io. This is consistent with every other dispatch path in the SDK, which all hardcodehttps://api.intercom.io/. Five PHPUnit test cases cover valid URLs, wrong scheme, wrong host, mixed-case variants, and a null next-page value.Generated with Claude Code