Merged 3 Oct 2026 Fix moved into the shared Pimalaya crate
Himalaya: terminal escape injection and BiDi spoofing defense in Rust email tables
BudgetScan flagged that Himalaya, a popular CLI email client written in Rust, printed unescaped message metadata — subjects, sender names, attachment filenames, and IDs — directly into terminal tables. An external email sender could inject ANSI escape sequences (such as OSC 52 clipboard hijacking, screen clearing, and line overwrites) or Unicode BiDi overrides to disguise executable attachment extensions. Project maintainer Clément Douin (soywod) affirmed the problem and extracted our proposed sanitization into the central pimalaya-cli library, credited Martin Vlach as co-author, and made it available to the sister CLI tools cardamum and calendula, which share the same tables.
What was wrong
When an email user lists envelopes (himalaya envelope list) or inspects attachments, Himalaya formats the message metadata into clean CLI tables. Because this metadata comes directly from external untrusted sources — anyone who sends an email can set arbitrary Subject headers, sender display names, and attachment filenames — printing it raw to a terminal emulator creates severe security risks:
- OSC 52 Clipboard Hijacking: Terminals supporting OSC 52 can be instructed via
\x1b]52;c;...sequences to silently overwrite the user's system clipboard without interaction when an email listing is displayed. - BiDi (Bidirectional) Override Spoofing: Senders could insert Unicode direction override characters (such as
\u{202E}Right-to-Left Override) into attachment names. A dangerous executable namedinvoice\u{202e}fdp.exeis rendered by the terminal asinvoiceexe.pdf, tricking the user into executing malware under the impression it is a PDF document. - Terminal Manipulation & Spoofing: ANSI escape codes like screen clearing (
\x1b[2J), cursor positioning, or color changes (SGR) could wipe terminal history, disguise output, or spoof command prompts. - Incomplete binary check: The existing binary output check did not catch DEL (
0x7F) or C1 control bytes (0x80..=0x9F).
pimalaya-cli 0.2.6, and maintainer Clément Douin's review.How it was fixed
We submitted pull request #771 proposing a sanitization routine and automated test coverage. Project maintainer Clément Douin (soywod) recognized that sister applications across the Pimalaya suite (cardamum for contacts and calendula for calendar) share the same table rendering logic:
“Thanks for the report and the clear write-up, the problem is real. Since cardamum and calendula share the same tables, I moved the function into pimalaya-cli and credited you as co-author. It's released in 0.2.6 as pimalaya_cli::table::sanitize. It's your printable(), with one addition: it also replaces bidi controls (U+202A–202E, U+2066–2069). Without that, a filename like invoice\u{202e}fdp.exe displays as invoiceexe.pdf.”
Following the maintainer's guidance, we completed the PR implementation:
- Bumped
pimalaya-clito 0.2.6 and converted single-line fields across IMAP, JMAP, Gmail, Graph, and pimdir backends to usepimalaya_cli::table::sanitize. - Refined
write_bytes_or_saveinoutput.rsto reject DEL and C1 control bytes on terminal outputs, reading non-UTF-8 bytes as Latin-1 so UTF-8 continuation bytes are not misidentified. - Added unit tests rendering tables from strings containing OSC 52, SGR, screen-clearing sequences, and BiDi overrides.
- Updated the Cairn CLI specification (
cairn/spec/commands.md) to formally require printable sanitization on single-line metadata fields.
Maintainer Clément Douin verified the changes and merged the pull request into master on 3 October 2026:
“Thanks, all good. The Windows-1252 side effect is fine for now; we'll revisit if it turns out to bite. Merging.”
Related later work in Himalaya
After our fix, contributor SORALNV sent two more hardening patches to Himalaya. They are separate work and do not refer to PR #771, but they cover neighbouring ground. Clément Douin merged both:
- PR #782, merged 11 Oct 2026: attachment downloads reserve the destination file with
create_newand write through that handle, so a dangling symlink counts as taken and an existing file is never truncated. The pull request notes that the destination directory itself still has to be trusted. - PR #783, merged 14 Oct 2026: the human-readable
message readandmessage parseviews now replace terminal and BiDi control characters in message bodies, address headers, the subject and MIME metadata. JSON output and explicitly requested raw bytes stay unchanged.
Timeline
- 2 Oct 2026. BudgetScan flagged unescaped metadata rendering in Himalaya's table output. We opened pull request #771 with an initial patch and test suite.
- 2 Oct. Maintainer Clément Douin confirmed the vulnerability, extracted the sanitization routine into the shared library
pimalaya-cli0.2.6 with BiDi override filtering, and credited Martin Vlach as co-author. - 3 Oct. We completed the PR with the updated library, comprehensive backend coverage, and specification updates. Maintainer Douin reviewed and merged the PR into master.
- 11 and 14 Oct. Separately, Clément Douin merged two patches by contributor SORALNV: PR #782 (attachment downloads) and PR #783 (control-character sanitization in the message view).
How to read this
Automated scanning found an unescaped control sequence flaw in CLI tables, and thoughtful maintainer collaboration turned a single-repo bug fix into a shared function for the broader Pimalaya suite. This is open-source security at its best: automated detection generating clear leads, followed by thoughtful human review, robust test cases, and collaborative architectural hardening. Special thanks to Clément Douin for outstanding open-source leadership and swift action.
Sources
- Merged pull request (pimalaya/himalaya#771)
- Maintainer review & co-authorship announcement
- Maintainer merge comment
- pimalaya-cli crate on crates.io
- Official Himalaya repository on GitHub
- Related later work: Pull request #782: Reserve attachment download filenames exclusively and Pull request #783: Sanitize the human-readable message view (both merged)