🛡️ CVE-2026-57126 — praisonaiagents
Description
praisonaiagents: SSRF guard validates literal IPs only and never resolves DNS
# praisonaiagents: SSRF guard validates literal IPs only and never resolves DNS
Researcher: Kai Aizen — SnailSploit (@SnailSploit), Adversarial & Offensive Security Research
Target: https://github.com/MervinPraison/PraisonAI
Weakness: CWE-918 Server-Side Request Forgery (SSRF).
Summary
The SSRF guard shared by PraisonAI's web tools (SpiderTools._validate_url → _host_is_blocked in praisonaiagents/tools/spider_tools.py) inspects only literal IP-address encodings of the URL host. It never resolves DNS names. Any hostname whose A/AAAA record points at an internal, loopback, link-local, or cloud-metadata address passes validation and the request is issued to that target. A static internal A record is sufficient — no DNS-rebinding race is required.
The guard's own docstring claims it returns True "when hostname resolves to loopback/private/internal targets," but no resolution is performed. The fix for CVE-2026-47390 added more *encodings of literal IPs* (decimal integer, 0x hex, inet_aton); it did not address the *class* "host is a name that resolves to a forbidden address."
The same guard is reached through two tool surfaces:
scrape_page/crawl/extract_links/extract_text(spider tools)- the
@urlmention fetch inpraisonaiagents/tools/mentions.py(which calls the identicalSpiderTools._validate_urlthenurllib.request.urlopen)
The correct pattern already exists in the same package: file_tools.py resolves the host with socket.getaddrinfo and checks each resolved address before fetching. spider_tools / mentions do not.
Affected packages
pip/praisonaiagents<= 1.6.39pip/PraisonAI<= 4.6.39
Root cause
praisonaiagents/tools/spider_tools.py, _host_is_blocked (def at line 26):
```python
def _host_is_blocked(hostname: str) -> bool:
"""Return True when hostname resolves to loopback/private/internal targets."""
...
if host.isdigit(): # decimal-int IPv4 literal
return _ip_blocked(ipaddress.ip_address(int(host)))
if host.startswith("0x"): # hex IPv4 literal
return _ip_blocked(ipaddress.ip_address(int(host, 16)))
try:
return _ip_blocked(ipaddress.ip_address(host)) # dotted v4 / v6 literal
except ValueError:
pass
try:
return _ip_blocked(ipaddress.ip_address(socket.inet_aton(host))) # octal/short v4
except OSError:
pass
return False # <-- any DNS name lands here
```
Every branch operates on the literal string. For a DNS name (attacker.example): it is not in the literal block sets, not a .local/.internal suffix, int(host) is not applicable, ipaddress.ip_address(name) raises ValueError (swallowed), inet_aton(name) raises OSError (swallowed), and the function returns False — "not blocked." socket.getaddrinfo / gethostbyname are never called anywhere in this path.
_validate_url (def line 74) ends with:
```python
if _host_is_blocked(parsed.hostname):
return False
return True
```
so a name verdict of "not blocked" yields _validate_url(...) == True, and the caller (scrape_page, or mentions._fetch_url at lines 273–284) proceeds to fetch the original URL via requests / urllib.request.urlopen.
The literal-IP coverage is otherwise good — Python's ipaddress.is_reserved / is_private happen to flag NAT64 (64:ff9b::/96), 6to4 (2002::/16), IPv4-mapped (::ffff:), and IPv4-compatible (::/96) forms. The single residual literal gap is deprecated site-local fec0::/10 (is_private and is_reserved both False), which is low-impact on modern stacks. The DNS-name class is the material issue.
The promise that was broken
The block set explicitly contains "169.254.169.254" and "metadata.google.internal" (line 33) — documented intent to stop cloud-metadata theft. A name-based request defeats exactly that intent: register metadata-thief.example with an A record of 169.254.169.254, and the literal block is never consulted because resolution never happens.
Proof of concept
```python
import socket
from praisonaiagents.tools.spider_tools import _host_is_blocked, SpiderTools
# Literal forms the CVE-2026-47390 fix added — correctly blocked:
for h in ["127.0.0.1", "2130706433", "0x7f000001", "169.254.169.254", "::1"]:
assert _host_is_blocked(h) is True, h
# DNS names that resolve to internal targets — NOT blocked (the class the fix missed):
for h in ["attacker-controlled.example", "metadata-thief.com", "rebind.attacker.net"]:
assert _host_is_blocked(h) is False, h # A record may be 127.0.0.1 / 169.254.169.254
st = SpiderTools
assert st._validate_url("http://127.0.0.1/") is False # literal blocked
assert st._validate_url("http://metadata-thief.com/") is True # name pa
How this vulnerability can be exploited
This issue can be reached over the network, attack complexity is low, an attacker needs low-level privileges on the target. No user interaction is required. The scope is changed, meaning a successful attack can affect components beyond the vulnerable one. Rated impact: confidentiality high, integrity low, availability none.
Weakness class
CVE-2026-57126 is classified as CWE-918: Server-Side Request Forgery (SSRF). The server fetches a URL supplied by the caller, which can be pointed at internal systems it alone can reach.
Affected software
CVE-2026-57126 is recorded against 1 package.
- praisonaiagents (fixed in 1.6.59)
Timeline and source
Published on 18 June 2026 and last revised on 23 July 2026. No public exploit is currently recorded for this entry. Record sourced from OSV.
References
Details
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:N
Affected Packages
| Software | From version | Fixed in |
|---|---|---|
| praisonaiagents | — | 1.6.59 |
References
Similar Threats
- Medium CVE-2026-47395
- Medium CVE-2026-47390
- Critical CVE-2026-47392
- High CVE-2026-44339
- High CVE-2026-44335
More CVE 2026 advisories
Browse all of CVE 2026 in the advisory index.
Site Security Check
Is praisonaiagents part of your stack?
CVE-2026-57126 is rated CVSS 8.0 High. BotEraser scans your installation against known CVE records and tells you whether this vulnerability applies to the versions you actually run.
Scan My Site Free →No credit card required · Results in minutes
ⓘ Data Notice: The information presented above has been compiled from publicly available internet sources. Boteraser aggregates this data solely for informational purposes and does not independently classify, evaluate, or endorse any findings about the vulnerabilities listed. The accuracy and completeness of this information is the sole responsibility of the original publishers. Boteraser and its operators accept no liability for any decisions made based on this data.