TL;DR

unstructured is the library most Python RAG stacks call to turn a URL or file into text chunks. Its partition(url=…), partition_html(url=…) and partition_md(url=…) fetched the URL with requests.get() and no host validation, and returned the response body as element text — a full-read SSRF from wherever the ingestion service runs: loopback admin APIs, internal HTTP services, and cloud metadata endpoints. CVE-2026-71428, CVSS 3.1 9.3 (AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:L/A:N), affects ≥ 0.4.7, < 0.24.0, fixed in 0.24.0. The vendor advisory itself names the downstream reach: "the de-facto URL ingestion layer for LangChain UnstructuredURLLoader, LlamaIndex UnstructuredReader, Chainlit, and many agent frameworks." If an agent tool or an "add a source by URL" form ends in partition(), the URL an attacker (or a prompt-injected model) supplies is fetched from inside your network.

What happened

Unstructured-IO published GHSA-4mvj-m6j5-pmf7 on 2026-07-10 (reporter: hayato1121); NVD's record followed on 2026-08-20 with the same 9.3 score, CNA GitHub, CWE-918 and CWE-601. Three code paths took a caller-supplied URL straight to the network:

  • unstructured/partition/auto.pyfile_and_type_from_url() behind partition(url=…)
  • unstructured/partition/html/partition.pypartition_html(url=…)
  • unstructured/partition/md.pypartition_md(url=…)

None of them checked for private, loopback or link-local destinations, and the fetched body became the returned Element text, so the attacker reads the response rather than merely triggering a request. The advisory lists the three usual routes around naive filters — direct private-IP targets, an HTTP redirect from a public host, and DNS rebinding — and the usual targets: internal services and the metadata endpoints of GCP, Azure, Oracle, DigitalOcean and EC2 IMDSv1. The fix (PR #4388, release 0.24.0) adds destination validation; the advisory offers no workaround for older versions.

Why this is an agent-framework issue and not a "library CVE." The URL usually is not typed by an administrator. In a RAG product it comes from the end user's "import this page" box; in an agent it comes from a tool call the model decided to make after reading something. Both are attacker-reachable inputs, and the same ingestion path is wired in by name in LangChain's UnstructuredURLLoader and LlamaIndex's UnstructuredReader. This joins the read-SSRF entries already tracked here for LangChain's SitemapLoader and for the MCP servers whose fetch tools trusted their input — the model's ability to choose a URL turns every unvalidated fetch into a network pivot.

No exploitation in the wild is reported by the vendor or NVD as of 2026-09-15.

Am I affected?

pip show unstructured 2>/dev/null | grep -i '^version'      # < 0.24.0 is vulnerable
pip index versions unstructured 2>/dev/null | head -1
# Do you pass URLs into partition()?
grep -rn 'partition(\|partition_html(\|partition_md(\|UnstructuredURLLoader\|UnstructuredReader' --include='*.py' . 2>/dev/null | grep -i 'url' | head -20

You are affected if any code path — a web form, an API parameter, an agent tool — can put an attacker-influenced URL into partition*(url=…) on a version below 0.24.0, and the process runs somewhere with anything worth reaching on its network (a cloud VM with a metadata service, a VPC with internal services, a machine with local admin ports).

If you are affected

  1. Upgrade to unstructured ≥ 0.24.0. Pin it; transitive pins from langchain-community or llama-index-readers-file may not have moved.
  2. Independently of the library fix, validate URLs at your own boundary (resolve, reject private/loopback/link-local, re-check after redirects) and run ingestion workers with IMDSv2-only or no metadata access and minimal network egress.
  3. If the endpoint was internet-facing on a cloud host, review ingestion logs for requests to 169.254.169.254, localhost, RFC 1918 ranges, or unexpected redirect chains; if found, rotate the instance's credentials. → playbooks/rotating-cloud-credentials.md

Prevention

Sources