Ghost: The Author Role Deletes Other People's Posts and Pages
The permission check has an ownership branch for contributors but none for authors on delete, so ownership is never checked there.
Advisory ID: TP-2026-107
Product: Ghost (publishing platform for blogs and newsletters, Node.js)
Vulnerability type: Missing authorization (CWE-862)
CVE: CVE-2026-105680
CVSS 3.1: 6.5 (Medium) · CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H
Affected versions: >= 5.81.0, < 6.60.0
Vendor advisory: GHSA-x3mg-q38v-m562
Fixed in: 6.60.0
Reported: 3 June 2026
Summary
The Author role in Ghost may create and manage its own drafts but must not touch anyone else's content. On deletion that limit does not hold: an account in that role removes posts and pages belonging to any other user, published ones included, and the site owner's as well. The permission check has a dedicated branch that tests ownership for the Contributor role, and another for editing by authors, but none for deletion by authors. The call therefore falls into a catch-all branch where the already granted permission is carried through an or-expression, so the question of ownership is never asked. Through the filtered bulk deletion the same account removes every matching post on the entire site in a single request.
Root cause
The permissible function in core/server/models/relations/authors.js:373-385 carries ownership branches for deletion by contributors and editing by authors, but none for the combination of the Author role and deletion. Such a call therefore lands in the catch-all branch hasUserPermission = hasUserPermission || isPrimaryAuthor(), where the first operand is already true because the Author role holds the global post-deletion permission, so the or-expression stays true regardless of ownership. The subsequent check in post.js:1436 lets the operation through as well, since a deletion changes no visibility. The bulk deletion passes no object id at all, so permissible is invoked with an undefined model and the service in services/posts/posts-service.js:250 deletes purely by the caller-supplied filter with no author scoping. That the gap survived so long shows in the test suite: a test exists for deletion by contributors, none for deletion by authors.
Proof of Concept
# precondition: an account with the Author role, as any ordinary staff
# invite creates. the owner has published a post.
# individually:
DELETE /ghost/api/admin/posts/<id of the foreign post>/ -> 204
# as a control, the same request as a Contributor:
DELETE /ghost/api/admin/posts/<id of the foreign post>/ -> 403
# by filter, in a single request:
DELETE /ghost/api/admin/posts/?filter=status:draft
The difference between 204 and 403 shows the gap immediately: for the Contributor role the ownership branch applies, for the Author role it is missing. In our reproduction an Author account deleted a draft, a published post and a page belonging to the owner.
Impact
- Permanent deletion of posts and pages belonging to every other role by an account holding the lowest content-writing role.
- Complete or targeted destruction of a site's content through the filtered bulk deletion, in a single request.
- No protection from the publication state, since drafts and published content are affected alike.
- No breach of confidentiality; the effect lies solely on the integrity and availability of the content.
References
Is Something Like This in Your Software?
Our team found this vulnerability in the course of its work. Have your applications tested by the same specialists, with a penetration test from turingpoint.
