// threat brief

SGLang disaggregated diffusion pickle deserialization: anyone who can reach the ZeroMQ port can run code

CVSS
9.8 CRITICAL
CISA KEV
Not listed
Weakness
CWE-502

PlainCVE Team · Published 2026-09-28 · Updated 2026-09-28 · AI-assisted · Press brief ↓

AI review 2026-09-28 (not human-reviewed)Command tests: none yetReproduced: not yetVerification record ↓

SGLang's disaggregated DiffusionServer unpickles network messages with no login, so anyone who can reach its port can run code. No fixed version yet.

Who is affectedSGLang deployments running disaggregated diffusion mode (head node started with --disagg-role server), version 0.5.11 or later. The CVE record currently lists 0.5.11 through 0.5.14; the researcher gives evidence that later versions are still affected; PlainCVE's source check on 2026-09-24 found that v0.5.20 and that day's main still call pickle.loads() directly. Remote attack requires the ZeroMQ port to be reachable over the network.
What happensAny host that can reach the head node's ZeroMQ frontend port can run arbitrary code inside the DiffusionServer process without credentials.
What to do nowNo fixed version yet. PlainCVE's analysis (from reading the code, not tested): restrict who can reach the head node's ZeroMQ ports. On a single host you can consider leaving --host unset so it binds to 127.0.0.1; across hosts that would stop remote workers from connecting back, so use an isolated private network and a firewall that allows only known nodes instead. Restrict encoder workers too.
Affected versionsThe CVE record (CERT/CC) currently lists 0.5.11 through 0.5.14 (OSV and Strix match; NVD has not analysed it and lists no range); the researcher gives evidence that later versions are still affected (verified on 0.5.17, and says 0.5.11 through 0.5.20 and main all have it); PlainCVE's source check on 2026-09-24 found v0.5.20 still calls pickle.loads() directly
Fixed versionsNo fixed version yet
CVSS vector
Show full vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Timeline
  • First public 2026-09-22

Blue team playbook

Check, fix / mitigate, respond, harden. Matching the affected versions does not mean you were compromised, and a check that finds nothing does not prove you are safe; each item says what it can and cannot show. Tagged steps point to where the attack flow below can be stopped.

DETECTCheckAm I affected? Any signs of compromise?
  1. 01Check your version and whether you use disaggregated diffusion

    You are affected if you run 0.5.11 or later and the head node is started with --disagg-role server. The CVE record currently lists only up to 0.5.14, but the researcher gives evidence that later versions are still affected, and PlainCVE's source check on 2026-09-24 found the same call in v0.5.20. Until a confirmed fixed version exists, PlainCVE suggests counting every version from 0.5.11 up as affected in your inventory; this is a conservative inventory policy and does not cover future fixed versions. Run pip show in the environment that actually runs the service (the same venv or container), or you may see a different version.

    Read-only check
    pip show sglang | grep -i '^version'; pgrep -af -- '--disagg-role'
  2. 02Check which address the head node binds to

    If the command line has --host 0.0.0.0 or another non-loopback address, the ZeroMQ frontend is exposed to the network; without --host it binds only to 127.0.0.1. There is no separate flag for the ZeroMQ address; both the frontend port and the ports that receive worker results follow the HTTP --host setting. ss only lists what is listening on this machine and does not by itself prove the port is reachable from outside; that also depends on firewalls and network setup.

    Read-only check
    ss -ltnp | grep -v '127.0.0.1'
  3. 03Don't forget the encoder workers

    A separate finding from the researcher (not part of this CVE): encoder workers bind to 0.0.0.0 regardless of --host and also call pickle.loads(). Checking only the head node is not enough.

  4. 04Look for suspicious connections

    The sources publish no indicators of compromise. PlainCVE suggests checking for connections to the ZeroMQ frontend port from hosts outside the cluster, and for unexpected child processes or outbound connections from the DiffusionServer process.

MITIGATEFix / mitigatePatch; block it if you cannot patch yet
  1. 01Restrict network reachability (no fixed version yet)

    No vendor workaround is documented. PlainCVE's analysis (from reading the v0.5.20 code, not tested): --host sets the bind address for HTTP, the ZeroMQ frontend port and the ZeroMQ ports workers use to return results. On a single host or within one network namespace, you can consider leaving --host unset so it binds to 127.0.0.1. Across hosts, binding to 127.0.0.1 stops remote workers from returning results; instead put the nodes on an isolated private network, firewall the necessary ports to known nodes only, and confirm worker traffic still works. A reverse proxy in front of HTTP does not cover the ZeroMQ ports. Note that the official guide shows --host 0.0.0.0.

  2. 02Firewall the encoder workers

    PlainCVE's analysis (not verified): because encoder workers bind to 0.0.0.0 regardless of --host, use a host or network firewall to restrict who can reach them.

  3. 03Track the upstream fix

    Community fix PR #30804 was closed by a bot without being merged. Maintainer PR #40259 only changed the SafeUnpickler allowlist in python/sglang/srt/utils/common.py and did not touch orchestrator.py in multimodal_gen; PlainCVE's source check on 2026-09-24 found that v0.5.20 and that day's main still call pickle.loads() directly.

RESPONDRespondIf you find signs of compromise
  1. 01Suspicious activity: investigate as a possible compromise

    PlainCVE suggests: unfamiliar connections alone do not prove compromise; they may be scans or junk traffic. First check the DiffusionServer process for unknown child processes, unusual outbound connections or other signs of code execution. If that evidence is there, since the attacker could run any code in the process, isolate the host, rebuild from a clean image and rotate any credentials and keys the process could reach. This is a precaution; the sources give no incident response guidance.

HARDENHardenPrevent the next one
  1. 01Keep inference cluster internal ports off public networks

    Put node-to-node ports on an isolated internal network and expose only the APIs you need.

  2. 02Developers: never unpickle network data

    Use plain data formats such as JSON across machines, and authenticate the sender first.

Full remediation steps and notes

There is no fixed version to upgrade to. PlainCVE’s suggestion (not a verified mitigation) is to restrict network access. PlainCVE’s additional notes:

  • --host sets the bind address for both HTTP and ZeroMQ (from reading the v0.5.20 code, not tested). In launch_disagg_server(), the ZeroMQ frontend port (--scheduler-port) and the ports that receive worker results (--scheduler-port plus 1 to 3) all use the --host value, with no separate flag. So it depends on your setup:
    • Single host or one network namespace: you can consider leaving --host unset so it binds to 127.0.0.1. The HTTP API then also becomes local-only, so you’ll need something like a reverse proxy to serve it externally; the reverse proxy protects only HTTP, not the ZeroMQ ports.
    • Across hosts: binding to 127.0.0.1 stops remote workers from sending results back to the head node. Put the nodes on an isolated private network, firewall the necessary ports (frontend and result ports) to known nodes only, and confirm worker traffic still works afterwards.
  • The official guide’s example is itself exposed. If you followed SGLang’s disaggregation guide and used --host 0.0.0.0, deal with that first.
  • Take a broad view when inventorying. The CVE record currently lists 0.5.11 through 0.5.14; the researcher gives evidence for later versions; our source check found v0.5.20 still has the call. Until a confirmed fixed version exists, count 0.5.11 and later as affected. This is a conservative inventory policy as of 2026-09-24 and does not cover future fixed versions.
  • Fix status. Community PR #30804 named this issue as one it would fix, but a bot closed it as idle without merging. Maintainer PR #40259 only changed python/sglang/srt/utils/common.py, tightening which classes SafeUnpickler allows; it did not touch orchestrator.py in multimodal_gen. PlainCVE’s source check on 2026-09-24 found that _handle_client_request() in v0.5.20 and that day’s main still calls pickle.loads() directly. The GitHub Advisory Database entry is still unreviewed and lists no fixed version.

Attack flow

Read left to right: this is the order the attack happens in. A blue shield means the step can be stopped; the earlier, the better. Click an icon for details.

Read top to bottom: this is the order the attack happens in. A blue shield means the step can be stopped; the earlier, the better. Tap an icon for details.

SGLang disaggregated diffusion pickle deserialization: anyone who can reach the ZeroMQ port can run code: 1. Finds a reachable DiffusionServer ZeroMQ frontend port → 2. Connects to the ROUTER socket with no credentials → 3. Sends a multipart message whose last part carries malicious serialized data → 4. The server calls pickle.loads() before any checks → 5. The attacker's code runs inside the DiffusionServer processCVE-2026-93088 · SGLang disaggregated diffusion pickle deserialization: anyone who can reach the ZeroMQ portcan run codeAttackerYour systemFinds a reachable DiffusionServer ZeroMQ frontend port — The port is reachable from the network only if the head node is bound to a non-loopback address with --host. SGLang's own disaggregation guide uses --host 0.0.0.0 (all network interfaces). 1Finds a reachableDiffusionServerZeroMQ frontendportConnects to the ROUTER socket with no credentials — The ZeroMQ socket has no authentication and accepts connections from any client. 2Connects to theROUTER socket withno credentialsSends a multipart message whose last part carries malicious serialized data — The server treats the last part of every multipart message as pickle data. 3Sends a multipartmessage whose lastpart carriesmaliciou…The server calls pickle.loads() before any checks — Loading pickle data can trigger actions named inside the data, so loading untrusted data lets the sender give orders. 4The server callspickle.loads()before any checksThe attacker's code runs inside the DiffusionServer process — What the attacker can do depends on the process's privileges and what it can reach. 5The attacker'scode runs insidetheDiffusionServe…Attack pathCan be stopped herePlainCVE · plaincve.date/en/vulns/cve-2026-93088-sglang-diffusion-pickle-rce · CC BY 4.0
SGLang disaggregated diffusion pickle deserialization: anyone who can reach the ZeroMQ port can run code: 1. Finds a reachable DiffusionServer ZeroMQ frontend port → 2. Connects to the ROUTER socket with no credentials → 3. Sends a multipart message whose last part carries malicious serialized data → 4. The server calls pickle.loads() before any checks → 5. The attacker's code runs inside the DiffusionServer processCVE-2026-93088 · SGLang disaggregateddiffusion pickle deserialization: anyonewho can reach the ZeroMQ port can runcodeFinds a reachable DiffusionServer ZeroMQ frontend port — The port is reachable from the network only if the head node is bound to a non-loopback address with --host. SGLang's own disaggregation guide uses --host 0.0.0.0 (all network interfaces). 1AttackerFinds a reachableDiffusionServer ZeroMQ frontendportConnects to the ROUTER socket with no credentials — The ZeroMQ socket has no authentication and accepts connections from any client. 2AttackerConnects to the ROUTER socketwith no credentialsSends a multipart message whose last part carries malicious serialized data — The server treats the last part of every multipart message as pickle data. 3AttackerSends a multipart message whoselast part carries maliciousserialized dataThe server calls pickle.loads() before any checks — Loading pickle data can trigger actions named inside the data, so loading untrusted data lets the sender give orders. 4Your systemThe server calls pickle.loads()before any checksThe attacker's code runs inside the DiffusionServer process — What the attacker can do depends on the process's privileges and what it can reach. 5Your systemThe attacker's code runs insidethe DiffusionServer processAttack pathCan be stopped herePlainCVE · CC BY 4.0plaincve.date/en/vulns/cve-2026-93088-sglang-diffusion-pickle-rce
Free to use in reporting and teaching under CC BY 4.0. Attribution is already on the image.
  1. Attacker

    Finds a reachable DiffusionServer ZeroMQ frontend port

    The port is reachable from the network only if the head node is bound to a non-loopback address with --host. SGLang's own disaggregation guide uses --host 0.0.0.0 (all network interfaces).

    Defense: PlainCVE's analysis (from reading the code, not tested): on a single host, consider leaving --host unset (binds to 127.0.0.1); across hosts, use an isolated network and a firewall that allows only known nodes.

  2. Attacker

    Connects to the ROUTER socket with no credentials

    The ZeroMQ socket has no authentication and accepts connections from any client.

    Defense: Allow only trusted cluster nodes at the network layer.

  3. Attacker

    Sends a multipart message whose last part carries malicious serialized data

    The server treats the last part of every multipart message as pickle data.

  4. Your system

    The server calls pickle.loads() before any checks

    Loading pickle data can trigger actions named inside the data, so loading untrusted data lets the sender give orders.

    Defense: No fixed version yet; the real fix is not using pickle for network messages, or authenticating the sender first.

  5. Your system

    The attacker's code runs inside the DiffusionServer process

    What the attacker can do depends on the process's privileges and what it can reach.

    Defense: Run the service with a least-privilege account and isolate it on the network.

Who is affected

Remote exploitation requires all of these: disaggregated diffusion mode (head node started with --disagg-role server), an affected version, and a ZeroMQ frontend port reachable from the network.

ConditionAffected?
0.5.11 to 0.5.14 + disaggregated diffusion modeAffected (the CVE record, OSV, Strix and the researcher agree)
0.5.15 to 0.5.20, current mainNot listed in the CVE record, but not safe: the researcher gives evidence and confirmed the flaw on 0.5.17; PlainCVE’s source check on 2026-09-24 found v0.5.20 and that day’s main still call pickle.loads() directly
Head node set with --host 0.0.0.0 or another non-loopback addressAttackable over the network; SGLang’s official guide shows exactly this
--host left unset (binds to 127.0.0.1)Reachable only by processes on the same machine; in a cross-host deployment, remote workers can’t connect back either (from reading the code, not tested)
Not using disaggregated diffusion modeThis CVE’s precondition is not met; but the CVE record for a separate flaw, CVE-2026-7301 (published 2026-05-18), says the multimodal generation runtime scheduler’s ROUTER socket binds to 0.0.0.0 by default and also calls pickle.loads() on incoming messages, so assess it separately
Every version as of 2026-09-24No fixed version yet

The researcher also found a related problem that is not part of this CVE: encoder workers bind to 0.0.0.0 no matter what --host says, and they also call pickle.loads(). So binding the head node to 127.0.0.1 alone does not make the deployment safe.

How it works

SGLang’s multimodal generation feature can split the work of a diffusion model (a model that generates images and similar content) across several machines, coordinated by a DiffusionServer on the head node. The nodes talk through ZeroMQ, a library for passing messages between programs over the network.

The problem has two layers:

  1. No authentication. The DiffusionServer’s ZeroMQ ROUTER socket accepts connections from any client without checking who it is.
  2. Unpickling outside data. When the server receives a multipart message, it hands the last part straight to Python’s pickle.loads(), before any checks. pickle is Python’s object serialization format, and loading it can trigger actions named in the data, so unpickling data from a stranger lets them run code in your process. This kind of mistake is CWE-502 (Deserialization of Untrusted Data).

Put together: any host that can open a TCP connection to the port can run arbitrary code inside the DiffusionServer process. CISA’s SSVC assessment rates it automatable, with total technical impact.

The flawed approach (conceptual illustration, not the original source code):

Show code example(python)
frames = router_socket.recv_multipart()   # anyone can connect
request = pickle.loads(frames[-1])        # loaded before any check

A safer approach (PlainCVE’s illustration; there is no fixed version yet):

Show code example(python)
frames = router_socket.recv_multipart()
request = json.loads(frames[-1])          # accept plain data only
validate(request)                         # then check the contents

Timeline

DateEvent
2026-04-16According to the researcher, commit 9da998a8 (PR #21701, disaggregated diffusion feature) adds orchestrator.py, introducing the problem
2026-07-03Researcher reports privately via GitHub Security Advisory (GHSA-8374-wrr5-7q7f)
2026-07-10Community PR #30804 publicly lists “disaggregation orchestrator accepts external pickle payloads”: the issue was visible in a public PR before the CVE was published
2026-08-11Researcher opens public tracking issue #34419, noting more than five weeks with no maintainer response
2026-08-14Reported to CERT/CC (VINCE, VU#727584)
2026-09-17CERT/CC verifies the flaw and assigns the CVE
2026-09-19Community fix PR #30804 closed unmerged; maintainer PR #40259 merged (changes only srt/utils/common.py, not orchestrator.py)
2026-09-22CVE published
2026-09-24Still not in CISA KEV (catalog version 2026.09.23), and no fixed version; PlainCVE’s source check finds main still calls pickle.loads() directly

Further reading

  • Scoring: the 9.8 (CVSS 3.1) score comes from CISA’s ADP, not the CNA (CERT/CC). NVD still lists the CVE as “Awaiting Analysis”.
  • Affected versions: the CVE record currently lists 0.5.11 through 0.5.14 (OSV and Strix match; NVD has not analysed it and lists no range); the researcher gives evidence for later versions; our source check found v0.5.20 still has the call. OSV’s starting commit (612785ff…) differs from the researcher’s 9da998a8 and may simply be the 0.5.11 release commit.
  • Public PoC: the researcher published the core PoC snippet (a harmless version that only prints a marker), the verification method and its output, and CISA’s SSVC in the CVE record rates Exploitation as poc; whether a complete, ready-to-run tool also exists has not been verified. TheHackerWire says no public exploit code has been confirmed, which conflicts with the above.
  • Exploitation: not in KEV as of 2026-09-24 (catalog version 2026.09.23). The sources have no evidence either way on exploitation in the wild or before a patch; not being in KEV does not prove it hasn’t been exploited.
  • Related weakness type: CWE-502.

Verification Checked item by item against sources by AI under our review rules, all passed; not human-reviewed and not yet reproduced in our lab

AI review2026-09-28 · review rules

Verification records describe the environment and the result only, never reproduction steps or code that could attack other people’s systems. See our policy.

Sources

  1. AdvisoryCVE-2026-93088 Record (CVE Services API) · CVE Program / CERT/CC, 2026-09-22 · accessed 2026-09-24
  2. CVE / NVD / OSVNVD API - CVE-2026-93088 · NIST · accessed 2026-09-24
  3. OtherOSV - CVE-2026-93088 · OSV · accessed 2026-09-24
  4. AdvisoryGitHub Advisory Database - CVE-2026-93088 (unreviewed) · GitHub · accessed 2026-09-24
  5. ResearchSGLang CVE-2026-93088 write-up · Keita Sode (SYZD Research) · accessed 2026-09-24
    Contains demonstration code (a harmless marker PoC); CISA tags this URL as "exploit" in the CVE record
  6. Othersglang issue #34419 (public tracking issue) · sgl-project (GitHub), 2026-08-11 · accessed 2026-09-24
  7. Othersglang PR #30804 (community fix, closed unmerged) · sgl-project (GitHub), 2026-09-19 · accessed 2026-09-24
  8. Othersglang PR #40259 (SafeUnpickler allowlist) · sgl-project (GitHub), 2026-09-19 · accessed 2026-09-24
  9. VendorSGLang Diffusion disaggregation guide · SGLang · accessed 2026-09-24
  10. OtherCVE-2026-93088 · Strix · accessed 2026-09-24
  11. NewsCVE-2026-93088 · TheHackerWire · accessed 2026-09-24
  12. Othersglang v0.5.20 orchestrator.py (DiffusionServer._handle_client_request) · sgl-project (GitHub) · accessed 2026-09-24
  13. Othersglang v0.5.20 launch_server.py (launch_disagg_server endpoints) · sgl-project (GitHub) · accessed 2026-09-24
  14. Othersglang main a69583f orchestrator.py · sgl-project (GitHub) · accessed 2026-09-24
  15. Othersglang PR #40259 changed files · sgl-project (GitHub) · accessed 2026-09-24
  16. OtherCISA Known Exploited Vulnerabilities Catalog (version 2026.09.23) · CISA · accessed 2026-09-24

Press brief

In one sentence

SGLang is an open-source framework for running AI models. Its 'disaggregated diffusion' mode for multimodal generation such as images has a critical flaw: the server loads data received from the network with Python's pickle, with no login required, so anyone who can reach the port can run code on the server. CISA scored it CVSS 9.8. As of September 24, 2026, no fixed version has been released.

Key facts

Severity
CVSS 3.1 9.8 (CISA ADP); NVD not yet analysed cveawg.mitre.org
CVE published
2026-09-22 cveawg.mitre.org
Fixed version
No fixed version yet (the CVE record lists no fixed version) cveawg.mitre.org
Credited to
Keita Sode (SYZD Research) cveawg.mitre.org
Weakness
CWE-502 Deserialization of Untrusted Data cveawg.mitre.org

Confirmed

  • The DiffusionServer's ZeroMQ ROUTER socket has no authentication and calls pickle.loads() on received data before any checks.
  • As of the researcher's write-up, the latest release and main still contain the vulnerable code; there is no fixed version.
  • PlainCVE's source check on 2026-09-24: _handle_client_request() in v0.5.20 and that day's main still calls pickle.loads() directly.
  • Maintainer PR #40259 only changed srt/utils/common.py and did not touch the pickle.loads() in orchestrator.py.
  • The researcher published the core PoC snippet, the verification method and its output; CISA's SSVC in the CVE record rates Exploitation as poc.
  • Not in CISA KEV as of 2026-09-24 (catalog version 2026.09.23); not being listed does not mean it has not been exploited.

Unconfirmed / disputed

  • Whether the CVE record will update its affected range (currently 0.5.11 through 0.5.14), and when an official fixed version will be released.
  • The core PoC snippet and verification results have been published; whether a complete, ready-to-run tool also exists has not been verified.
  • Whether it has been exploited in the wild: no evidence either way; not being in KEV does not prove it hasn't been.

Quotable line

Unpickling data from strangers is like letting a stranger's note tell your computer what to do.

Images

Download the share image (PNG) · Attack flow diagram (downloadable)

Please credit "PlainCVE" and link to this page. Full citation format is under "How to cite" below. Found a factual error? Report it.

How to cite this page

This article is CC BY 4.0. Please keep the attribution and link when republishing.

PlainCVE Team (2026). "SGLang disaggregated diffusion pickle deserialization: anyone who can reach the ZeroMQ port can run code". PlainCVE. https://plaincve.date/en/vulns/cve-2026-93088-sglang-diffusion-pickle-rce (accessed YYYY-MM-DD)
BibTeX
@misc{cve202693088sglangdiffusionpicklerce2026,
  title  = {SGLang disaggregated diffusion pickle deserialization: anyone who can reach the ZeroMQ port can run code},
  author = {PlainCVE Team},
  year   = {2026},
  howpublished = {PlainCVE},
  url    = {https://plaincve.date/en/vulns/cve-2026-93088-sglang-diffusion-pickle-rce},
  note   = {Updated 2026-09-28}
}