{"uuid": "e8e642dd-a2c6-4724-b4ab-610d2175169a", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "CVE-2026-41608", "type": "seen", "source": "https://gist.github.com/prajnak/06b8b2c74ce416478b826c460d0061f8", "content": "# Apache Thrift 2026 CVEs are invisible to purl-matching SCA\n\n**TL;DR** \u2014 The August 2026 Apache Thrift advisories (fixed in `0.24.0`) carry\n**CPE identifiers only**. No `pkg:pypi` / `pkg:maven` purl is ever produced, so any\nSCA that matches code libraries by ecosystem+purl finds nothing to attach them to.\nAn image with `thrift==0.22.0` installed from PyPI scans **clean**.\n\nThis is reproducible in two `curl` calls against public APIs \u2014 no scanner licence,\nno private data.\n\n---\n\n## Reproduce in 30 seconds (no Docker needed)\n\n### 1. Ask OSV whether PyPI `thrift` 0.22.0 has known vulnerabilities\n\n```bash\ncurl -s -X POST https://api.osv.dev/v1/query \\\n  -d '{\"package\":{\"name\":\"thrift\",\"ecosystem\":\"PyPI\"},\"version\":\"0.22.0\"}'\n```\n\n```json\n{}\n```\n\nEmpty. Zero vulnerabilities.\n\n### 2. Now confirm the advisory exists \u2014 and explicitly names Python\n\n```bash\ncurl -s https://api.osv.dev/v1/vulns/CVE-2026-41608 | jq '{id, summary}'\n```\n\n```json\n{\n  \"id\": \"CVE-2026-41608\",\n  \"summary\": \"Apache Thrift: Unbounded Zlib Decompression in Python THeaderTransport\"\n}\n```\n\nThe advisory exists. Its own summary says **Python**. `0.22.0` is in range\n(fixed in `0.24.0`). Yet step 1 returns nothing.\n\n---\n\n## Root cause\n\n```bash\ncurl -s https://api.osv.dev/v1/vulns/CVE-2026-41608 | jq '.affected[0]'\n```\n\n```json\n{\n  \"ranges\": [\n    {\n      \"type\": \"GIT\",\n      \"repo\": \"https://github.com/apache/thrift\",\n      \"events\": [\n        { \"introduced\": \"0\" },\n        { \"fixed\": \"6d2ec95f450b4ce404afb8929ff479e68770141a\" }\n      ],\n      \"database_specific\": {\n        \"cpe\": \"cpe:2.3:a:apache:thrift:*:*:*:*:*:*:*:*\",\n        \"extracted_events\": [\n          { \"introduced\": \"0\" },\n          { \"fixed\": \"0.24.0\" }\n        ],\n        \"source\": \"CPE_RANGE\"\n      }\n    }\n  ],\n  \"versions\": [\"last-before-0-10\"]\n}\n```\n\nThree things to notice:\n\n1. **There is no `package` key.** No ecosystem, no name, no purl. A purl-matching\n   engine has nothing to key on.\n2. `ranges[0].type` is **`GIT`**, not `ECOSYSTEM` \u2014 the range is a commit SHA,\n   auto-derived from a CPE (`\"source\": \"CPE_RANGE\"`).\n3. `versions` is `[\"last-before-0-10\"]` \u2014 a placeholder artifact of that\n   conversion, not a usable version list.\n\nThe upstream advisory was filed against the Apache Thrift *product* via CPE.\nNothing downstream ever binds it to `pkg:pypi/thrift`, `pkg:maven/org.apache.thrift/libthrift`,\n`pkg:npm/thrift`, or any other ecosystem package.\n\n---\n\n## Reproduce against a container image\n\n```dockerfile\n# Dockerfile\nFROM python:3.12-slim\nRUN pip install --no-cache-dir thrift==0.22.0\n```\n\n```bash\ndocker build -t thrift-cve-repro:0.22.0 .\n```\n\nConfirm the package and its metadata are present and discoverable:\n\n```bash\ndocker run --rm thrift-cve-repro:0.22.0 \\\n  sh -c 'pip show thrift | head -2; ls /usr/local/lib/python3.12/site-packages | grep -i thrift'\n```\n\n```\nName: thrift\nVersion: 0.22.0\nthrift\nthrift-0.22.0.dist-info\n```\n\nNow scan that image with any purl-matching SCA.\n\n**Expected:** at least CVE-2026-41608, CVE-2026-48586, CVE-2026-43871 reported,\nfixed in `0.24.0`.\n\n**Actual:** zero findings for `thrift`. The SBOM correctly lists\n`thrift 0.22.0 (PyPI)` \u2014 discovery works fine. Only *matching* fails.\n\n---\n\n## Which advisories actually apply to a Python deployment\n\nApache Thrift published 18 advisories fixed in `0.24.0`. **Only three name the\nPython bindings.** These are the ones a PyPI `thrift` install is exposed to:\n\n| CVE | Bindings named in the advisory | CVSS |\n|---|---|---|\n| CVE-2026-41608 | **Python** only | 7.5 |\n| CVE-2026-48586 | C++, Java, **Python**, Go, D, C/GLib | 7.5 |\n| CVE-2026-43871 | **Python**, Go, PHP, Java | 7.5 |\n\nAll three: `AV:N/AC:L/PR:N/UI:N/C:N/I:N/A:H` \u2014 remote unauthenticated\ndenial of service against anything deserializing untrusted Thrift payloads.\n\nThe other 15 do **not** apply to a Python deployment. They are C++, c_glib,\nSwift, Rust, Node.js, Go and Java binding bugs \u2014 `TSSLSocket::matchName`,\n`web_server.js`, `TFramedTransport` uint32 overflow, and similar. None of that\ncode ships in the PyPI package.\n\nVerify any of them yourself:\n\n```bash\nfor c in CVE-2026-55971 CVE-2026-58023 CVE-2026-41604 CVE-2026-41602; do\n  curl -s \"https://api.osv.dev/v1/vulns/$c\" | jq -r '\"\\(.id)  \\(.summary)\"'\ndone\n```\n\n```\nCVE-2026-55971  Apache Thrift: C++ ZLIB heap buffer overflow (write) in THeaderTransport::untransform()\nCVE-2026-58023  Apache Thrift: c_glib heap out-of-bounds read in transport\nCVE-2026-41604  Apache Thrift: Swift Range crash in skip()\nCVE-2026-41602  Apache Thrift: Go TFramedTransport uint32 overflow\n```\n\n---\n\n## What a fix should look like\n\nAdd ecosystem/purl mappings **per binding**, driven by the bindings each advisory\nnames in its own text:\n\n```\nCVE-2026-41608  -&gt;  pkg:pypi/thrift                         (&lt; 0.24.0)\nCVE-2026-48586  -&gt;  pkg:pypi/thrift, pkg:maven/org.apache.thrift/libthrift, ...\nCVE-2026-43871  -&gt;  pkg:pypi/thrift, pkg:maven/..., pkg:composer/...\n```\n\n## What a fix should **not** look like\n\nDo not blanket-CPE-match ecosystem packages against `cpe:2.3:a:apache:thrift`.\n\nA scanner that does this reports **all 18** against `thrift 0.22.0`, including\n~14 rated Critical \u2014 because it inherits per-binding CVSS scores from bindings\nthat aren't installed. That is roughly **5\u00d7 over-reported** for a Python\ndeployment, and triaging it back down to the real three costs more time than the\noriginal finding saved.\n\nThe gap here is precision *and* recall: three real findings are missed, and the\nnaive fix would introduce fifteen false ones.\n\n---\n\n## Wider question\n\nApache Thrift is unlikely to be unique. The pattern is: **one upstream project,\nmany language bindings, advisories filed at product level with CPE only**.\nOther multi-binding projects (protobuf, gRPC, Avro, Arrow, ICU, libxml2\nwrappers) plausibly share it.\n\nTwo things that would help anyone consuming this data:\n\n1. A way to report on SBOM components whose only matching advisories are\n   CPE-only \u2014 i.e. *where am I blind right now?*\n2. Systematic per-binding purl mapping where an advisory's text names the\n   affected bindings, rather than case-by-case backfill.\n\n---\n\n*Verified 2026-08-27 against `api.osv.dev`. Everything above uses public APIs and\npublic package data; no scanner output or private environment data is included.*\n", "creation_timestamp": "2026-08-27T11:48:03.095065Z"}