The move_plugin admin endpoint does not prevent a plugin from being reparented under itself or one of its own descendants. Doing so creates a cycle in the plugin tree, after which the recursive descendant/ancestor SQL queries loop without terminating, stalling the request worker.
The CMS page cache key ignores the request headers that plugins declare via get_vary_cache_on(). The header is added to the response Vary header, but the CMS's own cache key does not incorporate the header values, so the first visitor's variant is served to all subsequent visitors regardless of their header values.
The structure-board endpoint (render_object_structure) renders a page's plugin structure without verifying that the requesting user is allowed to view the page. The edit and preview endpoints enforce this via render_page(), but the structure endpoint does not, allowing a low-privileged staff user to read the plugin structure of a view-restricted page.
When 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. If an editor could store HTML in data used by a plugin's get_short_description() (or …
The django-cms frontend-editing structure endpoint GET /<lang>/admin/cms/placeholder/object/<content_type_id>/structure/<object_id>/ did not perform an object-level authorization check for non-PageContent objects. Any authenticated, active staff user could request the structure endpoint for a frontend-editable object (a model using PlaceholderRelationField) and read its placeholder/plugin structure, even without permission to change that object and without the cms.use_structure permission that the toolbar UI requires before offering structure mode. PageContent objects were already protected (a page-view check added …
The clipboard copy paths of the copy_plugins admin endpoint validate only the target (the user's own clipboard) and skip source-side authorization. A staff user can copy plugins out of a placeholder they have no permission on into their clipboard, then read the (secret) content.
The only authorization gate on the duplicate flow is PageAdmin.has_add_permission, which checks user_can_add_page(user, site) / user_can_add_subpage(…) — i.e. “may this user create a page at all”. Nothing checks the user’s relationship to the page being copied: cms/admin/forms.py — DuplicatePageForm.source = ModelChoiceField(queryset=Page.objects.all(), widget=HiddenInput()) spans every page in the database, on every site. cms/admin/forms.py — AddPageForm.init returns early when the source widget is hidden, so the queryset is never narrowed to the …