Skip to content
CVE-2026-80430 ·
Medium · September 25, 2026

Improper link resolution in the kitty drag and drop protocol allows a client to create files outside the staging directory

S0
Secur0 CNA
CVE-2026-80430

Description

Improper Link Resolution Before File Access (CWE-59) in subdir_data_for_drag() (kitty/dnd.c), in the drag source staging path of the drag and drop protocol, in kitty from 0.47.0 before 0.49.0, which allows a program writing to the terminal to create files and directories at paths outside the staging directory, because the function resolves a descendant of the staged item tree by constructing a path string and opening it with safe_open(path, O_DIRECTORY | O_RDONLY, 0) rather than by walking the tree one component at a time, so a client that declares two entries with the same name, the first a symlink whose target is an arbitrary absolute path and the second a directory, causes mkdirat() to fail with EEXIST, which the code ignores, and causes the subsequent path resolution to follow the symlink and return a directory descriptor outside the staging directory, which is then stored in parent->fd_plus_one and passed as the dirfd argument to add_payload() and used for every further create operation on that item and its descendants. Entry names are sanitised against path separators and dot components in populate_dir_entries(), but symlink targets are not validated. This results in the creation of files and directories at any path writable by the user running kitty, provided the symlink target is an existing directory.

The primitive is create-only. Files are created with O_CREAT | O_WRONLY | O_EXCL at mode 0644, so an existing file at the target path causes the operation to fail rather than be overwritten, and the primitive carries no read capability. Directories are created with mkdirat() at mode 0755 and an EEXIST result is tolerated, so intermediate directories that did not previously exist can be created by the attacker. The constraint is therefore that the symlink target must be an existing directory, not that the final target directory must exist.

The create operations themselves are constrained. A code comment at the symlink branch records that arbitrary symlink targets are accepted because files are only ever created with O_EXCL, and mkdirat() and symlinkat() both fail with EEXIST where a symlink already occupies the path. That reasoning holds for the create calls; what it does not cover is the resolution of the directory those calls are made relative to, which is where the defect lies.

On a case-insensitive staging filesystem, uniqify_dir_entries_for_case_insensitive_fs() renames the later of two identically named entries to case-conflict-<n>-<name>. Both entries survive, but they no longer share a name, so the collision the mechanism depends on does not occur. Case sensitivity is detected per staging directory and the case-sensitive path is the default.

Vulnerability type (CWE)

  • CWE-59: Improper Link Resolution Before File Access ('Link Following')

Affected versions

kitty from 0.47.0 before 0.49.0. The drag and drop protocol was introduced in kitty/dnd.c, first released in v0.47.0 (2026-05-19); releases before that do not contain the affected code. Every release from v0.47.0 to v0.48.2 inclusive is affected. Default status: unaffected.

Score (CVSS 4.0)

Medium (4.6)

CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:A/VC:N/VI:N/VA:N/SC:N/SI:L/SA:N

User interaction is Active because the staging path is only reached once the user has started a drag out of the terminal window, which is a deliberate action rather than mere display of the attacker's output. The impact falls on the subsequent system rather than on kitty itself, because the files are created in the user's filesystem outside the terminal. Integrity is scored Low rather than High because the primitive cannot alter a file that already exists. Creating a file at an attacker-chosen path may lead to code execution when the created file is one the environment later reads, for example a desktop autostart entry in a directory that did not previously exist, but that outcome depends on the victim's configuration and is not scored.

Solution

Upgrade to kitty 0.49.0 or later.

Patch

Commit 554908ed, which replaces path construction with a component-by-component walk from a directory kitty created itself, opening each component with O_NOFOLLOW and bounding the descent with MAX_DRAG_DIR_DEPTH.

The same commit adds O_NOFOLLOW to the open of the top level staging directory in toplevel_data_for_drag(), whose name kitty generates itself, and adds ELOOP and ENOTDIR to the errors reported to the client, so a drag that attempts this is denied with an error rather than silently contained.

Commit 3c3133d3, titled "Defense in depth use O_NOFOLLOW when opening regular files", adds O_NOFOLLOW to a single call site in drop_send_file_data(). That is the drop path, where kitty opens a local file read-only to send its contents to a client, not the drag source create path this advisory describes. It is cited in the record as a related reference rather than as a patch.

Credits

  • Darío Rivas Quero - finder
  • Cristian Fernández Cornejo - analyst
  • Xoán M. Otero Jorge - analyst
  • Secur0 CNA - coordinator
  • Kovid Goyal - remediation developer Discovery source: Internal

Official record: CVE-2026-80430

Related advisories

  • CVE-2026-80431 - Medium severity
  • CVE-2026-80432 - Medium severity