{"id":"RUSTSEC-2026-0430","summary":"Path traversal in `rio-vt` Kitty graphics protocol handler allows out-of-bounds file delete (and read)","details":"The Kitty graphics protocol handler in `rio-vt` builds a filesystem path from an attacker-controlled APC escape sequence and, for the temp-file medium, only checks that the path *contains the substring* `\"tty-graphics-protocol\"` before it opens, reads, and `remove_file`s it. There is no `..` rejection and no canonicalization, so a path containing the marker substring plus `..` escapes the terminal's temp directory (path traversal, CWE-22/CWE-23).\n\nBecause a terminal renders whatever arrives on the PTY (ssh output, a `cat`'d file, CI logs), untrusted output can delete a file outside the temp area and cause its bytes to be read back in.\n\n## Details\n\nThe sink is `create_graphic_data()` in `src/ansi/kitty_graphics_protocol.rs`, in the `TransmissionMedium::TempFile` branch, reachable through the public API `rio_vt::ansi::kitty_graphics_protocol::parse`. The path is the base64 APC payload of an `a=t,t=t` command, and the guard is:\n\n```rust\nlet path = Path::new(path_str);\nif !path.is_file() { return Err(GraphicError::FileNotFound); }       // literal path, resolves `..`\n// reject /proc/ /sys/ /dev/ (lowercased substring)\nif cmd.medium == TransmissionMedium::TempFile\n    && !path_str.contains(\"tty-graphics-protocol\") {                 // SUBSTRING, not a component\n    return Err(GraphicError::InvalidData);\n}\n// ...\nlet mut file = File::open(path)...;          // reads file bytes\nif cmd.medium == TransmissionMedium::TempFile {\n    let _ = std::fs::remove_file(path);      // deletes the file\n}\n```\n\nThis affects any terminal emulator or application built on `rio-vt` (e.g. the Rio terminal) that renders attacker-influenced output: an out-of-bounds file is deleted, and its contents are read into the process.\n\n## The fix\n\nFixed in `rio-vt` 0.5.29 (Rio 0.5.29) by [PR #1998](https://github.com/raphamorim/rio/pull/1998). For the temp-file medium the parent directory is resolved with `canonicalize` before any check, the resolved entry must lie inside a temp directory (`temp_dir()`, `/tmp` or `/dev/shm`) and contain the marker, and the delete goes through `unlinkat` on a handle to the validated directory, so neither `..` nor a swapped symlink can redirect it. Path checks for the file medium also run on the file actually opened.","modified":"2026-11-02T11:00:02.247101984Z","published":"2026-11-01T12:00:00Z","database_specific":{"license":"CC0-1.0"},"references":[{"type":"PACKAGE","url":"https://crates.io/crates/rio-vt"},{"type":"ADVISORY","url":"https://rustsec.org/advisories/RUSTSEC-2026-0430.html"},{"type":"ADVISORY","url":"https://github.com/raphamorim/rio/security/advisories/GHSA-m926-9wrf-jr8w"},{"type":"WEB","url":"https://github.com/raphamorim/rio/pull/1998"}],"affected":[{"package":{"name":"rio-vt","ecosystem":"crates.io","purl":"pkg:cargo/rio-vt"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0.0.0-0"},{"fixed":"0.5.29"}]}],"ecosystem_specific":{"affected_functions":null,"affects":{"arch":[],"os":[],"functions":["rio_vt::ansi::kitty_graphics_protocol::parse"]}},"database_specific":{"informational":null,"source":"https://github.com/rustsec/advisory-db/blob/osv/crates/RUSTSEC-2026-0430.json","categories":["file-disclosure"],"cvss":"CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:N/I:H/A:N"}}],"schema_version":"1.9.2","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:N/I:H/A:N"}]}