Skip to main content
Blog/ HTTP2Shell (CVE-2026-111497): How to check if your Python web scraper is affected
October 24, 2026 · 13 min read

HTTP2Shell (CVE-2026-111497): How to check if your Python web scraper is affected

Joel Olawanle
Joel Olawanle
HTTP2Shell (CVE-2026-111497): How to check if your Python web scraper is affected

Your Python web scraper could be using a vulnerable Brotli decoder without importing brotli anywhere in your code. HTTP libraries such as Requests, HTTPX, and Scrapy can decompress Brotli-encoded responses automatically, which means a malicious compressed response from an untrusted website may reach vulnerable code inside your application.

The issue is CVE-2026-111497, commonly called Node2Shell and sometimes HTTP2Shell. Although it first gained attention through Node.js, the vulnerability is in Google's Brotli decoder and also affects Python packages that use the same underlying library.

In this tutorial, we'll check whether your Python environment contains affected Brotli packages, examine how an HTTP client handles compressed responses, and apply the available fixes. We'll also build a small Python script that checks the installed versions without downloading or processing any malicious payloads.

What is HTTP2Shell (CVE-2026-111497)?

HTTP2Shell is a name used for the broader impact of CVE-2026-111497, a memory corruption vulnerability in Google's Brotli decoder.

Brotli is a compression format commonly used to reduce the size of HTTP responses. Websites can compress HTML, JavaScript, CSS, JSON, and other content before sending it to a client. The receiving application decompresses the response to recover the original data.

The vulnerability occurs when the decoder processes a specially crafted Brotli stream containing an invalid block switch. Instead of rejecting that condition, the affected decoder can continue writing data beyond an internal buffer.

This is an out-of-bounds write that can corrupt memory and potentially lead to process crashes, disclosure of sensitive memory, or arbitrary code execution.

The vulnerability was publicly addressed in the Node.js security releases on October 21, 2026. It received a CVSS v4.0 score of 9.2, although the Node.js project classifies the issue as High severity in its own advisory.

The underlying vulnerability is not limited to Node.js. The GitHub security advisory for CVE-2026-111497 also identifies affected Python packages.

For Python developers, the relevant packages and fixes are:

Python packageFirst fixed release
Brotli1.2.1
brotlicffi1.2.1.0

The vulnerability affects the decoder, not the HTTP/2 protocol itself. Applications using HTTP/1.1 can also be exposed when they decompress malicious Brotli content.

An application must encounter attacker-controlled data that reaches the affected decoder for the vulnerability to be exploitable. Merely using Python, Requests, or Scrapy does not mean an attacker can automatically execute code in your application.

Why Python web scrapers can be affected

A typical web scraping workflow sends an HTTP request, receives a response, and passes the returned content to an HTML parser or extraction function.

What happens between receiving the response and reading the HTML is easy to overlook.

Consider a webpage that supports Brotli compression.

The client might send this request header:

Accept-Encoding: gzip, deflate, br

The br value tells the server that the client supports Brotli-encoded responses.

The server can respond with:

Content-Encoding: br

The client then decompresses the response body before returning the content to application code.

If the Brotli decoder is vulnerable and the remote server supplies a specially crafted compressed stream, the decoder itself becomes the attack surface.

The application doesn't need to call brotli.decompress() directly.

Requests and urllib3

Requests documents that it automatically decodes Brotli-encoded responses when either brotli or brotlicffi is installed.

Requests uses urllib3 for HTTP communication, and urllib3 also supports Brotli when one of those packages is available.

A script like this may use Brotli decompression even though it never imports Brotli:

import requests

response = requests.get(
    "https://example.com",
    timeout=15,
)

print(response.status_code)

The relevant decoder can be reached inside the HTTP library, before the application starts processing the returned content.

HTTPX

HTTPX supports Brotli decompression through the optional httpx[brotli] dependency group, which uses brotli or brotlicffi.

Applications that install this optional support can decode Brotli responses without explicitly handling decompression in their own request-processing code.

Scrapy

Scrapy also supports Brotli through its HTTP compression middleware.

Starting with Scrapy 2.18, Brotli support became a required part of its dependency setup, with the appropriate package installed for the Python implementation.

The middleware handles compressed responses before passing their contents to spiders.

This makes Scrapy's installed dependency versions relevant to CVE-2026-111497, even if your spider only extracts ordinary HTML.

The takeaway is that checking application imports isn't enough. You also need to inspect the dependencies installed in the Python environment where the scraper runs.

Check whether Brotli is installed in your Python environment

Let's begin with the simplest check.

Open a terminal in the virtual environment used by your scraper and run:

python -m pip show Brotli brotlicffi

This checks whether either distribution is installed and displays its package metadata.

You can also inspect the packages with:

python -m pip list

Look for Brotli or brotlicffi and compare their versions against the patched releases.

If the installed PyPI distribution is below the relevant fixed version, plan to update it. That finding means the installed package version is below the published security fix, not that you've proven your application is remotely exploitable.

Run the check in the environment your application actually uses. A developer's laptop, a local virtual environment, and a production container can have different dependencies.

You can confirm which interpreter your terminal is using with:

python -c "import sys; print(sys.executable)"

For containerized applications, perform the inventory inside the built image or running container. Checking only the local project directory or a developer workstation can miss a vulnerable dependency in the deployed environment.

Check whether Requests advertises Brotli support

Before building the version checker, let's examine an ordinary HTTP request.

Requests prepares default headers based on its installed capabilities. We can inspect those headers without sending a network request.

Create check_encoding.py:

# check_encoding.py
from requests import Request, Session

request = Request("GET", "https://example.com")
prepared = Session().prepare_request(request)

print(prepared.headers.get("Accept-Encoding"))

In a Python environment containing Requests and Brotli, this produced:

gzip, deflate, br

The br value confirms that the prepared request advertises Brotli support.

It doesn't establish that the installed decoder is vulnerable, or that a particular website will return a Brotli response. Those are separate questions.

It also isn't a complete security test. A server can send an encoded response even when the client didn't explicitly request that encoding, and application-specific code may decode response bodies elsewhere.

What this check demonstrates is that an HTTP client can enable Brotli handling through an installed dependency, without an explicit Brotli import in the application.

Build a Python script to identify affected Brotli versions

Manually checking package versions works for one environment, but a small script makes the process repeatable.

We'll use importlib.metadata to inspect installed Python distributions and packaging.version to compare their versions with the first patched releases.

Install the packaging library if it isn't already available:

python -m pip install packaging

Create check_http2shell_python.py:

# check_http2shell_python.py
from importlib.metadata import PackageNotFoundError, version
import sys

from packaging.version import InvalidVersion, Version

FIXED = {"Brotli": "1.2.1", "brotlicffi": "1.2.1.0"}


def evaluate(installed_version: str, fixed_version: str) -> str:
    try:
        return (
            "PATCHED VERSION"
            if Version(installed_version) >= Version(fixed_version)
            else "BELOW FIXED VERSION"
        )
    except InvalidVersion:
        return "UNRECOGNIZED VERSION"


def main() -> int:
    print(f"Python: {sys.version.split()[0]}")
    issues = False

    for name, fixed in FIXED.items():
        try:
            installed = version(name)
        except PackageNotFoundError:
            print(f"{name}: not installed")
            continue

        status = evaluate(installed, fixed)
        print(f"{name}: {installed} | {status} (fix: {fixed})")

        if status != "PATCHED VERSION":
            issues = True

    return 1 if issues else 0


if __name__ == "__main__":
    raise SystemExit(main())

The script checks both distributions independently because an environment may contain either package or both.

It also distinguishes an absent package from an installed version below the published fix.

Run the script:

python check_http2shell_python.py

Here is the actual output from a Python environment containing Brotli 1.2.0:

Python: 3.13.5
Brotli: 1.2.0 | BELOW FIXED VERSION (fix: 1.2.1)
brotlicffi: not installed

The installed Brotli version is below the first patched PyPI release, so the script identifies it as requiring attention.

The script also returns a nonzero exit status when it finds an installed distribution below its fixed version or a version it cannot interpret. This makes it suitable for use as a simple dependency check in a development or CI environment.

It doesn't attempt exploitation, decompress an untrusted payload, or scan a running server.

There are limits to this check: it examines installed Python distribution metadata, not every possible copy of the Brotli decoder. An application might also use a system library, an embedded binary, or another runtime that needs separate evaluation.

Find out why Brotli is installed

If your scraper uses Requests, HTTPX, or Scrapy, Brotli might not appear among the dependencies you've explicitly added to your project.

It may have been installed through another package or dependency group.

To investigate, install pipdeptree:

python -m pip install pipdeptree

Then run:

pipdeptree --reverse --packages Brotli

The reverse dependency view shows which installed packages declare a dependency on Brotli.

If your environment uses brotlicffi, replace Brotli with brotlicffi.

This helps identify whether the dependency was installed directly or introduced by another library. If it's transitive, updating the parent package or its dependency constraints may be necessary to keep the fixed version installed consistently.

You should also inspect your project's dependency files, such as requirements.txt, pyproject.toml, poetry.lock, or uv.lock.

A version installed manually into a virtual environment can be replaced by an older pinned version the next time that environment is built.

For projects deployed through Docker, the same applies to dependencies installed during image builds.

Upgrade vulnerable Brotli packages

For a Python environment using the affected PyPI packages, the permanent fix is to install a version containing the patched decoder.

If Brotli is installed, upgrade it with:

python -m pip install --upgrade "Brotli>=1.2.1"

If your application uses brotlicffi, upgrade that package instead:

python -m pip install --upgrade "brotlicffi>=1.2.1.0"

You don't need to install both packages if your application only uses one. Update whichever affected distributions are present.

After upgrading, rerun:

python check_http2shell_python.py

The checker should report PATCHED VERSION for the upgraded distribution once its installed version meets the minimum fix.

You can also run:

python -m pip check

This checks whether installed package dependencies have compatible requirements. It doesn't perform a vulnerability scan, but it can reveal dependency conflicts introduced by an upgrade.

For a project using a lockfile, update the corresponding version constraints and regenerate the lockfile using the project's package manager. Then rebuild the runtime environment or container image and verify the installed versions again.

If the affected library is already loaded by a running application, restart or redeploy that application to ensure the process uses the updated code.

What about system-installed Brotli packages?

The commands above apply to the Python packages distributed through PyPI.

Operating system packages may use different versioning and backport security fixes.

For example, Debian's DSA-6582-1 advisory identifies a fixed Brotli package for Debian 13 as 1.1.0-2+deb13u1.

That package's upstream version number is still 1.1.0, but its distribution-specific release contains the security fix.

If you're dealing with system-managed libraries, follow the operating system vendor's security advisory rather than treating a Python package version comparison as the final authority.

The same distinction applies to software that bundles Brotli internally. Updating the standalone Python package won't necessarily update a separate native library embedded in another application.

Can you temporarily disable Brotli decompression?

If you can't update immediately, preventing untrusted Brotli data from reaching the affected decoder can reduce exposure while you arrange the permanent fix.

One possible measure is to stop advertising Brotli support in outbound HTTP requests.

For example, with Requests:

import requests

response = requests.get(
    "https://example.com",
    headers={"Accept-Encoding": "gzip, deflate"},
    timeout=15,
)

This asks the server to return a different supported encoding.

However, changing the request header is not a complete remediation. A remote server can still send a Brotli-encoded response, and other components in your application may independently decompress Brotli content.

A more reliable temporary restriction would involve disabling Brotli decoding at every relevant entry point or removing the affected decoder where the application can operate without it.

Removing the package isn't always practical because another dependency may require it. It also doesn't address copies bundled elsewhere.

The preferred fix remains upgrading to a patched library or runtime.

Don't rely only on vulnerability scanners

A vulnerability scanner is useful, but it's not a substitute for understanding which libraries your application uses.

The advisory information for CVE-2026-111497 expanded quickly after the Node.js disclosure. Python-specific entries were subsequently added to vulnerability databases, including PYSEC-2026-4322 for Brotli and PYSEC-2026-4323 for brotlicffi.

You can use pip-audit to check installed Python packages against known advisories.

Install it with:

python -m pip install pip-audit

Then audit the current environment:

python -m pip_audit

For projects with a requirements file, you can also audit that file directly:

python -m pip_audit -r requirements.txt

The tool's findings depend on its configured advisory source and the vulnerability data available when it runs.

Because CVE records and package mappings can be updated after a vulnerability is disclosed, keep the version check alongside your broader dependency-auditing process.

A clean audit result doesn't prove that an application is free from vulnerabilities, especially when native libraries or separate runtime components are involved.

What if your application uses a web scraping API?

Applications that retrieve content from external websites have several responsibilities beyond sending an HTTP request.

They may need to manage response compression, redirects, JavaScript rendering, cookies, browser interactions, and parsing. Each of those introduces dependencies and data-processing paths that require maintenance.

One option is to delegate webpage retrieval and extraction to a web-data API instead of implementing every part of the retrieval stack yourself.

For example, Spidra Scrape retrieves webpage content and returns Markdown or structured JSON through an API. Python applications can consume that output without maintaining their own browser-based scraping infrastructure.

For workflows involving many URLs, Spidra also provides Batch scrape. Our guide to scraping multiple URLs in Python shows how to process URL lists and handle failed extractions.

Using an external API changes which components your application is responsible for, but it doesn't eliminate the need for secure dependency management.

You still need to update your application runtime, validate returned data, and ensure that any external content is handled as untrusted input. An API provider's underlying security also needs to be assessed separately.

The practical decision is whether you want to maintain your own retrieval and extraction infrastructure or use an API for those operations while concentrating on how your application processes the returned data.

What this check does and doesn't prove

The Python script we've built is deliberately a dependency inventory tool.

It answers whether the current Python interpreter has an installed Brotli distribution below the published patched version. It does not establish whether an attacker can reach the vulnerable decoder in a particular application.

Your actual exposure depends on the installed libraries, how they're used, whether untrusted compressed data reaches the decoder, and the versions deployed in production.

Checking the packages, understanding your HTTP client's decompression behavior, and applying the relevant vendor updates are more reliable than trying to reproduce the vulnerability against a live system.

There is no need to send malformed Brotli payloads to websites or production services to confirm that an environment contains an affected package.

Conclusion

HTTP2Shell demonstrates how a vulnerability in a shared native library can affect applications written in different languages.

For Python web scrapers, the risk can be easy to miss because Requests, HTTPX, and Scrapy may handle Brotli decompression without exposing that operation directly in application code.

We checked the installed packages, examined how Requests advertises Brotli support, built a Python version checker, and reviewed the steps needed to deploy patched dependencies.

The same approach applies to other shared libraries used inside web-data pipelines: inspect the installed components, identify where untrusted input reaches them, and verify fixes in the environment that actually runs your application.

If you want to retrieve and extract webpage content without maintaining your own browser-based scraping infrastructure, you can try Spidra with your existing Python applications.

Frequently asked questions

HTTP2Shell is a name used for the broader impact of CVE-2026-111497, an out-of-bounds write vulnerability in Google's Brotli decoder. It is also commonly called Node2Shell. The issue can affect multiple runtimes and libraries that decompress attacker-controlled Brotli data, including certain Python packages.

Share this article

Start scraping for free.

Get 300 free credits to explore Spidra. Build your first scraper in minutes, not hours. Upgrade anytime as you scale.

We build features around real workflows. Usually within days.