Label Studio: Task Data Runs Script in the Labeling Editor

The filter for task data forbids event attributes through a denylist in which onfocusin and the SMIL elements are missing.

Advisory ID: TP-2026-086
Product: Label Studio (data labeling tool for training data, Django and React)
Vulnerability type: Stored cross-site scripting (CWE-79)
CVE: not requested
CVSS 3.1: 9.0 (Critical) · CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:H/I:H/A:H
Affected versions: <= 1.23.0
Vendor advisory: GHSA-f3gx-jhwx-w7pr
Fixed in: 1.23.1
Reported: 7 June 2026

Summary

Label Studio shows the task data to be annotated as live HTML in the HyperText and Text object tags, so annotators can see formatting. The server stores that data unchanged, and the only gate is a filter in the browser. The filter works from a list of forbidden event attributes rather than a list of permitted elements, and that list is incomplete. Through onfocusin, onanimationstart or the SMIL elements animate and set, the attacker's script runs in the application's origin. It hits everyone who opens the task, other annotators as much as the project lead, and from there the whole API.

Root cause

The editor passes the task value through setRemoteValue() (web/libs/editor/src/tags/object/RichText/model.js:240), which hands it to sanitizeHtml() (web/libs/editor/src/utils/html.js:520), and writes the result into the DOM with dangerouslySetInnerHTML (RichText/view.jsx:570) or into an iframe whose srcDoc sandbox carries allow-same-origin and allow-scripts at once (:591). That filter is the only gate, because the server stores task data verbatim, as a read of /api/tasks/<id>/ confirms. The check consists of a list of forbidden on attributes plus the tags script and iframe (html.js:553-639), not a list of permitted elements. The list misses onfocusin, onanimationstart and the ontouch family, and the SMIL elements animate and set are not covered at all. The finding is therefore a bypass of the fix from GHSA-6xv9-957j-qfhg, which introduced exactly that list.

Proof of Concept

# create a task with crafted data (the Annotator role suffices)
POST /api/projects/<id>/import
[{"text": "<svg><set attributeName=onbegin value=alert(document.domain) />"}]

# variant using an unlisted event attribute
[{"text": "<p onfocusin=alert(document.domain) tabindex=0 autofocus>x</p>"}]

# the victim opens the task in the labeling editor -> the script runs

Both payloads survive sanitizeHtml(), because the list knows only certain on attributes and does not consider SMIL. In the iframe branch the sandbox does not help, since it sets allow-same-origin and allow-scripts together and the document therefore runs in the application's origin.

Impact

  • Script execution in the application's origin for every user who opens the crafted task in the editor.
  • Privilege escalation from annotator to project lead or administration as soon as one of those roles reviews the same task, which is the intended workflow.
  • Access to the entire REST API in the victim's name, including projects, annotations and the stored storage credentials.
  • The same flaw in further sinks of the same filter, such as the html attribute of Choice (Choice.jsx:214) and Label (Label.jsx:319).

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.