Every CVE Search/ Get API key

CVE-2026-55526

PraisonAI: SSRF protection bypass in `spider_tools._host_is_blocked()` via DNS-resolved hostnames (`127.0.0.1.nip.io`)

8.5 HighFix available

PraisonAI is a multi-agent teams system. Prior to praisonaiagents 1.6.58, spider_tools._host_is_blocked() does not resolve ordinary hostnames before scrape_page fetches them. A hostname such as 127.0.0.1.nip.io passes validation and resolves to loopback, permitting internal HTTP access. The fix uses socket.getaddrinfo and fails closed on DNS errors. This issue is fixed in version 1.6.58.

Attack profile

How easy it is to exploit

Attack vector
Network
Complexity
Low
Privileges needed
Low
User interaction
None

What an attacker can affect

Confidentiality
High
Integrity
Low
Availability
None

Exploit likelihood

0.36%Up 0.2 points since Aug 26, 2026
90%50%10%1%

What each source reports

CVE List Score used 8.5

PraisonAI: SSRF protection bypass in `spider_tools._host_is_blocked()` via DNS-resolved hostnames (`127.0.0.1.nip.io`)

PraisonAI is a multi-agent teams system. Prior to praisonaiagents 1.6.58, spider_tools._host_is_blocked() does not resolve ordinary hostnames before scrape_page fetches them. A hostname such as 127.0.0.1.nip.io passes validation and resolves to loopback, permitting internal HTTP access. The fix uses socket.getaddrinfo and fails closed on DNS errors. This issue is fixed in version 1.6.58.

Record
CVE-2026-55526
CVSS 3.1
8.5 HighCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:N
Weakness
CWE-350, CWE-918
Updated
2026-11-02
EUVDEUVD-2026-65499 8.5

PraisonAI is a multi-agent teams system. Prior to praisonaiagents 1.6.58, spider_tools._host_is_blocked() does not resolve ordinary hostnames before scrape_page fetches them. A hostname such as 127.0.0.1.nip.io passes validation and resolves to loopback, permitting internal HTTP access. The fix uses socket.getaddrinfo and fails closed on DNS errors. This issue is fixed in version 1.6.58.

Record
EUVD-2026-65499
CVSS 3.1
8.5 HighCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:N
Updated
2026-11-02
GitHub AdvisoriesGHSA-x44h-65qv-cw74 8.5

praisonaiagents has an SSRF protection bypass in `spider_tools._host_is_blocked()` via DNS-resolved hostnames (`127.0.0.1.nip.io`)

Summary

praisonaiagents/tools/spider_tools.py contains an SSRF protection bypass. The function _host_is_blocked() validates URLs against a list of blocked IP literals and hostname aliases, but never performs DNS resolution. Any hostname that resolves to a private or loopback IP address — including public wildcard DNS services like 127.0.0.1.nip.io — bypasses the protection entirely.

This has been confirmed with a live exploit: scrape_page("http://127.0.0.1.nip.io:PORT/secret") makes an HTTP request to 127.0.0.1:PORT and returns the internal service response. No attacker-controlled infrastructure is required.

scrape_page, extract_links, crawl, and extract_text are all registered as LLM-callable agent tools (see tools/__init__.py lines 51-55), so any agent instructed to fetch a user-supplied URL will trigger this path.

This is a new bypass of prior fix commit 004dcfef (GHSA-q9pw-vmhh-384g), which only rejected IP literal encoding tricks (hex, octal, backslash). The fix was also applied to web_crawl_tools.py (line 231: socket.gethostbyname call), but that fix was not ported to spider_tools.py.

Details

Root cause — spider_tools.py lines 26-65:

def _host_is_blocked(hostname: str) -> bool:
    host = hostname.lower().rstrip(".")
    # Checks literal aliases only — never resolves
    if host in ("localhost", "0.0.0.0", "::1"):
        return True
    if host in ("169.254.169.254", "metadata.google.internal"):
        return True
    if any(host.endswith(s) for s in (".local", ".internal", ".localdomain")):
        return True
    # Tries to parse as IP literal only
    try:
        return _ip_blocked(ipaddress.ip_address(host))
    except ValueError:
        pass
    try:
        return _ip_blocked(ipaddress.ip_address(socket.inet_aton(host)))
    except OSError:
        pass
    return False   # <-- ANY real hostname passes without DNS lookup

socket.inet_aton() only converts dotted-decimal strings, not hostnames. For any real hostname (e.g. 127.0.0.1.nip.io), both ipaddress.ip_address() and socket.inet_aton() raise exceptions, and the function returns False (not blocked).

Contrast with the fixed version in web_crawl_tools.py line 228-238:

if os.environ.get("ALLOW_LOCAL_CRAWL") != "true":
    try:
        ip_str = socket.gethostbyname(hostname)   # DNS resolution performed
        ip = ipaddress.ip_address(ip_str)
        if ip.is_loopback or ip.is_private or ip.is_link_local or ip.is_multicast:
            continue  # BLOCKED
    except socket.gaierror:
        continue  # fail-closed

Tool registration confirms this is user-reachable:

# praisonaiagents/tools/__init__.py lines 51-55
TOOL_MAPPINGS = {
    'scrape_page':   ('.spider_tools', None),  # <- user-reachable LLM tool
    'extract_links': ('.spider_tools', None),
    'crawl':         ('.spider_tools', None),
    'extract_text':  ('.spider_tools', None),
    ...
}

Any agent given these tools will call scrape_page(url) when instructed to fetch a user-supplied URL — including attacker-controlled ones.

PoC

Environment: Python 3.x, praisonaiagents <= 1.6.52, internet access (for nip.io)

Step 1 — Verify the filter bypass (no network needed):

from praisonaiagents.tools.spider_tools import SpiderTools, _host_is_blocked

# nip.io: public wildcard DNS — 127.0.0.1.nip.io always resolves to 127.0.0.1
print(_host_is_blocked("127.0.0.1.nip.io"))                        # False — NOT blocked
print(SpiderTools()._validate_url("http://127.0.0.1.nip.io/"))      # True  — ALLOWED
print(_host_is_blocked("127.0.0.1"))                                # True  — correctly blocked

Expected output:

False
True
True

Step 2 — Full SSRF: internal service response exfiltrated

import threading, time, requests
from http.server import HTTPServer, BaseHTTPRequestHandler
from…
Record
GHSA-x44h-65qv-cw74
CVSS 3.1
8.5 HighCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:N
Weakness
CWE-350, CWE-918
Updated
2026-11-02
NVD 8.5

PraisonAI is a multi-agent teams system. Prior to praisonaiagents 1.6.58, spider_tools._host_is_blocked() does not resolve ordinary hostnames before scrape_page fetches them. A hostname such as 127.0.0.1.nip.io passes validation and resolves to loopback, permitting internal HTTP access. The fix uses socket.getaddrinfo and fails closed on DNS errors. This issue is fixed in version 1.6.58.

Record
CVE-2026-55526
CVSS 3.1
8.5 HighCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:N
Weakness
CWE-350, CWE-918
Updated
2026-11-02
OSV 8.5

PraisonAI: SSRF protection bypass in `spider_tools._host_is_blocked()` via DNS-resolved hostnames (`127.0.0.1.nip.io`)

PraisonAI is a multi-agent teams system. Prior to praisonaiagents 1.6.58, spider_tools._host_is_blocked() does not resolve ordinary hostnames before scrape_page fetches them. A hostname such as 127.0.0.1.nip.io passes validation and resolves to loopback, permitting internal HTTP access. The fix uses socket.getaddrinfo and fails closed on DNS errors. This issue is fixed in version 1.6.58.

Record
CVE-2026-55526
CVSS 3.1
8.5 HighCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:N
Weakness
CWE-350, CWE-918
Updated
2026-11-02
OSVGHSA-x44h-65qv-cw74 8.5

praisonaiagents has an SSRF protection bypass in `spider_tools._host_is_blocked()` via DNS-resolved hostnames (`127.0.0.1.nip.io`)

Summary

praisonaiagents/tools/spider_tools.py contains an SSRF protection bypass. The function _host_is_blocked() validates URLs against a list of blocked IP literals and hostname aliases, but never performs DNS resolution. Any hostname that resolves to a private or loopback IP address — including public wildcard DNS services like 127.0.0.1.nip.io — bypasses the protection entirely.

This has been confirmed with a live exploit: scrape_page("http://127.0.0.1.nip.io:PORT/secret") makes an HTTP request to 127.0.0.1:PORT and returns the internal service response. No attacker-controlled infrastructure is required.

scrape_page, extract_links, crawl, and extract_text are all registered as LLM-callable agent tools (see tools/__init__.py lines 51-55), so any agent instructed to fetch a user-supplied URL will trigger this path.

This is a new bypass of prior fix commit 004dcfef (GHSA-q9pw-vmhh-384g), which only rejected IP literal encoding tricks (hex, octal, backslash). The fix was also applied to web_crawl_tools.py (line 231: socket.gethostbyname call), but that fix was not ported to spider_tools.py.

Details

Root cause — spider_tools.py lines 26-65:

def _host_is_blocked(hostname: str) -> bool:
    host = hostname.lower().rstrip(".")
    # Checks literal aliases only — never resolves
    if host in ("localhost", "0.0.0.0", "::1"):
        return True
    if host in ("169.254.169.254", "metadata.google.internal"):
        return True
    if any(host.endswith(s) for s in (".local", ".internal", ".localdomain")):
        return True
    # Tries to parse as IP literal only
    try:
        return _ip_blocked(ipaddress.ip_address(host))
    except ValueError:
        pass
    try:
        return _ip_blocked(ipaddress.ip_address(socket.inet_aton(host)))
    except OSError:
        pass
    return False   # <-- ANY real hostname passes without DNS lookup

socket.inet_aton() only converts dotted-decimal strings, not hostnames. For any real hostname (e.g. 127.0.0.1.nip.io), both ipaddress.ip_address() and socket.inet_aton() raise exceptions, and the function returns False (not blocked).

Contrast with the fixed version in web_crawl_tools.py line 228-238:

if os.environ.get("ALLOW_LOCAL_CRAWL") != "true":
    try:
        ip_str = socket.gethostbyname(hostname)   # DNS resolution performed
        ip = ipaddress.ip_address(ip_str)
        if ip.is_loopback or ip.is_private or ip.is_link_local or ip.is_multicast:
            continue  # BLOCKED
    except socket.gaierror:
        continue  # fail-closed

Tool registration confirms this is user-reachable:

# praisonaiagents/tools/__init__.py lines 51-55
TOOL_MAPPINGS = {
    'scrape_page':   ('.spider_tools', None),  # <- user-reachable LLM tool
    'extract_links': ('.spider_tools', None),
    'crawl':         ('.spider_tools', None),
    'extract_text':  ('.spider_tools', None),
    ...
}

Any agent given these tools will call scrape_page(url) when instructed to fetch a user-supplied URL — including attacker-controlled ones.

PoC

Environment: Python 3.x, praisonaiagents <= 1.6.52, internet access (for nip.io)

Step 1 — Verify the filter bypass (no network needed):

from praisonaiagents.tools.spider_tools import SpiderTools, _host_is_blocked

# nip.io: public wildcard DNS — 127.0.0.1.nip.io always resolves to 127.0.0.1
print(_host_is_blocked("127.0.0.1.nip.io"))                        # False — NOT blocked
print(SpiderTools()._validate_url("http://127.0.0.1.nip.io/"))      # True  — ALLOWED
print(_host_is_blocked("127.0.0.1"))                                # True  — correctly blocked

Expected output:

False
True
True

Step 2 — Full SSRF: internal service response exfiltrated

import threading, time, requests
from http.server import HTTPServer, BaseHTTPRequestHandler
from…
Record
GHSA-x44h-65qv-cw74
CVSS 3.1
8.5 HighCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:N
Weakness
CWE-350, CWE-918
Updated
2026-11-02
OSVPYSEC-2026-3905 8.5

praisonaiagents has an SSRF protection bypass in `spider_tools._host_is_blocked()` via DNS-resolved hostnames (`127.0.0.1.nip.io`)

Summary

praisonaiagents/tools/spider_tools.py contains an SSRF protection bypass. The function _host_is_blocked() validates URLs against a list of blocked IP literals and hostname aliases, but never performs DNS resolution. Any hostname that resolves to a private or loopback IP address — including public wildcard DNS services like 127.0.0.1.nip.io — bypasses the protection entirely.

This has been confirmed with a live exploit: scrape_page("http://127.0.0.1.nip.io:PORT/secret") makes an HTTP request to 127.0.0.1:PORT and returns the internal service response. No attacker-controlled infrastructure is required.

scrape_page, extract_links, crawl, and extract_text are all registered as LLM-callable agent tools (see tools/__init__.py lines 51-55), so any agent instructed to fetch a user-supplied URL will trigger this path.

This is a new bypass of prior fix commit 004dcfef (GHSA-q9pw-vmhh-384g), which only rejected IP literal encoding tricks (hex, octal, backslash). The fix was also applied to web_crawl_tools.py (line 231: socket.gethostbyname call), but that fix was not ported to spider_tools.py.

Details

Root cause — spider_tools.py lines 26-65:

def _host_is_blocked(hostname: str) -> bool:
    host = hostname.lower().rstrip(".")
    # Checks literal aliases only — never resolves
    if host in ("localhost", "0.0.0.0", "::1"):
        return True
    if host in ("169.254.169.254", "metadata.google.internal"):
        return True
    if any(host.endswith(s) for s in (".local", ".internal", ".localdomain")):
        return True
    # Tries to parse as IP literal only
    try:
        return _ip_blocked(ipaddress.ip_address(host))
    except ValueError:
        pass
    try:
        return _ip_blocked(ipaddress.ip_address(socket.inet_aton(host)))
    except OSError:
        pass
    return False   # <-- ANY real hostname passes without DNS lookup

socket.inet_aton() only converts dotted-decimal strings, not hostnames. For any real hostname (e.g. 127.0.0.1.nip.io), both ipaddress.ip_address() and socket.inet_aton() raise exceptions, and the function returns False (not blocked).

Contrast with the fixed version in web_crawl_tools.py line 228-238:

if os.environ.get("ALLOW_LOCAL_CRAWL") != "true":
    try:
        ip_str = socket.gethostbyname(hostname)   # DNS resolution performed
        ip = ipaddress.ip_address(ip_str)
        if ip.is_loopback or ip.is_private or ip.is_link_local or ip.is_multicast:
            continue  # BLOCKED
    except socket.gaierror:
        continue  # fail-closed

Tool registration confirms this is user-reachable:

# praisonaiagents/tools/__init__.py lines 51-55
TOOL_MAPPINGS = {
    'scrape_page':   ('.spider_tools', None),  # <- user-reachable LLM tool
    'extract_links': ('.spider_tools', None),
    'crawl':         ('.spider_tools', None),
    'extract_text':  ('.spider_tools', None),
    ...
}

Any agent given these tools will call scrape_page(url) when instructed to fetch a user-supplied URL — including attacker-controlled ones.

PoC

Environment: Python 3.x, praisonaiagents <= 1.6.52, internet access (for nip.io)

Step 1 — Verify the filter bypass (no network needed):

from praisonaiagents.tools.spider_tools import SpiderTools, _host_is_blocked

# nip.io: public wildcard DNS — 127.0.0.1.nip.io always resolves to 127.0.0.1
print(_host_is_blocked("127.0.0.1.nip.io"))                        # False — NOT blocked
print(SpiderTools()._validate_url("http://127.0.0.1.nip.io/"))      # True  — ALLOWED
print(_host_is_blocked("127.0.0.1"))                                # True  — correctly blocked

Expected output:

False
True
True

Step 2 — Full SSRF: internal service response exfiltrated

import threading, time, requests
from http.server import HTTPServer, BaseHTTPRequestHandler
from…
Record
PYSEC-2026-3905
CVSS 3.1
8.5 HighCVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:N
Updated
2026-11-02

Affected products

Affected packages

Affected packages
PackageAffectedFixed
praisonaiagents
PyPI
before 1.6.58
1.6.58
praisonaiagents
PyPI
before 1.6.58, plus listed versions
0.0.10.0.100.0.1000.0.1010.0.102
Show 45 moreShow less
0.0.1030.0.1040.0.1050.0.1060.0.1070.0.1080.0.1090.0.110.0.1100.0.1110.0.1120.0.1130.0.1140.0.1150.0.1160.0.1170.0.1180.0.1190.0.120.0.1200.0.1210.0.1220.0.1230.0.1240.0.1250.0.1260.0.1270.0.1280.0.1290.0.130.0.1300.0.1310.0.1320.0.1330.0.1340.0.1350.0.1360.0.1370.0.1380.0.1390.0.140.0.1400.0.1410.0.1420.0.143
1.6.58

History

History
RecordedChangedSources
first indexedCVE List, EUVD, GitHub Advisories, NVD, OSV

Dates are when Every CVE recorded each change. Upstream dates may differ.

In short

CVE-2026-55526 is a high vulnerability (CVSS 8.5) in Mervinpraison Praisonai, published August 25, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.36%, the 29th percentile. Fixed in praisonaiagents 1.6.58.

References

This page doesn't list everything

Left out here: 535 versions and the rest of 3 source descriptions. The JSON record has them.

MoveEnter Open