Talos Vulnerability Report

TALOS-2026-2415

VisiData VisiData EmailSheet extract_parts path traversal vulnerability

October 7, 2026

CVE Number

CVE-2026-42532

Summary

A path traversal vulnerability exists in the EmailSheet extract_parts functionality of VisiData (version(s): dev (commit 38b21f78)). A specially crafted .eml file can lead to arbitrary file write. An attacker can provide a malicious file to trigger this vulnerability.

Confirmed Vulnerable Versions

The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.

VisiData (version(s): dev (commit 38b21f78))

Product URLs

VisiData - https://www.visidata.org

CVSSv3 Score

5.5 - CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N

CWE

CWE-22 - Improper Limitation of a Pathname to a Restricted Directory (‘Path Traversal’)

Details

VisiData is a popular open-source terminal interface for exploring and arranging tabular data. It supports a wide range of file formats and data sources, and can open files both locally and remotely via URL, presenting the contents as interactive navigable sheets.

VisiData supports opening RFC 2822 email files (.eml, .mhtml), presenting each MIME part as a row in a sheet. The loader is implemented in visidata/loaders/eml.py. Users can extract attachments individually with x or in bulk with gx, which prompts for a destination directory and extracts all selected parts into it.

The affected flow begins when a .eml file is opened in VisiData. EmailSheet.iterload() parses the message using Python’s email.parser module and yields each MIME part via Message.walk() [1]:

      def iterload(self):
          import email.parser
          parser = email.parser.Parser()
          with self.source.open(encoding='utf-8') as fp:
[1]           yield from parser.parse(fp).walk()

Each yielded object is an email.message.Message instance, presented as a selectable row in the sheet. When the user selects parts and presses gx, VisiData calls extract_parts() with the selected Message objects and the user-supplied destination path. Inside extract_parts(), each part’s filename is obtained and used unsanitised to construct the output path [2] [3]:

      for i, part in enumerate(parts):
[2]       fn = part.get_filename() or f'part{i}'
[3]       vd.execAsync(sheet.extract_part, givenpath / fn, part)

At [2], fn is obtained by calling Message.get_filename(), a Python stdlib method in email/message.py that reads the filename parameter from the part’s Content-Disposition header and returns it verbatim without any path sanitisation. At [3], Python’s pathlib / operator joins the user-supplied destination givenpath with fn. As pathlib does not resolve or strip path traversal sequences, a Content-Disposition filename value of ../traversal.txt produces a path of givenpath/../traversal.txt. The OS resolves this when extract_part() opens the path for writing, causing the file to land outside the intended destination directory.

Exploitation requires user interaction: the victim must open the .eml and trigger extraction. While filenames are shown during extraction, a user is not expected to scrutinise every filename for path traversal sequences, particularly when the email contains many attachments. A realistic attack delivers an email that appears to carry several ordinary attachments alongside one entry whose Content-Disposition filename parameter is set to a path traversal string. When the victim opens the .eml in VisiData and bulk extracts all attachments, the malicious entry is written to the traversed path, which can include shell initialisation files, ~/.ssh/authorized_keys, or any other file writable by the running user.

Timeline

2026-05-26 - Vendor Disclosure
2026-06-08 - Vendor Disclosure Resent
2026-06-15 - Request for Vendor Receipt
2026-06-16 - Vendor Acknowledged
2026-09-18 - Follow-up sent

Credit

Claudio Bozzato of Cisco Talos