CVE-2026-39376: Fastfeedparser Denial of Service Vulnerability

HIGH
Advisory Identifier: GHSA-4gx2-pc4f-wq37
CVSS 7.5
CWE-400 CWE-674
Published
Advisory Summary

CVE-2026-39376 is a HIGH (CVSS 7.5) denial of service vulnerability (CWE-400: Uncontrolled Resource Consumption) affecting Fastfeedparser libraries in the PyPI ecosystem. The issue involves fastfeedparser has an infinite redirect loop dos via meta-refresh chain. Remediation is available by upgrading affected packages to version 0.5.10.

CVE-2026-39376 at a Glance

CVE Identifier CVE-2026-39376
GHSA Identifier GHSA-4gx2-pc4f-wq37
Severity HIGH
CVSS v3.1 Base Score 7.5
CWE Classification CWE-400: Uncontrolled Resource Consumption
Affected Product Fastfeedparser
Package Ecosystem PyPI
Published Date
Last Updated

Security Impact

CVSS: 7.5

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required None (PR:N)
User Interaction None (UI:N)
Scope Unchanged (S:U)
Confidentiality None (C:N)
Integrity None (I:N)
Availability High (A:H)

How to Fix CVE-2026-39376

To remediate this vulnerability, upgrade all affected Fastfeedparser dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

fastfeedparser fixed: 0.5.10
pip install fastfeedparser>=0.5.10
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

pip show fastfeedparser

How to Check if You Are Affected

To determine whether your Python environment is affected by CVE-2026-39376, inspect your installed packages using pip:

pip show fastfeedparser

Analyze the terminal output to locate any version of Fastfeedparser in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 0.5.10), your application is affected.

Note: The pip show fastfeedparser command checks currently installed packages in your active Python environment.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-400: Uncontrolled Resource Consumption, where the product does not properly control the allocation and maintenance of a limited resource. In Fastfeedparser, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

### Summary When `parse()` fetches a URL that returns an HTML page containing a `<meta http-equiv="refresh">` tag, it recursively calls itself with the redirect URL — with no depth limit, no visited-URL deduplication, and no redirect count cap. An attacker-controlled server that returns an infinite chain of HTML meta-refresh responses causes unbounded recursion, exhausting the Python call stack and crashing the process. This vulnerability can also be chained with the companion SSRF issue to reach internal network targets after bypassing the initial URL check. ### Details `parse()` catches `ValueError` on XML parse failure, extracts a meta-refresh URL from the HTML response via `_extract_meta_refresh_url()`, and tail-calls itself with that URL. The recursive call is unconditional — there is no maximum redirect depth, no set of already-visited URLs, and no guard against self-referential or looping redirects. **`fastfeedparser/main.py` — `parse()` (recursive sink):** ```python def parse(source: str | bytes, ...) -> FastFeedParserDict: is_url = isinstance(source, str) and source.startswith(("http://", "https://")) if is_url: content = _fetch_url_content(source) try: return _parse_content(content, ...) except ValueError as e: ... redirect_url = _extract_meta_refresh_url(content, source) if redirect_url is None: raise return parse(redirect_url, ...) # ← unconditional recursion, no depth limit ``` `_extract_meta_refresh_url()` uses `urljoin(base_url, match.group(1))` so relative, protocol-relative (`//host/path`), and absolute URLs in the `content=` attribute are all followed. ### PoC No live server required. The following monkeypatches `_fetch_url_content` to return an infinite HTML meta-refresh chain and confirms unbounded recursion: ```python import fastfeedparser.main as m call_count = 0 _orig = m._fetch_url_content def mock_fetch(url): global call_count call_count += 1 if call_count > 10: raise RuntimeError(f"Stopped at call {call_count}") next_url = f"http://169.254.169.254/step{call_count}/" return f"""<html><head> <meta http-equiv="refresh" content="0; url={next_url}"> </head><body>not a feed</body></html>""".encode() m._fetch_url_content = mock_fetch try: m.parse("http://attacker.com/loop") except RuntimeError as e: print(f"CONFIRMED infinite loop: {e}") finally: m._fetch_url_content = _orig print(f"Total fetches before stop: {call_count}") # Output: # CONFIRMED infinite loop: Stopped at call 11 # Total fetches before stop: 11 ``` Each recursive call performs a real HTTP request (30 s timeout), HTML parsing, and a Python stack frame allocation. With Python's default recursion limit of 1000 and a 30 s per-request timeout, a single attacker request can hold a server thread busy for up to ~8 hours before a `RecursionError` is raised. **SSRF chain variant:** The first response can be legitimate HTML redirecting to an internal address (`http://192.168.1.1/`), letting the redirect loop also serve as an SSRF bypass for targets that would otherwise be blocked by application-level URL validation applied only to the initial URL. ### Impact This is a denial-of-service vulnerability with a secondary SSRF-chaining impact. Any application that accepts user-supplied feed URLs and calls `fastfeedparser.parse()` is affected — including RSS aggregators, feed preview services, and "subscribe by URL" features. An attacker with no authentication can: - Hold a server worker thread indefinitely (one request per attacker connection) - Crash the worker process via `RecursionError` after ~1000 redirects - Use the redirect chain to pivot SSRF requests to internal network targets

CVE-2026-39376 Affected Fastfeedparser Versions

1 component(s)
Fixed Versions:
0.5.10
Product / Package Ecosystem Affected Versions Fixed Version
fastfeedparser
PyPI
< 0.5.10
0.5.10

References

Frequently Asked Questions

What is CVE-2026-39376?
CVE-2026-39376 is a security vulnerability in Fastfeedparser involving FastFeedParser has an infinite redirect loop DoS via meta-refresh chain under CWE-400: Uncontrolled Resource Consumption.
Which Fastfeedparser versions are affected?
Affected versions include releases prior to 0.5.10 across affected packages: fastfeedparser.
What version fixes CVE-2026-39376?
Upgrading to 0.5.10 (or the latest patched release for your respective branch) resolves the vulnerability.
Is fastfeedparser affected?
Yes, fastfeedparser is affected in releases prior to 0.5.10. Upgrade your dependency to 0.5.10 or later.
What is the CVSS score of CVE-2026-39376?
CVE-2026-39376 has a CVSS v3.1 base score of 7.5 (HIGH) with vector string CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H.
What CWE is associated with CVE-2026-39376?
CVE-2026-39376 is classified under CWE-400: Uncontrolled Resource Consumption (The product does not properly control the allocation and maintenance of a limited resource.).
How can I check PyPI dependencies for CVE-2026-39376?
Run the dependency tree command: 'pip show fastfeedparser'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-4gx2-pc4f-wq37",
  "modified" : "2026-06-06T00:24:06Z",
  "published" : "2026-04-08T00:12:26Z",
  "aliases" : [ "CVE-2026-39376" ],
  "summary" : "FastFeedParser has an infinite redirect loop DoS via meta-refresh chain",
  "details" : "### Summary\nWhen `parse()` fetches a URL that returns an HTML page containing a `<meta http-equiv=\"refresh\">` tag, it recursively calls itself with the redirect URL — with no depth limit, no visited-URL deduplication, and no redirect count cap. An attacker-controlled server that returns an infinite chain of HTML meta-refresh responses causes unbounded recursion, exhausting the Python call stack and crashing the process. This vulnerability can also be chained with the companion SSRF issue to reach internal network targets after bypassing the initial URL check.\n\n\n### Details\n`parse()` catches `ValueError` on XML parse failure, extracts a meta-refresh URL from the HTML response via `_extract_meta_refresh_url()`, and tail-calls itself with that URL. The recursive call is unconditional — there is no maximum redirect depth, no set of already-visited URLs, and no guard against self-referential or looping redirects.\n\n**`fastfeedparser/main.py` — `parse()` (recursive sink):**\n```python\ndef parse(source: str | bytes, ...) -> FastFeedParserDict:\n    is_url = isinstance(source, str) and source.startswith((\"http://\", \"https://\"))\n    if is_url:\n        content = _fetch_url_content(source)\n    try:\n        return _parse_content(content, ...)\n    except ValueError as e:\n        ...\n        redirect_url = _extract_meta_refresh_url(content, source)\n        if redirect_url is None:\n            raise\n        return parse(redirect_url, ...)   # ← unconditional recursion, no depth limit\n```\n\n`_extract_meta_refresh_url()` uses `urljoin(base_url, match.group(1))` so relative, protocol-relative (`//host/path`), and absolute URLs in the `content=` attribute are all followed.\n\n### PoC\nNo live server required. The following monkeypatches `_fetch_url_content` to return an infinite HTML meta-refresh chain and confirms unbounded recursion:\n\n```python\nimport fastfeedparser.main as m\n\ncall_count = 0\n_orig = m._fetch_url_content\n\ndef mock_fetch(url):\n    global call_count\n    call_count += 1\n    if call_count > 10:\n        raise RuntimeError(f\"Stopped at call {call_count}\")\n    next_url = f\"http://169.254.169.254/step{call_count}/\"\n    return f\"\"\"<html><head>\n<meta http-equiv=\"refresh\" content=\"0; url={next_url}\">\n</head><body>not a feed</body></html>\"\"\".encode()\n\nm._fetch_url_content = mock_fetch\n\ntry:\n    m.parse(\"http://attacker.com/loop\")\nexcept RuntimeError as e:\n    print(f\"CONFIRMED infinite loop: {e}\")\nfinally:\n    m._fetch_url_content = _orig\n    print(f\"Total fetches before stop: {call_count}\")\n\n# Output:\n# CONFIRMED infinite loop: Stopped at call 11\n# Total fetches before stop: 11\n```\n\nEach recursive call performs a real HTTP request (30 s timeout), HTML parsing, and a Python stack frame allocation. With Python's default recursion limit of 1000 and a 30 s per-request timeout, a single attacker request can hold a server thread busy for up to ~8 hours before a `RecursionError` is raised.\n\n**SSRF chain variant:** The first response can be legitimate HTML redirecting to an internal address (`http://192.168.1.1/`), letting the redirect loop also serve as an SSRF bypass for targets that would otherwise be blocked by application-level URL validation applied only to the initial URL.\n\n\n\n### Impact\nThis is a denial-of-service vulnerability with a secondary SSRF-chaining impact. Any application that accepts user-supplied feed URLs and calls `fastfeedparser.parse()` is affected — including RSS aggregators, feed preview services, and \"subscribe by URL\" features. An attacker with no authentication can:\n\n- Hold a server worker thread indefinitely (one request per attacker connection)\n- Crash the worker process via `RecursionError` after ~1000 redirects\n- Use the redirect chain to pivot SSRF requests to internal network targets",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "PyPI",
      "name" : "fastfeedparser"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "0.5.10"
      } ]
    } ],
    "database_specific" : {
      "last_known_affected_version_range" : "<= 0.5.9"
    }
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/kagisearch/fastfeedparser/security/advisories/GHSA-4gx2-pc4f-wq37"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-39376"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/kagisearch/fastfeedparser"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/pypa/advisory-database/tree/main/vulns/fastfeedparser/PYSEC-2026-60.yaml"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-400", "CWE-674" ],
    "severity" : "HIGH",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-04-08T00:12:26Z",
    "nvd_published_at" : "2026-04-07T20:16:32Z"
  }
}