{"id":"PYSEC-2026-3825","summary":"django CMS: Stored XSS in edit-mode plugin exception rendering","details":"## Summary\n\nWhen plugin rendering fails in edit mode, django CMS renders a `cms-rendering-exception` block so editors can see that a placeholder could not be rendered. Older code built that block's heading by interpolating the exception message, placeholder/source strings, and the failing plugin's short description directly into an HTML string, then returned the placeholder output as safe markup.\n\nIf an editor could store HTML in data used by a plugin's `get_short_description()` (or in other values interpolated into the exception message), and that plugin later raised during edit-mode rendering, the payload was parsed as HTML in the staff user's browser. This is a stored XSS condition in the CMS editing context.\n\n## Impact\n\nThe vulnerable path is only reached when placeholder rendering catches a plugin rendering exception:\n\n```python\ntry:\n    placeholder_content = \"\".join(plugin_content)\nexcept Exception as e:\n    context[\"exc_info\"] = sys.exc_info()\n    placeholder_content = self.render_exception(\"rendering placeholder\", context, placeholder, editable)\n```\n\n`render_exception()` constructs a `message` from values that can include stored content:\n\n- `value` - the exception message.\n- `placeholder` - the placeholder string representation.\n- `placeholder.source` - the source object string representation, such as page content.\n- `instance.get_short_description()` - plugin-provided summary text, often derived from plugin model fields.\n\nIn the vulnerable implementation, that message was embedded directly into an HTML heading. The final placeholder content was later returned through `mark_safe`, so Django template autoescaping did not protect the heading.\n\n`settings.DEBUG` does not mitigate the issue: it only controls whether Django's traceback HTML is appended. The custom heading is rendered in edit mode regardless of `DEBUG`.\n\n## Patch\n\nEscape the custom exception heading before returning it as safe placeholder markup. The current fixed code uses `format_html`, which escapes `message` before inserting it into the heading:\n\n```python\nheading = format_html('\u003ch2 class=\"cms-rendering-exception-title\"\u003e{}\u003c/h2\u003e', message)\n```\n\nThe traceback HTML from `ExceptionReporter.get_traceback_html()` should remain separate from django CMS's custom heading; Django's traceback escaping does not protect additional HTML assembled by django CMS.\n\n## Workarounds\n\nUntil patched, reduce exposure by ensuring only fully trusted staff can edit plugins whose stored fields are included in `get_short_description()`, and fix or disable plugins that can be made to raise during edit-mode rendering. This is only a partial mitigation because the escaping bug is in the shared exception-rendering path.\n\n## References\n\n- `cms/plugin_rendering.py` - `ContentRenderer.render_placeholder`\n- `cms/plugin_rendering.py` - `ContentRenderer.render_exception`\n- Fixed code: `heading = format_html('\u003ch2 class=\"cms-rendering-exception-title\"\u003e{}\u003c/h2\u003e', message)`\n- Regression tests:\n  `cms.tests.test_plugin_renderers.TestExceptionCatchers.test_exception_in_plugin_render_escapes_user_content_in_edit_mode`,\n  `cms.tests.test_plugin_renderers.TestLegacyRendererExceptionCatcher.test_exception_in_plugin_render_escapes_user_content_in_edit_mode`","aliases":["CVE-2026-75526","GHSA-hvq6-2r72-p2x7"],"modified":"2026-09-10T12:15:03.035763796Z","published":"2026-09-10T09:44:52.636615Z","references":[{"type":"WEB","url":"https://github.com/django-cms/django-cms/security/advisories/GHSA-hvq6-2r72-p2x7"},{"type":"WEB","url":"https://github.com/django-cms/django-cms/pull/8711"},{"type":"WEB","url":"https://github.com/django-cms/django-cms/commit/b56a568844ff3702495945f73a31d0868285bf88"},{"type":"PACKAGE","url":"https://github.com/django-cms/django-cms"},{"type":"WEB","url":"https://github.com/django-cms/django-cms/releases/tag/5.0.9"},{"type":"PACKAGE","url":"https://pypi.org/project/django-cms"},{"type":"ADVISORY","url":"https://github.com/advisories/GHSA-hvq6-2r72-p2x7"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-75526"}],"affected":[{"package":{"name":"django-cms","ecosystem":"PyPI","purl":"pkg:pypi/django-cms"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"5.0.8"},{"fixed":"5.0.9"}]}],"versions":["5.0.8"],"database_specific":{"source":"https://github.com/pypa/advisory-database/blob/main/vulns/django-cms/PYSEC-2026-3825.yaml"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:H/PR:L/UI:R/S:C/C:L/I:L/A:N"}]}