{"id":"CVE-2026-107290","aliases":["GHSA-fpf4-vwcp-v4hp","PYSEC-2026-4277","PYSEC-2026-4286"],"url":"https://o3.security/vulnerability/CVE-2026-107290","summary":"Pydantic AI: Event loop blocked by quadratic title extraction in `web_fetch`","details":"### Summary\n\nThe local web-fetch tool (`web_fetch_tool`, also used as the `WebFetch` capability's local fallback) processed responses with several steps whose running time grows quadratically with the size of certain server-controlled inputs, and ran them on the event loop: decoding the body with whichever charset the server declared, extracting the page title with a backtracking regular expression, and converting the HTML to markdown. An application that exposes this tool to untrusted prompts can be steered to fetch an attacker-controlled page of a megabyte or two that blocks the event loop for minutes, stalling every other coroutine in the process — other agent runs, other requests being served — for the duration.\n\nThis is an **availability** issue only. SSRF protections and the download size limit introduced in GHSA-v2xh-2vp8-57h8 are unaffected; that limit bounds how much is downloaded, not how long the response takes to process.\n\n### Details\n\nTitle extraction used a backtracking pattern over the raw response body, so a body made of repeated unterminated tag openings cost time proportional to the square of its size. The HTML-to-markdown conversion had the same shape in three of its steps: normalizing whitespace, stripping preformatted blocks, and numbering ordered lists all took time proportional to the square of a run of spaces or a list's length. All of it ran on the event loop, and the regex steps hold the interpreter lock even when moved off it, so the whole process paid for the size of a server-controlled response.\n\nThe response body was also decoded on the event loop with the codec named by the `charset` parameter of the response's `Content-Type`, looked up in Python's codec registry. That registry includes `punycode`, whose decoder takes time proportional to the square of its input: a response of about one megabyte labelled `charset=punycode` blocked the event loop for roughly half a minute, with no HTML required. The registry also includes codecs that aren't text encodings at all, such as `rot_13` and `base64_codec`; a response labelled with one of those raised an unexpected exception out of the tool, aborting the agent run that fetched it.\n\nSeparately, the HTML-to-markdown conversion recursed once per nested element, so a page nested a few hundred elements deep raised a `RecursionError` out of the tool, aborting the agent run that fetched it. A JSON response nested deeper than the interpreter allows did the same. These only affect that one run.\n\n### Who Is Affected\n\nYou are affected if your application registers the local web-fetch tool (or relies on the `WebFetch` capability's local fallback) and exposes the agent to untrusted prompts. `allowed_domains` narrows the exposure to pages on those domains but does not remove it. Applications that only fetch developer-controlled URLs are not exposed to the model-chosen attack path.\n\n### Remediation\n\nUpgrade to a patched version. The title is now found with a single linear scan, the conversion steps above run in linear time, and decoding, title extraction and conversion all run in a worker thread. A charset naming a codec that isn't a text encoding, and a page too deeply nested to convert, are reported back to the model as a failed fetch instead of aborting the run; a JSON body too deeply nested to parse is returned as plain text.\n\n### Credits\n\nReported privately by @BrianWillows, whose report covered the quadratic title extraction. The response decoding, the codecs that are not text encodings, and the quadratic steps in the HTML-to-markdown conversion were found while fixing it.","published":"2026-10-08T16:09:04.272Z","modified":"2026-10-21T02:30:25.818323530Z","cvss":{"score":6.5,"severity":"MEDIUM","vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H"},"epss":null,"cisaKev":null,"exploitsKnown":null,"affectedPackages":[{"ecosystem":"PyPI","name":"pydantic-ai","fixedVersion":"1.107.6"},{"ecosystem":"PyPI","name":"pydantic-ai","fixedVersion":"2.44.0"},{"ecosystem":"PyPI","name":"pydantic-ai-slim","fixedVersion":"1.107.6"},{"ecosystem":"PyPI","name":"pydantic-ai-slim","fixedVersion":"2.44.0"}],"fix":{"url":"https://github.com/pydantic/pydantic-ai/commit/2faa6181d8a17d83bc9516d035c5270db8730fa0","label":"pydantic/pydantic-ai@2faa618"},"references":[{"type":"WEB","url":"https://github.com/pydantic/pydantic-ai/releases/tag/v1.107.6"},{"type":"WEB","url":"https://github.com/pydantic/pydantic-ai/releases/tag/v2.44.0"},{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/107xxx/CVE-2026-107290.json"},{"type":"ADVISORY","url":"https://github.com/pydantic/pydantic-ai/security/advisories/GHSA-fpf4-vwcp-v4hp"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-107290"},{"type":"FIX","url":"https://github.com/pydantic/pydantic-ai/commit/2faa6181d8a17d83bc9516d035c5270db8730fa0"},{"type":"FIX","url":"https://github.com/pydantic/pydantic-ai/commit/9cdc952e4c3319e85a3e04f2de49fbbb765bd38b"},{"type":"FIX","url":"https://github.com/pydantic/pydantic-ai/commit/a93ea5226be1e93ae13131ae3f22287190411389"},{"type":"FIX","url":"https://github.com/pydantic/pydantic-ai/commit/c3fd1cc1f15fdbf750d78e4e3ec1e8b4d6a3d920"},{"type":"FIX","url":"https://github.com/pydantic/pydantic-ai/commit/fb92ccfc3ca2735dab877e2ed73856681bf72ad1"},{"type":"FIX","url":"https://github.com/pydantic/pydantic-ai/pull/8397"},{"type":"FIX","url":"https://github.com/pydantic/pydantic-ai/pull/8399"},{"type":"FIX","url":"https://github.com/pydantic/pydantic-ai/pull/8418"},{"type":"FIX","url":"https://github.com/pydantic/pydantic-ai/pull/84332"},{"type":"FIX","url":"https://github.com/pydantic/pydantic-ai/pull/8434"},{"type":"WEB","url":"https://github.com/pydantic/pydantic-ai/pull/8433"},{"type":"PACKAGE","url":"https://github.com/pydantic/pydantic-ai"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-10-21T02:30:25.818323530Z"}}