Re: [RFC] Vetting cURL connections with CURLOPT_OPENSOCKETFUNCTION
| From: | Xavier Leune | Date: | Thu, 12 Nov 2026 12:47:07 +0000 |
| Subject: | Re: [RFC] Vetting cURL connections with CURLOPT_OPENSOCKETFUNCTION | ||
| References: | 1 2 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-133087@lists.php.net to get a copy of this message | ||
Hi Nicolas,
Le jeu. 12 nov. 2026 Ã 11:57, Nicolas Grekas <nicolas.grekas+php@gmail..com>
a écrit :
> Hi Xavier,
>
> thanks for the RFC
>
> Le jeu. 12 nov. 2026 Ã 11:45, Xavier Leune <
> xavier.leune+internals@gmail.com> a écrit :
>
>> Hi internals,
>>
>> Following the "SSRF protection in curl" thread, I'd like to start the
>> discussion on this RFC:
>> https://wiki.php.net/rfc/curl_opensocketfunction
>>
>> It adds CURLOPT_OPENSOCKETFUNCTION to ext/curl, with libcurl's name and
>> value. The callback is invoked after DNS resolution and before connect(),
>> once for each socket libcurl is about to create. It returns a bool that
>> allows or refuses the connection:
>>
>> curl_setopt($ch, CURLOPT_OPENSOCKETFUNCTION,
>> function (CurlHandle $handle, CurlAddressFamily $family, string
>> $address, ?int $port): bool {
>> // Only publicly routable addresses.
>> return $family !== CurlAddressFamily::Unix
>> && filter_var($address, FILTER_VALIDATE_IP,
>> FILTER_FLAG_GLOBAL_RANGE) !== false;
>> });
>>
>> This is the point where an SSRF policy can be enforced reliably. It
>> applies to the address actually about to be contacted, on every redirect
>> hop and for every address libcurl tries, whatever spelling the URL used and
>> whatever DNS answered.
>>
>> Compared to the earlier proposals:
>>
>> - It returns a bool, as Sjoerd suggested in the thread. No socket is
>> exposed to userland, and ext/curl gains no dependency on ext/sockets.
>> - It keeps libcurl's option name, rather than a PHP-specific one.
>> - The handle comes first, as in the other ext/curl callbacks. A new
>> CurlAddressFamily enum (IpV4, IpV6, Unix) tells a policy whether $address
>> is an IP or a socket path. $port is null for UNIX sockets.
>> - The return type can later be widened, and parameters appended, without
>> breaking existing callbacks.
>>
>> The implementation is in https://github.com/php/php-src/pull/25414,kƒ
>> ÖM´¤`ó~|pG"
>> which supersedes GH-23657.
>>
>> A polyfill, curl_hardening (https://github.com/xavierleune/curl-hardening),
>> implements the same API for PHP 8.3 to 8.6. It stays inert on a PHP that
>> provides the option natively.
>>
>> As discussed with Levi, streams are left to a separate RFC (see Future
>> Scope).
>>
>> Thanks to everyone who took part in the previous thread and reviewed the
>> PRs. Feedback is welcome.
>>
>> Regards,
>> Xavier
>>
>
>
> As I put on the PR, I'd drop the enum and use an int port:
>
> function (CurlHandle $handle, string $address, int $port): bool
>
> The address already tells IPv4, IPv6 and UNIX sockets apart, and
> filter_var() rejects a socket path anyway. The enum can even mislead:
> ::ffff:127.0.0.1 is reported as IpV6.
> For UNIX sockets, port 0 is what CURLOPT_PREREQFUNCTION and
> CURLINFO_PRIMARY_PORT already report/. Using null would be inconsistent and
> thus surprizing.
>
> Cheers,
> Nicolas
>
>
>
>
>
Thanks for your feedback. I dropped the enum, you're right the new api is
more consistent with other curl callbacks.
PR, RFC and polyfill are up to date,
Xavier