# [RFC] Vetting cURL connections with CURLOPT_OPENSOCKETFUNCTION

- Thread: https://externals.io/message/133085
- Original archive: https://news-web.php.net/php.internals/133085
- Started by Xavier Leune on 2026-11-12T10:42:54+00:00
- Messages: 2
- RFC: Vetting cURL connections with CURLOPT_OPENSOCKETFUNCTION (Under discussion) — https://externals.io/rfc/curl_opensocketfunction.md
- Reply by email to internals@lists.php.net
- More for agents: search with https://externals.io/search.md?q={query} · RFCs at https://externals.io/rfc.md · JSON API at https://externals.io/api · documentation at https://externals.io/llms.txt

---

## #133085 — Xavier Leune — 2026-11-12T10:42:54+00:00

URL: https://externals.io/message/133085#133085

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

---

## #133086 — Nicolas Grekas — 2026-11-12T10:56:49+00:00

URL: https://externals.io/message/133085#133086 · In reply to: #133085

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, 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
