{"id":"GHSA-q8gq-377p-jq3r","summary":"vLLM: Security Check Bypass via assert Statement in Activation Function Loading Allows Arbitrary Code Execution","details":"### Summary\n\nAn `assert`-based security check in vLLM's activation function loading allows any unauthenticated attacker to achieve arbitrary code execution on the server by publishing a malicious HuggingFace model, when vLLM runs in Python optimized mode (`python -O` or `PYTHONOPTIMIZE=1`).\n\n### Details\n\nvLLM uses an `assert` statement at [`vllm/model_executor/layers/pooler/activations.py:48`](https://github.com/vllm-project/vllm/blob/main/vllm/model_executor/layers/pooler/activations.py#L48) as its sole security control to restrict which activation functions can be loaded from a HuggingFace model's `config.json`:\n\n```python\n# vllm/model_executor/layers/pooler/activations.py:35-53\nfunction_name: str | None = None\nif (\n    hasattr(config, \"sentence_transformers\")\n    and \"activation_fn\" in config.sentence_transformers\n):\n    function_name = config.sentence_transformers[\"activation_fn\"]\nelif (\n    hasattr(config, \"sbert_ce_default_activation_function\")\n    and config.sbert_ce_default_activation_function is not None\n):\n    function_name = config.sbert_ce_default_activation_function\n\nif function_name is not None:\n    assert function_name.startswith(\"torch.nn.modules.\"), (\n        \"Loading of activation functions is restricted to \"\n        \"torch.nn.modules for security reasons\"\n    )\n    fn = resolve_obj_by_qualname(function_name)()\n```\n\nPython's `assert` statements are stripped at compile time when running in optimized mode (`python -O` or `PYTHONOPTIMIZE=1`). When the assert is absent, the attacker-controlled `function_name` from the model's `config.json` is passed directly to [`resolve_obj_by_qualname()`](https://github.com/vllm-project/vllm/blob/main/vllm/utils/import_utils.py#L106) — an unrestricted import gadget:\n\n```python\ndef resolve_obj_by_qualname(qualname: str) -\u003e Any:\n    module_name, obj_name = qualname.rsplit(\".\", 1)\n    module = importlib.import_module(module_name)\n    return getattr(module, obj_name)\n```\n\nThis is the same vulnerability class as **CVE-2017-1000433** (pysaml2 assert-based auth bypass), flagged by Bandit B101 and Ruff S101, and the reason Django proactively replaced all assert-based security checks (ticket #32508).\n\n**Attacker-controlled input sources:**\n- `config.sentence_transformers[\"activation_fn\"]` (line 40)\n- `config.sbert_ce_default_activation_function` (line 45)\n\n**Affected call sites** — `get_act_fn()` is called via `resolve_classifier_act_fn()` from:\n- `vllm/model_executor/layers/pooler/seqwise/poolers.py:122` — SequencePooler\n- `vllm/model_executor/layers/pooler/tokwise/poolers.py:130` — TokenPooler\n\n**Broader systemic risk:** `resolve_obj_by_qualname` is called from ~20 locations across the codebase with no validation of its own. Any future caller feeding user-controlled input to it without validation creates the same vulnerability class.\n\n**Suggested fix:** Replace the `assert` with an explicit conditional raise:\n\n```python\nif not function_name.startswith(\"torch.nn.modules.\"):\n    raise ValueError(\n        \"Loading of activation functions is restricted to \"\n        \"torch.nn.modules for security reasons\"\n    )\n```\n\n### Impact\n\n**Arbitrary code execution.** A malicious model author publishes a HuggingFace model with a crafted `config.json`. When a victim loads this model with vLLM running under `python -O` or `PYTHONOPTIMIZE=1`, arbitrary code executes during model initialization with the privileges of the vLLM process.\n\nThe attack requires:\n1. Victim loads a malicious model from HuggingFace (user interaction)\n2. vLLM runs under `python -O` or `PYTHONOPTIMIZE=1` (documented in production use)\n3. Model uses a cross-encoder architecture (e.g. BERT or RoBERTa with sequence classification)\n\n**Coordinated disclosure note:** This vulnerability was also reported via huntr.com on April 2, 2026 (https://huntr.com/bounties/dcb05b04-e625-41e7-adbc-bbae0cc2d64c). A GitHub Security Advisory was also filed because it is vLLM's stated preferred disclosure channel per SECURITY.md.\n\n### Fix\n\nA fix for this was introduced in this commit: https://github.com/vllm-project/vllm/commit/b3c7ffcab82c2439726f8cb213800f6f38c023d3","aliases":["CVE-2026-41523","PYSEC-2026-2300"],"modified":"2026-09-10T03:50:50.172404895Z","published":"2026-06-16T17:34:49Z","database_specific":{"cwe_ids":["CWE-617","CWE-94"],"severity":"HIGH","github_reviewed":true,"github_reviewed_at":"2026-06-16T17:34:49Z","nvd_published_at":"2026-06-22T23:16:30Z"},"references":[{"type":"WEB","url":"https://github.com/vllm-project/vllm/security/advisories/GHSA-q8gq-377p-jq3r"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-41523"},{"type":"WEB","url":"https://github.com/vllm-project/vllm/commit/b3c7ffcab82c2439726f8cb213800f6f38c023d3"},{"type":"WEB","url":"https://security.access.redhat.com/data/csaf/v2/vex/2026/cve-2026-41523.json"},{"type":"WEB","url":"https://huntr.com/bounties/dcb05b04-e625-41e7-adbc-bbae0cc2d64c"},{"type":"PACKAGE","url":"https://github.com/vllm-project/vllm"},{"type":"WEB","url":"https://github.com/pypa/advisory-database/tree/main/vulns/vllm/PYSEC-2026-2300.yaml"},{"type":"WEB","url":"https://bugzilla.redhat.com/show_bug.cgi?id=2491582"},{"type":"WEB","url":"https://access.redhat.com/security/cve/CVE-2026-41523"},{"type":"WEB","url":"https://access.redhat.com/errata/RHSA-2026:62336"},{"type":"WEB","url":"https://access.redhat.com/errata/RHSA-2026:62335"},{"type":"WEB","url":"https://access.redhat.com/errata/RHSA-2026:61629"},{"type":"WEB","url":"https://access.redhat.com/errata/RHSA-2026:61627"},{"type":"WEB","url":"https://access.redhat.com/errata/RHSA-2026:59151"},{"type":"WEB","url":"https://access.redhat.com/errata/RHSA-2026:59144"},{"type":"WEB","url":"https://access.redhat.com/errata/RHSA-2026:59139"},{"type":"WEB","url":"https://access.redhat.com/errata/RHSA-2026:59138"},{"type":"WEB","url":"https://access.redhat.com/errata/RHSA-2026:57390"},{"type":"WEB","url":"https://access.redhat.com/errata/RHSA-2026:57389"},{"type":"WEB","url":"https://access.redhat.com/errata/RHSA-2026:57387"},{"type":"WEB","url":"https://access.redhat.com/errata/RHSA-2026:57380"},{"type":"WEB","url":"https://access.redhat.com/errata/RHSA-2026:36006"},{"type":"WEB","url":"https://access.redhat.com/errata/RHSA-2026:36005"}],"affected":[{"package":{"name":"vllm","ecosystem":"PyPI","purl":"pkg:pypi/vllm"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0.22.0"}]}],"versions":["0.0.1","0.1.0","0.1.1","0.1.2","0.1.3","0.1.4","0.1.5","0.1.6","0.1.7","0.10.0","0.10.1","0.10.1.1","0.10.2","0.11.0","0.11.1","0.11.2","0.12.0","0.13.0","0.14.0","0.14.1","0.15.0","0.15.1","0.16.0","0.17.0","0.17.1","0.18.0","0.18.1","0.19.0","0.19.1","0.2.0","0.2.1","0.2.1.post1","0.2.2","0.2.3","0.2.4","0.2.5","0.2.6","0.2.7","0.20.0","0.20.1","0.20.2","0.21.0","0.3.0","0.3.1","0.3.2","0.3.3","0.4.0","0.4.0.post1","0.4.1","0.4.2","0.4.3","0.5.0","0.5.0.post1","0.5.1","0.5.2","0.5.3","0.5.3.post1","0.5.4","0.5.5","0.6.0","0.6.1","0.6.1.post1","0.6.1.post2","0.6.2","0.6.3","0.6.3.post1","0.6.4","0.6.4.post1","0.6.5","0.6.6","0.6.6.post1","0.7.0","0.7.1","0.7.2","0.7.3","0.8.0","0.8.1","0.8.2","0.8.3","0.8.4","0.8.5","0.8.5.post1","0.9.0","0.9.0.1","0.9.1","0.9.2"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/06/GHSA-q8gq-377p-jq3r/GHSA-q8gq-377p-jq3r.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H"}]}