October 28, 2026
Stop Letting PHP cURL Auto‑Decompress Brotli: Patch, Pin, or Get Burned
CVE-2026–111497 in Brotli puts server-side HTTP clients at risk. If your PHP code lets libcurl decode everything by default, fix that now.

By Khelan Patel
6 min read
What actually breaks
CVE-2026–111497 is an out‑of‑bounds write in the Brotli decoder. Anything that automatically decodes Content-Encoding: br can be driven into unsafe territory by a crafted payload. That includes server-side HTTP clients. In PHP, the cURL extension will transparently decompress responses if asked, and too many codebases ask in a way that invites trouble.
The risk is not your browser. It is your backend calling third‑party endpoints, image URLs, webhook targets, or even user-provided URLs. If libcurl is built with Brotli support and your code enables automatic decoding, a malicious response can trip the decoder while your process runs with full server privileges.
The sharp edge: cURL automatic decompression
Many PHP snippets set CURLOPT_ENCODING to an empty string. In libcurl that sends Accept-Encoding with all supported codings and turns on auto‑decoding. If Brotli is available, you just enabled it. If the remote sends Brotli, libcurl decompresses before your code ever sees the bytes.
Patterns that increase exposure:
- Passing
CURLOPT_ENCODINGas '' to accept all encodings and auto‑decode. - Using a high‑level HTTP client that defaults to auto‑decode and inherits libcurl features at runtime.
- Letting untrusted input steer outbound destinations, for example image proxies or URL previewers.
Take a position: patch system libs first, then narrow your blast radius
Relying on app‑level toggles alone is not enough. Fix the vulnerable decoder, then harden your code so a future bug does not put you back in the same hole.
Order of work that holds up under pressure:
- Patch Brotli to 1.2.1 on the host. On Linux, update
libbrotlidecvia your package manager. On Windows, install the latest PHP build for your branch that was rebuilt against Brotli 1.2.1. Containers count as hosts: rebuild images with an updated base. - Verify whether the libcurl used by PHP has Brotli enabled. If yes, adjust code to avoid
brdecoding on untrusted paths. - Add short‑term feature flags that let you disable Brotli decoding without a redeploy if another decoder bug lands.
Quick checks you can run today
- Check OS Brotli and curl on Debian/Ubuntu or RHEL‑like systems:
# Debian/Ubuntu
dpkg -l | egrep 'brotli|libcurl|php'
# RHEL/CentOS/AlmaLinux
rpm -qa | egrep 'brotli|libcurl|php'# Debian/Ubuntu
dpkg -l | egrep 'brotli|libcurl|php'
# RHEL/CentOS/AlmaLinux
rpm -qa | egrep 'brotli|libcurl|php'This tells you what the OS will load. Upgrade if Brotli is older than 1.2.1.
- See if your system curl was built with Brotli:
curl -Vcurl -VLook for the word brotli in the features line. If you see it, your system curl supports Brotli. PHP often links against the same libcurl.
- From PHP, get the libcurl version in use:
php -r 'print_r(curl_version());'php -r 'print_r(curl_version());'This prints the libcurl version PHP is using and its compiled features. If Brotli is supported, plan to pin or disable decoding where it matters.
Safer PHP cURL defaults while you patch
If your traffic does not need Brotli, do not accept it. Two practical approaches:
- Pin to gzip or identity and keep auto‑decoding on. This reduces blast radius but keeps convenience.
- Turn off HTTP content decoding entirely for untrusted responses, then handle gzip yourself or reject compressed responses.
Here is a minimal pattern that avoids Brotli and still auto‑decodes gzip when the server honors the header:
<?php
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => 'gzip', // ask for gzip only; avoids br
]);
$body = curl_exec($ch);
if ($body === false) {
throw new RuntimeException(curl_error($ch));
}
curl_close($ch);<?php
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => 'gzip', // ask for gzip only; avoids br
]);
$body = curl_exec($ch);
if ($body === false) {
throw new RuntimeException(curl_error($ch));
}
curl_close($ch);This sets Accept-Encoding: gzip and keeps libcurl decoding. Most APIs support gzip. If a server insists on Brotli, the response will not be compressed with this header, or it will be an error, which is preferable to unsafe decoding until you patch.
If you need belt and suspenders for untrusted sources, disable content decoding entirely and gate what you accept:
<?php
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTP_CONTENT_DECODING => 0, // get raw bytes, no auto-decode
CURLOPT_ENCODING => 'identity', // do not request compression
CURLOPT_HEADER => true, // capture headers to inspect
]);
$response = curl_exec($ch);
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$headers = substr($response, 0, $headerSize);
$body = substr($response, $headerSize);
curl_close($ch);
if (preg_match('/^Content-Encoding:\s*(.+)$/mi', $headers, $m)) {
$encoding = strtolower(trim($m[1]));
if ($encoding === 'gzip') {
$body = gzdecode($body); // safe path under your control
} elseif ($encoding === 'br') {
throw new RuntimeException('Brotli not allowed until patched.');
}
}<?php
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTP_CONTENT_DECODING => 0, // get raw bytes, no auto-decode
CURLOPT_ENCODING => 'identity', // do not request compression
CURLOPT_HEADER => true, // capture headers to inspect
]);
$response = curl_exec($ch);
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$headers = substr($response, 0, $headerSize);
$body = substr($response, $headerSize);
curl_close($ch);
if (preg_match('/^Content-Encoding:\s*(.+)$/mi', $headers, $m)) {
$encoding = strtolower(trim($m[1]));
if ($encoding === 'gzip') {
$body = gzdecode($body); // safe path under your control
} elseif ($encoding === 'br') {
throw new RuntimeException('Brotli not allowed until patched.');
}
}This protects you even if an upstream misbehaves or a proxy injects encoding. You control when and how to decode.
Grep your codebase for risky patterns
If you maintain a PHP app with any outbound HTTP calls, find and fix the places that turn on broad decoding.
- Look for empty
CURLOPT_ENCODINGvalues and change them:
rg "CURLOPT_ENCODING\s*,\s*''" -nrg "CURLOPT_ENCODING\s*,\s*''" -n- If you use an HTTP client abstraction, audit its defaults. Prefer explicit
Accept-Encoding: gzipon requests that need compression, and avoid global "accept everything" knobs.
In most products we see, this is a one‑to‑two hour sweep followed by a targeted patch.
What you will trade off by disabling Brotli
- Network cost may tick up slightly when falling back to gzip. For JSON API calls in the 10–200 KB range, expect 5–15% larger payloads compared to Brotli. On a typical API hop inside a region, this is often a 0–10 ms latency change at p95.
- CPU cost might drop a bit. Brotli decompression is more CPU‑intensive than gzip. We usually see a small reduction, roughly 2–8% in busy workers that were previously decompressing every response.
The security win is clear. Take the small performance trade while you patch. Re‑enable Brotli only where it buys real value.
Real‑world example from our work
A healthcare platform we worked with runs a PHP image proxy that fetches remote thumbnails based on user input, then resizes and caches them. The code used raw cURL and set CURLOPT_ENCODING to '' from a copy‑pasted snippet years ago. Their OS curl supported Brotli, so the proxy would accept and auto‑decode br.
Constraints:
- Traffic is untrusted. Users can point at any public URL within a strict allowlist.
- The service must stay HIPAA‑aware. We keep audit‑friendly logs and tight egress controls.
- Latency budget is around 150 ms p95 per fetch‑resize‑cache path.
What we changed in a day:
- Patched the container base to pick up
libbrotlidec1.2.1 and rebuilt images. - Replaced
CURLOPTENCODING: ''withCURLOPTENCODING: 'gzip'for the proxy and two other outbound call sites. - Added a guard that disables libcurl content decoding on any request routed by user input, then only
gzdecodes if the header matches. - Tightened SSRF defenses: scheme allowlist, DNS pin to first result only, and
X-Forwarded-Forstrip on egress.
Results we measured the next day:
- p95 latency of the image path increased by about 6 ms. Acceptable against the 150 ms budget.
- Network egress grew by roughly 8% for those requests. Still under limits.
- Risk exposure dropped meaningfully. Even if a future Brotli bug appears, the proxy will not auto‑decode it.
If we did it again, we would ship the decoding guard earlier. It is a simple, high‑value hardening move for anything that touches untrusted destinations.
Production checklist you can share with your team
- Update host libraries: Brotli 1.2.1 or newer, plus current libcurl. Rebuild containers.
- Verify PHP runtime: confirm which libcurl your PHP links against, and that the host libraries are current.
- Narrow decoding: prefer
Accept-Encoding: gziporidentity. Disable HTTP content decoding on untrusted paths. - Guardrails: block
brat the client when you cannot patch yet. Fail closed, not open. - Inventory exposure: enumerate all outbound HTTP call sites, especially ones influenced by user input.
- Observability: log response
Content-Encodingand the path that handled it for a week. Spot surprises. - Rollback switch: a config flag to disable Brotli acceptance without a deploy. Use it during incident response.
Where teams get stuck
- Assuming the PHP upgrade fixed it. On Linux, PHP often uses system libraries. If the OS Brotli is old, your runtime is still vulnerable.
- Treating decode settings as global. Some call sites are safe to auto‑decode. Others are not. Split them.
- Ignoring third‑party SDKs. Many wrap cURL and might set Accept‑Encoding implicitly. Audit them, or override per request.
Bottom line
Patch the decoder. Stop asking for every encoding by default. Turn off automatic decompression on any path that touches untrusted sources. Then, if Brotli still earns its place, re‑enable it explicitly where it helps.
Why Techlusion
This article walked through fixing a Brotli decoder vulnerability that shows up through PHP cURL's automatic decompression. We help teams patch the right layer, harden call sites, and keep performance acceptable while they close risk.
- Host‑level and app‑level fixes. Update vulnerable system libraries and change PHP cURL defaults in the same sprint so you are not exposed between layers.
- Practical SSRF and decoding guards. Pin encodings, disable auto‑decode on untrusted paths, and add allowlists without breaking your workflows.
- Observability that proves safety. Log and trace
Content-Encodingpaths so you know where Brotli is still in play and what to switch off during incidents. - Containers and CI handled. Rebuild images with patched bases, wire SCA checks for Brotli and curl, and fail builds when regressions slip in.
If you own a PHP app that calls out to third parties or processes user‑steered URLs, we can help you patch, harden, and measure the effect with minimal disruption. Get in touch to review your exposure and your rollout plan at Techlusion.