The Fix Was Closed by a Bot: CrewAI CVE-2026-112308 and the Selenium Tool That Skips the SSRF Check

On 19 October 2026 at 16:17:06Z, NVD published CVE-2026-112308 (CWE-918, server-side request forgery), sourced from disclosure@vulncheck.com, against crewai-tools through 1.15.28 — which is every current release. The defect sits in SeleniumScrapingTool: the tool accepts an http(s) URL, much of it under the language model’s control at runtime, and navigates a local headless Chrome (WebDriver) to it with no private- or reserved-address check. An attacker who can influence the agent’s tool input — the record names prompt injection explicitly — can point that browser at loopback, RFC1918 space, link-local addresses or the cloud metadata endpoint and read the results back through the scrape output. The scores are CVSS 3.1 7.4 (AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:N/A:N) and CVSS 4.0 8.3. There is no patched release, and — this is the part of the story worth sitting with — a working fix has existed since 8 August and was closed without human review.

CrewAI is not a side project: 59,900 stars, 8,806 forks, MIT-licensed, and one of the most widely deployed agent frameworks in production. Its whole product is giving a model tools that act on the world. A scraping tool that fetches arbitrary URLs is therefore not an accessory; it is the confused-deputy surface itself. We traced this record end to end: the CVE, the vulnerable source on main today, the unmerged fix, its auto-close, the prior SSRF record against the project’s own safety helper, and the release tags.

The missing gate, verified on main today

The vulnerable file is lib/crewai-tools/src/crewai_tools/tools/selenium_scraping_tool/selenium_scraping_tool.py. Read on main first-hand on 20 October 2026, it contains no reference to validate_url anywhere. The two validation layers that do exist check format, not destination. The Pydantic schema validator (validate_website_url) rejects empty input, over-long input (2,048 characters), non-http(s) schemes, malformed URLs and whitespace. The request path (_make_request) re-checks only that the URL is non-empty and matches ^https?:// — and then calls self.driver.get(url) followed by a sleep. That is the whole gate: a scheme check standing in front of a full browser. http://169.254.169.254/latest/meta-data/ is a perfectly well-formed http URL, so it sails through both layers and lands in Chrome running on the crew’s host.

The project already owns the correct helper. crewai_tools/security/safe_path.py exposes validate_url, which resolves the hostname with getaddrinfo and rejects non-global addresses, blocks file:// outright, and — worth knowing when hardening — can be silently disabled by setting the CREWAI_TOOLS_ALLOW_UNSAFE_PATHS environment variable. The sibling scrape tools were already routed through it: the fix PR’s body documents that FirecrawlScrapeWebsiteTool and ScrapflyScrapeWebsiteTool call the helper, and we verified the Firecrawl call sites first-hand. Selenium, the one tool that launches a real browser with ambient host network access, was never wired in. The highest-privilege fetcher got the thinnest check.

The fix that died in triage

Pull request crewAIInc/crewAI#6870, titled “fix(tools): block private URLs in SeleniumScrapingTool”, opened 8 August 2026 from a first-time contributor’s fork. The proposal was exactly right in shape: route Selenium URLs through the existing validate_url helper at three layers — schema validation, fixed-URL construction, and _make_request — explicitly described as defense in depth for the fixed-URL schema path, where constructor-supplied URLs bypass the schema validator entirely. Two files changed, +44/−3, with unit tests rejecting 127.0.0.1, 169.254.169.254 and fixed-URL localhost. An automated reviewer left its walkthrough the same day. And then nothing human ever happened: on 17 September 2026 at 06:05:35Z, github-actions[bot] closed the PR — four seconds after posting the project’s form notice that first-time contributors need an associated open issue before review. A bot labelled it, a bot closed it, forty days later the CVE published, and main still carries the vulnerable code.

There is a temptation to file this as bad luck, and it is worth resisting. The process gate that killed the fix exists to keep triage orderly; here it functioned as a vulnerability-preservation mechanism with a 40-day half-life. The contributor did the two things every maintainer asks for — reused the project’s own safety helper instead of inventing a check, and covered the bypass path (fixed URLs) the naive patch would have missed — and the reward was an automated close citing a paperwork rule. Every operator running crewai-tools with the Selenium tool attached has been exposed since, through 1.15.28, released 14 October, five days before the CVE number arrived.

The helper had its own CVE first

This is the second SSRF record against the same tool family, and the first one indicted the safety net itself. CVE-2026-62240 (published 13 July 2026, Analyzed, CVSS 3.1 7.4) found that validate_url performed one-shot DNS resolution and blocklist checks and then returned the original URL unchanged — so redirects to internal addresses and DNS-rebinding techniques walked straight past it into internal services and cloud metadata. Fixed in 1.15.1 (commit 5d4851e, via PR #6331 and issue #6520). Read the two records together and the pattern is uncomfortable: in July the blessed check was bypassable, and in October we learn the browser tool never called it at all. Fixing the helper without auditing its callers left the worst fetcher unguarded — the classic “patched the function, not the call graph” outcome we have now measured in Mercury’s blocklist and the fifteen-CVE agent-platform batch, where gates were invoked and their answers discarded.

What to do

There is no fixed release: PyPI and the GitHub releases page both agree 1.15.28 (14 October 2026) is latest, and main is still unpatched, so upgrading cannot close this. Until a fix ships: do not attach SeleniumScrapingTool to any crew whose input can be influenced by untrusted content — retrieved pages, user submissions, tickets, model-generated URLs. If you must scrape, prefer the sibling tools that call validate_url on a crewai-tools ≥ 1.15.1 (which carries the 62240 fix), and confirm CREWAI_TOOLS_ALLOW_UNSAFE_PATHS is unset in every environment — it disables the helper globally. Treat the browser’s host as the trust boundary it actually is: egress rules that deny loopback, RFC1918 and 169.254.169.254 from agent workers blunt this whole class regardless of which tool regresses next. If the Selenium tool was reachable with untrusted input, assume internal endpoints and instance metadata were fetchable and rotate cloud credentials. None of the related records — 112308, 62240, or the August FileWriter/NL2SQL pair — appears in the CISA KEV catalogue (checked directly, 20 October 2026, 1,748 entries, zero hits), and no exploitation has been reported; given the unpatched state, read that as absence of signal, not absence of risk.

Our verification was read-only. The CVE-2026-112308 identifier, publication timestamp of 2026-10-19T16:17:06.887Z, Awaiting Analysis status, disclosure@vulncheck.com source, CWE-918 assignment, CVSS 3.1 and 4.0 vectors and scores, affected range and three references were read first-hand from the NVD 2.0 API, as were the CVE-2026-62240 publication timestamp, Analyzed status, description, score and references. The vulnerable source (absence of validate_url, the two format-only checks, the driver.get(url) sink), the validate_url implementation including the CREWAI_TOOLS_ALLOW_UNSAFE_PATHS escape hatch, and the Firecrawl call sites were read first-hand from main via the GitHub API. PR #6870’s title, timestamps, fork, file counts, body text and the bot close event with its first-time-contributor notice were read first-hand from the GitHub API. Release tags and timestamps (1.15.28 on 14 October 2026) and repository metadata (59,900 stars, 8,806 forks, MIT, not archived) were read first-hand from the GitHub API and PyPI. KEV status was checked directly against the published catalogue. Brave Search was used for discovery only. We installed nothing, ran no crew, pointed no browser at any internal address and sent no third-party request beyond reading the cited public records.

Sources: