{"id":"GHSA-gj48-438w-jh9v","summary":"Bleach clean() / Cleaner() fails to sanitize dangerous URI schemes in allowed formaction attributes","details":"### Summary\n\nBleach `clean()` / `Cleaner()` fails to sanitize dangerous URI schemes in allowed `formaction` attributes.\n\nBleach applies URI protocol sanitization only to attributes listed in `attr_val_is_uri`. While URI-bearing attributes such as `action`, `href`, `src`, and `poster` are included in that set, `formaction` is not. As a result, if a downstream application explicitly allows `formaction` on submit-capable controls in untrusted HTML, Bleach preserves dangerous values such as `javascript:alert(1)` instead of stripping them.\n\nThis can lead to **submit-triggered JavaScript execution** in applications that rely on Bleach to sanitize untrusted HTML and allow the relevant tag/attribute combination.\n\n---\n\n### Details\n\nThe issue appears to be a URI-sanitization coverage gap in Bleach’s sanitizer logic.\n\nRelevant code paths:\n\n* `bleach/sanitizer.py` — `BleachSanitizerFilter.allow_token` (around line 553)\n* `bleach/_vendor/html5lib/filters/sanitizer.py` — `attr_val_is_uri` (around line 525)\n\nIn `BleachSanitizerFilter.allow_token`, URI protocol sanitization is only applied when:\n\n```python id=\"pft79m\"\nif namespaced_name in self.attr_val_is_uri:\n```\n\nHowever, `(None, 'formaction')` is currently missing from `attr_val_is_uri`.\n\nThis creates an inconsistency where `action` is protocol-sanitized, but `formaction` is not.\n\nAs a result, if a downstream application allows:\n\n* tags such as `\u003cbutton\u003e` or `\u003cinput\u003e`\n* the `formaction` attribute\n\nthen Bleach preserves dangerous URI schemes such as `javascript:` in `formaction`.\n\nExamples of affected submit-capable controls include:\n\n* `\u003cbutton\u003e` (default submit behavior unless `type=\"button\"` is set)\n* `\u003cinput type=\"submit\"\u003e`\n* `\u003cinput type=\"image\"\u003e`\n\nThis appears to be a real library-side sanitizer gap rather than only an application misuse issue, because Bleach already treats similar URI-bearing attributes (such as `action`) as protocol-sensitive and sanitizes them.\n\nSuggested minimal fix:\n\nAdd:\n\n```python id=\"4v4fkn\"\n(None, 'formaction')\n```\n\nto `attr_val_is_uri` in:\n\n* `bleach/_vendor/html5lib/filters/sanitizer.py`\n\nI also prepared a minimal patch and focused regression tests if helpful.\n\n---\n\n### PoC\n\nBelow are minimal reproductions using `bleach.clean()`.\n\n#### 1) `\u003cbutton\u003e`\n\n```python id=\"d3g0v7\"\nfrom bleach import clean\n\nprint(clean(\n    '\u003cform\u003e\u003cbutton formaction=\"javascript:alert(1)\"\u003ego\u003c/button\u003e\u003c/form\u003e',\n    tags={'form', 'button'},\n    attributes={'button': ['formaction']},\n))\n```\n\n**Actual output:**\n\n```html id=\"i4nd7s\"\n\u003cform\u003e\u003cbutton formaction=\"javascript:alert(1)\"\u003ego\u003c/button\u003e\u003c/form\u003e\n```\n\n**Expected output:**\n\n```html id=\"g4d2r1\"\n\u003cform\u003e\u003cbutton\u003ego\u003c/button\u003e\u003c/form\u003e\n```\n\n---\n\n#### 2) `\u003cinput type=\"submit\"\u003e`\n\n```python id=\"l4dy0j\"\nprint(clean(\n    '\u003cform\u003e\u003cinput type=\"submit\" formaction=\"javascript:alert(1)\" value=\"go\"\u003e\u003c/form\u003e',\n    tags={'form', 'input'},\n    attributes={'input': ['type', 'formaction', 'value']},\n))\n```\n\n**Actual output:**\n\n```html id=\"h8lgbt\"\n\u003cform\u003e\u003cinput type=\"submit\" formaction=\"javascript:alert(1)\" value=\"go\"\u003e\u003c/form\u003e\n```\n\n**Expected output:**\n\n```html id=\"6y8mws\"\n\u003cform\u003e\u003cinput type=\"submit\" value=\"go\"\u003e\u003c/form\u003e\n```\n\n---\n\n#### 3) `\u003cinput type=\"image\"\u003e`\n\n```python id=\"g8q0x8\"\nprint(clean(\n    '\u003cform\u003e\u003cinput type=\"image\" formaction=\"javascript:alert(1)\" src=\"/foo.png\"\u003e\u003c/form\u003e',\n    tags={'form', 'input'},\n    attributes={'input': ['type', 'formaction', 'src']},\n))\n```\n\n**Actual output:**\n\n```html id=\"fd22kg\"\n\u003cform\u003e\u003cinput type=\"image\" formaction=\"javascript:alert(1)\" src=\"/foo.png\"\u003e\u003c/form\u003e\n```\n\n**Expected output:**\n\n```html id=\"z6t6je\"\n\u003cform\u003e\u003cinput type=\"image\" src=\"/foo.png\"\u003e\u003c/form\u003e\n```\n\n---\n\n### Impact\n\nThis is a **client-side HTML sanitization bypass / dangerous URI preservation issue**.\n\nIf an application relies on Bleach to sanitize untrusted HTML and explicitly allows:\n\n* `formaction`\n* and submit-capable controls such as `\u003cbutton\u003e` or `\u003cinput\u003e`\n\nthen Bleach can emit sanitized output that still contains a dangerous `javascript:` URI in `formaction`.\n\nThat can lead to **submit-triggered JavaScript execution** when the user activates the control.\n\nImpact is limited to configurations that explicitly allow the relevant tag/attribute combination, but the issue is still security-relevant because:\n\n* `formaction` is a real browser sink\n* Bleach already protocol-sanitizes similar URI-bearing attributes like `action`\n* the omission creates inconsistent sanitizer coverage for dangerous URI schemes\n\nI would currently assess this as **Medium severity**.\n\nIf useful, I also have:\n\n* a minimal patch\n* focused regression tests for:\n\n  * `\u003cbutton formaction=\"javascript:...\"\u003e`\n  * `\u003cinput type=\"submit\" formaction=\"javascript:...\"\u003e`\n  * `\u003cinput type=\"image\" formaction=\"javascript:...\"\u003e`\n  * a safe control case where `formaction=\"/submit\"` is preserved","modified":"2026-09-10T03:50:49.646625202Z","published":"2026-06-16T14:07:49Z","database_specific":{"cwe_ids":["CWE-79"],"severity":"MODERATE","github_reviewed":true,"github_reviewed_at":"2026-06-16T14:07:49Z","nvd_published_at":null},"references":[{"type":"WEB","url":"https://github.com/mozilla/bleach/security/advisories/GHSA-gj48-438w-jh9v"},{"type":"PACKAGE","url":"https://github.com/mozilla/bleach"},{"type":"WEB","url":"https://github.com/mozilla/bleach/releases/tag/v6.4.0"}],"affected":[{"package":{"name":"bleach","ecosystem":"PyPI","purl":"pkg:pypi/bleach"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"6.4.0"}]}],"versions":["0.1","0.1.1","0.1.2","0.2","0.2.1","0.2.2","0.3","0.3.1","0.3.3","0.3.4","0.5.0","0.5.1","1.0.0","1.0.1","1.0.2","1.0.3","1.0.4","1.1.0","1.1.1","1.1.2","1.1.3","1.1.4","1.1.5","1.2","1.2.1","1.2.2","1.4","1.4.1","1.4.2","1.4.3","1.5.0","2.0.0","2.1","2.1.1","2.1.2","2.1.3","2.1.4","3.0.0","3.0.1","3.0.2","3.1.0","3.1.1","3.1.2","3.1.3","3.1.4","3.1.5","3.2.0","3.2.1","3.2.2","3.2.3","3.3.0","3.3.1","4.0.0","4.1.0","5.0.0","5.0.1","6.0.0","6.1.0","6.2.0","6.3.0"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/06/GHSA-gj48-438w-jh9v/GHSA-gj48-438w-jh9v.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N"}]}