CVE-2026-46497
SSRF via sitemap-derived URLs in Crawlee for Python
Crawlee is a web scraping and browser automation library. From version 1.0.0 to before version 1.7.0, Crawlee is vulnerable to SSRF via sitemap-derived URLs. This issue has been patched in version 1.7.0.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- None
- User interaction
- Passive
What an attacker can affect
- Confidentiality
- Low
- Integrity
- None
- Availability
- None
Exploit likelihood
What each source reports
CVE List Score used
2.3
SSRF via sitemap-derived URLs in Crawlee for Python
Crawlee is a web scraping and browser automation library. From version 1.0.0 to before version 1.7.0, Crawlee is vulnerable to SSRF via sitemap-derived URLs. This issue has been patched in version 1.7.0.
- Record
CVE-2026-46497- CVSS 4.0
- 2.3 Low
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:L/VI:N/VA:N/SC:L/SI:N/SA:N - Weakness
- CWE-918
- Updated
- 2026-11-02
EUVDEUVD-2026-36067
2.3
Crawlee is a web scraping and browser automation library. From version 1.0.0 to before version 1.7.0, Crawlee is vulnerable to SSRF via sitemap-derived URLs. This issue has been patched in version 1.7.0.
- Record
EUVD-2026-36067- CVSS 4.0
- 2.3 Low
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:L/VI:N/VA:N/SC:L/SI:N/SA:N - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-3r75-xc34-5f44
2.3
Crawlee for Python: SSRF via sitemap-derived URLs
Overview
- Vulnerability type: Blind SSRF
- Affected components:
src/crawlee/_utils/sitemap.py,src/crawlee/_utils/robots.py,src/crawlee/request_loaders/_sitemap_request_loader.py, and all built-in HTTP clients. - Trigger: an attacker-controlled sitemap or
robots.txtcontaining a URL that points to an internal host (layer 1) or uses a non-http scheme (layer 2).
Two-layer SSRF via sitemap-derived URLs:
1) Cross-host HTTP SSRF
Base case, affects every HTTP client.** Sitemap entries and robots.txt Sitemap: directives were accepted regardless of the host they pointed to. A sitemap on example.com could push http://internal.corp/admin into the crawler's queue, and the configured HTTP client would dispatch the request.
2) Non-HTTP scheme SSRF
Escalation, only CurlImpersonateHttpClient.** Nested-sitemap fetching dispatches the URL straight to the HTTP client, bypassing the Request construction step where Pydantic enforces http(s). Combined with the libcurl-backed CurlImpersonateHttpClient, this lets gopher://, file://, dict://, ftp://, etc., through.
Root cause
Crawlee already validates URL schemes through Pydantic's AnyHttpUrl (via validate_http_url in src/crawlee/_utils/urls.py) wherever a crawl target is materialised as a Request: the Request.url field is declared as Annotated[str, BeforeValidator(validate_http_url), Field(frozen=True)]. Anything that becomes a Request is therefore guaranteed to be http(s).
Two parts of the sitemap pipeline sidestepped this property in different ways:
1) Sitemap-derived URLs were enqueued without any host policy
SitemapRequestLoader took every <urlset><url><loc> entry, wrapped it in Request.from_url (which accepts any valid http(s) URL), and pushed the result into the request queue. RobotsTxtFile.get_sitemaps() returned every Sitemap: directive verbatim. Neither imposed any host check against the parent sitemap or robots.txt URL, so an attacker controlling that content could push internal-network HTTP URLs into the queue and have them crawled by whichever HTTP client was configured.
2) Nested sitemap fetching bypassed the Request chokepoint entirely
When _XmlSitemapParser encountered <sitemapindex><sitemap><loc>…</loc></sitemap></sitemapindex>, or when RobotsTxtFile.parse_sitemaps forwarded Sitemap: directives into the same pipeline, _fetch_and_process_sitemap dispatched the URL directly to the HTTP client:
async with http_client.stream(
sitemap_url,
method='GET',
headers=SITEMAP_HEADERS,
proxy_info=proxy_info,
timeout=timeout,
) as response:
...
No Request was constructed, so the Pydantic validator never ran. Before the fix, the HTTP clients' own send_request() and stream() methods did not call validate_http_url either, so a non-http(s) scheme could pass straight through to the backend client.
The non-HTTP escalation in layer 2 is specific to CurlImpersonateHttpClient, which is backed by curl-cffi / libcurl and speaks gopher, file, dict, ftp, and other non-HTTP protocols. The other clients shipped with Crawlee (HttpxHttpClient, ImpitHttpClient, PlaywrightHttpClient) reject non-http(s) schemes at their own backend layer, regardless of what Crawlee passes in, so they were only affected by layer 1.
Vulnerable paths
Layer 1 — cross-host HTTP (all HTTP clients)
- Source: an attacker-controlled sitemap that lists internal URLs under
<urlset><url><loc>or<sitemapindex><sitemap><loc>, or an attacker-controlledrobots.txtthat lists internal URLs underSitemap:. - Sink: the configured HTTP client issues
GETrequests against those URLs — either viaclient.request(url=request.url, …)insidecrawl()for regular sitemap URLs, or viaclient.stream(url, …)inside the nested-sitemap fetch.
Layer 2 — non-HTTP schemes (CurlImpersonateHttpClient only)
- Source: a…
- Record
GHSA-3r75-xc34-5f44- CVSS 4.0
- 2.3 Low
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:L/VI:N/VA:N/SC:L/SI:N/SA:N - Weakness
- CWE-918
- Updated
- 2026-11-02
NVD
2.3
Crawlee is a web scraping and browser automation library. From version 1.0.0 to before version 1.7.0, Crawlee is vulnerable to SSRF via sitemap-derived URLs. This issue has been patched in version 1.7.0.
- Record
CVE-2026-46497- CVSS 4.0
- 2.3 Low
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:L/VI:N/VA:N/SC:L/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X - Weakness
- CWE-918
- Updated
- 2026-11-02
OSV
2.3
SSRF via sitemap-derived URLs in Crawlee for Python
Crawlee is a web scraping and browser automation library. From version 1.0.0 to before version 1.7.0, Crawlee is vulnerable to SSRF via sitemap-derived URLs. This issue has been patched in version 1.7.0.
- Record
CVE-2026-46497- CVSS 4.0
- 2.3 Low
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:L/VI:N/VA:N/SC:L/SI:N/SA:N - Weakness
- CWE-918
- Updated
- 2026-11-02
OSVGHSA-3r75-xc34-5f44
2.3
Crawlee for Python: SSRF via sitemap-derived URLs
Overview
- Vulnerability type: Blind SSRF
- Affected components:
src/crawlee/_utils/sitemap.py,src/crawlee/_utils/robots.py,src/crawlee/request_loaders/_sitemap_request_loader.py, and all built-in HTTP clients. - Trigger: an attacker-controlled sitemap or
robots.txtcontaining a URL that points to an internal host (layer 1) or uses a non-http scheme (layer 2).
Two-layer SSRF via sitemap-derived URLs:
1) Cross-host HTTP SSRF
Base case, affects every HTTP client.** Sitemap entries and robots.txt Sitemap: directives were accepted regardless of the host they pointed to. A sitemap on example.com could push http://internal.corp/admin into the crawler's queue, and the configured HTTP client would dispatch the request.
2) Non-HTTP scheme SSRF
Escalation, only CurlImpersonateHttpClient.** Nested-sitemap fetching dispatches the URL straight to the HTTP client, bypassing the Request construction step where Pydantic enforces http(s). Combined with the libcurl-backed CurlImpersonateHttpClient, this lets gopher://, file://, dict://, ftp://, etc., through.
Root cause
Crawlee already validates URL schemes through Pydantic's AnyHttpUrl (via validate_http_url in src/crawlee/_utils/urls.py) wherever a crawl target is materialised as a Request: the Request.url field is declared as Annotated[str, BeforeValidator(validate_http_url), Field(frozen=True)]. Anything that becomes a Request is therefore guaranteed to be http(s).
Two parts of the sitemap pipeline sidestepped this property in different ways:
1) Sitemap-derived URLs were enqueued without any host policy
SitemapRequestLoader took every <urlset><url><loc> entry, wrapped it in Request.from_url (which accepts any valid http(s) URL), and pushed the result into the request queue. RobotsTxtFile.get_sitemaps() returned every Sitemap: directive verbatim. Neither imposed any host check against the parent sitemap or robots.txt URL, so an attacker controlling that content could push internal-network HTTP URLs into the queue and have them crawled by whichever HTTP client was configured.
2) Nested sitemap fetching bypassed the Request chokepoint entirely
When _XmlSitemapParser encountered <sitemapindex><sitemap><loc>…</loc></sitemap></sitemapindex>, or when RobotsTxtFile.parse_sitemaps forwarded Sitemap: directives into the same pipeline, _fetch_and_process_sitemap dispatched the URL directly to the HTTP client:
async with http_client.stream(
sitemap_url,
method='GET',
headers=SITEMAP_HEADERS,
proxy_info=proxy_info,
timeout=timeout,
) as response:
...
No Request was constructed, so the Pydantic validator never ran. Before the fix, the HTTP clients' own send_request() and stream() methods did not call validate_http_url either, so a non-http(s) scheme could pass straight through to the backend client.
The non-HTTP escalation in layer 2 is specific to CurlImpersonateHttpClient, which is backed by curl-cffi / libcurl and speaks gopher, file, dict, ftp, and other non-HTTP protocols. The other clients shipped with Crawlee (HttpxHttpClient, ImpitHttpClient, PlaywrightHttpClient) reject non-http(s) schemes at their own backend layer, regardless of what Crawlee passes in, so they were only affected by layer 1.
Vulnerable paths
Layer 1 — cross-host HTTP (all HTTP clients)
- Source: an attacker-controlled sitemap that lists internal URLs under
<urlset><url><loc>or<sitemapindex><sitemap><loc>, or an attacker-controlledrobots.txtthat lists internal URLs underSitemap:. - Sink: the configured HTTP client issues
GETrequests against those URLs — either viaclient.request(url=request.url, …)insidecrawl()for regular sitemap URLs, or viaclient.stream(url, …)inside the nested-sitemap fetch.
Layer 2 — non-HTTP schemes (CurlImpersonateHttpClient only)
- Source: a…
- Record
GHSA-3r75-xc34-5f44- CVSS 4.0
- 2.3 Low
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:L/VI:N/VA:N/SC:L/SI:N/SA:N - Weakness
- CWE-918
- Updated
- 2026-11-02
OSVPYSEC-2026-2430
2.3
Crawlee for Python: SSRF via sitemap-derived URLs
Overview
- Vulnerability type: Blind SSRF
- Affected components:
src/crawlee/_utils/sitemap.py,src/crawlee/_utils/robots.py,src/crawlee/request_loaders/_sitemap_request_loader.py, and all built-in HTTP clients. - Trigger: an attacker-controlled sitemap or
robots.txtcontaining a URL that points to an internal host (layer 1) or uses a non-http scheme (layer 2).
Two-layer SSRF via sitemap-derived URLs:
1) Cross-host HTTP SSRF
Base case, affects every HTTP client.** Sitemap entries and robots.txt Sitemap: directives were accepted regardless of the host they pointed to. A sitemap on example.com could push http://internal.corp/admin into the crawler's queue, and the configured HTTP client would dispatch the request.
2) Non-HTTP scheme SSRF
Escalation, only CurlImpersonateHttpClient.** Nested-sitemap fetching dispatches the URL straight to the HTTP client, bypassing the Request construction step where Pydantic enforces http(s). Combined with the libcurl-backed CurlImpersonateHttpClient, this lets gopher://, file://, dict://, ftp://, etc., through.
Root cause
Crawlee already validates URL schemes through Pydantic's AnyHttpUrl (via validate_http_url in src/crawlee/_utils/urls.py) wherever a crawl target is materialised as a Request: the Request.url field is declared as Annotated[str, BeforeValidator(validate_http_url), Field(frozen=True)]. Anything that becomes a Request is therefore guaranteed to be http(s).
Two parts of the sitemap pipeline sidestepped this property in different ways:
1) Sitemap-derived URLs were enqueued without any host policy
SitemapRequestLoader took every <urlset><url><loc> entry, wrapped it in Request.from_url (which accepts any valid http(s) URL), and pushed the result into the request queue. RobotsTxtFile.get_sitemaps() returned every Sitemap: directive verbatim. Neither imposed any host check against the parent sitemap or robots.txt URL, so an attacker controlling that content could push internal-network HTTP URLs into the queue and have them crawled by whichever HTTP client was configured.
2) Nested sitemap fetching bypassed the Request chokepoint entirely
When _XmlSitemapParser encountered <sitemapindex><sitemap><loc>…</loc></sitemap></sitemapindex>, or when RobotsTxtFile.parse_sitemaps forwarded Sitemap: directives into the same pipeline, _fetch_and_process_sitemap dispatched the URL directly to the HTTP client:
async with http_client.stream(
sitemap_url,
method='GET',
headers=SITEMAP_HEADERS,
proxy_info=proxy_info,
timeout=timeout,
) as response:
...
No Request was constructed, so the Pydantic validator never ran. Before the fix, the HTTP clients' own send_request() and stream() methods did not call validate_http_url either, so a non-http(s) scheme could pass straight through to the backend client.
The non-HTTP escalation in layer 2 is specific to CurlImpersonateHttpClient, which is backed by curl-cffi / libcurl and speaks gopher, file, dict, ftp, and other non-HTTP protocols. The other clients shipped with Crawlee (HttpxHttpClient, ImpitHttpClient, PlaywrightHttpClient) reject non-http(s) schemes at their own backend layer, regardless of what Crawlee passes in, so they were only affected by layer 1.
Vulnerable paths
Layer 1 — cross-host HTTP (all HTTP clients)
- Source: an attacker-controlled sitemap that lists internal URLs under
<urlset><url><loc>or<sitemapindex><sitemap><loc>, or an attacker-controlledrobots.txtthat lists internal URLs underSitemap:. - Sink: the configured HTTP client issues
GETrequests against those URLs — either viaclient.request(url=request.url, …)insidecrawl()for regular sitemap URLs, or viaclient.stream(url, …)inside the nested-sitemap fetch.
Layer 2 — non-HTTP schemes (CurlImpersonateHttpClient only)
- Source: a…
- Record
PYSEC-2026-2430- CVSS 4.0
- 2.3 Low
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:P/VC:L/VI:N/VA:N/SC:L/SI:N/SA:N - Updated
- 2026-11-02
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
crawleePyPI |
1.0.0 up to 1.7.0 | 1.7.0 |
crawleePyPI |
1.0.0 up to 1.7.0, plus listed versions 1.0.11.0.21.0.31.0.41.1.0 Show 40 moreShow less1.1.11.2.01.2.11.3.01.3.11.3.21.4.01.5.01.5.1b11.5.1b21.5.1b31.5.1b41.5.1b51.6.01.6.11.6.1b11.6.1b21.6.1b31.6.1b41.6.1b51.6.21.6.2b11.6.2b21.6.2b31.6.2b41.6.31.6.3b11.6.3b21.6.3b31.6.3b41.6.3b51.6.3b61.6.4b11.6.4b21.6.4b31.6.4b41.6.4b51.6.4b61.6.4b71.6.4b8 |
1.7.0 |
History
| Recorded | Changed | Sources |
|---|---|---|
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-46497 is a low vulnerability (CVSS 2.3) in Apify Crawlee-python, published May 21, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.29%, the 21st percentile. Fixed in crawlee 1.7.0.
References
github.com/apify/crawlee-python/security/advisories/GHSA-3r75-xc34-5f44
github.com/apify/crawlee-python/releases/tag/v1.7.0
nvd.nist.gov/vuln/detail/CVE-2026-46497
github.com/advisories/GHSA-3r75-xc34-5f44
github.com/CVEProject/cvelistV5/tree/main/cves/2026/46xxx/CVE-2026-46497.json
github.com/apify/crawlee-python
pypi.org/project/crawlee
Left out here: the rest of 3 source descriptions. The JSON record has them.