[RFC] Vetting cURL connections with CURLOPT_OPENSOCKETFUNCTION

From: Date: Thu, 12 Nov 2026 10:42:54 +0000
Subject: [RFC] Vetting cURL connections with CURLOPT_OPENSOCKETFUNCTION
Groups: php.internals 
Request: Send a blank email to internals+get-133085@lists.php.net to get a copy of this message
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, 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

« previous php.internals (#133085) next »