· 6 min read ·

iTerm2's Feature Surface Is a Security Liability When You cat Files

Source: hackernews

The premise sounds absurd: catting a text file in your terminal can compromise your machine. But it is not absurd at all if you understand how terminal emulators work, and this writeup on the calif.io blog lays out concretely how iTerm2’s extensive escape sequence support transforms untrusted file content into an attack surface.

To understand why, you need to understand what a terminal emulator actually does.

Terminals Are Interpreters, Not Viewers

Every terminal emulator is, at its core, a parser for a byte stream. Mixed in with the printable characters are control sequences: the ANSI/VT100 standard defines things like cursor movement (ESC[A moves up), color (ESC[31m sets red foreground), and screen clearing (ESC[2J). The terminal emulator intercepts these before they reach your eyes and acts on them.

This works fine as long as the byte stream comes from a trusted source, like a program you invoked yourself. But cat does no filtering. It reads bytes from a file and writes them to stdout. If those bytes include control sequences, your terminal emulator processes every single one of them.

The classic attacks on this model are old. Injecting ESC]2;malicious title^G (the OSC 2 sequence) renames your terminal window. ESC[1000A scrolls your terminal buffer up so it looks empty. These are annoyances, not critical vulnerabilities.

The problem with iTerm2 is that it supports many sequences far beyond the base standard, and some of them have real side effects.

The OSC 1337 Problem

iTerm2 introduced a proprietary set of escape sequences under OSC 1337 (Operating System Command 1337) for features like inline image display, file transfers, and rich text. Some examples:

ESC]1337;File=name=evil.sh;size=42;inline=0:<base64 content>^G

This sequence, when processed by iTerm2, can write a file to disk. The inline=0 variant downloads the file rather than displaying it inline. The filename and content are both attacker-controlled.

There is also:

ESC]1337;SetUserVar=MYVAR=<base64 value>^G

This sets an iTerm2 user variable. User variables are accessible in iTerm2’s Triggers and Badges features. If a user has configured a Trigger that fires on a particular variable change and runs a shell command, setting that variable via an escape sequence in a file is sufficient to execute arbitrary code.

The attack chain is: craft a file containing the SetUserVar sequence, wait for someone with a matching Trigger configured to cat it, get code execution.

Clipboard Hijacking via OSC 52

OSC 52 is not iTerm2-specific, it is part of the xterm specification, but iTerm2 supports it by default:

ESC]52;c;<base64 encoded content>^G

When a terminal emulator processes this sequence, it replaces the system clipboard with the base64-decoded content. Embedding this in a file that someone is likely to cat, such as a README in a repository, means you can silently overwrite their clipboard with whatever you want. The obvious target is replacing a legitimate wallet address, SSH command, or curl | bash installer URL that the user is about to paste.

This attack requires no special configuration on the victim’s part. iTerm2 enables OSC 52 clipboard writing by default.

OSC 8 is the terminal hyperlink specification, supported by iTerm2, Kitty, VTE-based terminals, and others. It works like HTML anchors:

ESC]8;;https://evil.example.com^GESC\Click here to visit the docs^GESC]8;;^GESC\

The visible text is “Click here to visit the docs”. The actual link target is https://evil.example.com. A user who hovers and clicks without checking the status bar gets sent somewhere unexpected. In an age of OAuth flows and credential prompts that live in browsers, this is a credible phishing vector embedded in a plain text file.

The CVE-2019-9535 Precedent

None of this is without precedent in iTerm2’s history. CVE-2019-9535 was a critical remote code execution vulnerability discovered by security researchers at Radically Open Security, commissioned by Mozilla. The vulnerability lived in iTerm2’s tmux integration.

When tmux integration is active, iTerm2 parses tmux control-mode output to synchronize pane layouts and state. The researchers found that by injecting crafted sequences into the tmux output, an attacker who could influence any byte flowing through the terminal, including content from an SSH server or a file being catted, could achieve arbitrary code execution on the local machine. This affected iTerm2 versions before 3.3.6.

The architecture that made it possible is the same one that makes the current class of attacks work: iTerm2 trusts the byte stream far too much.

Comparison: What Other Terminals Do

This is where the comparison gets instructive. iTerm2’s vulnerability surface is substantially larger than most alternatives specifically because it offers more features that respond to escape sequences.

Alacritty is written in Rust and is deliberately minimal. It supports VT100, VT220, and xterm-compatible sequences but does not implement OSC 1337 file transfers, does not support tmux integration at the escape-sequence level, and does not have a user-variable system. Its attack surface via crafted files is limited to the standard set: clipboard writes via OSC 52 (which Alacritty allows by default), title injection, and cursor manipulation.

Kitty has a richer feature set comparable to iTerm2, including its own graphics protocol, but Kitty’s security documentation addresses these concerns explicitly. The kitty graphics protocol requires the application to explicitly request image display and includes limits on where data can be written. Kitty also supports disabling clipboard writes from escape sequences via the clipboard_control option.

macOS Terminal.app implements a much smaller subset of sequences than iTerm2 and has no equivalent to OSC 1337. It still accepts OSC 52 clipboard writes and OSC 8 hyperlinks, so it is not immune, but its attack surface is meaningfully narrower.

The pattern is: features add escape sequences, escape sequences add attack surface, and attack surface compounds when files from untrusted sources enter your terminal.

The Broader Principle

The vulnerability class here is not new. Security researchers have been documenting terminal injection attacks for decades. What has changed is the complexity of modern terminal emulators.

The VT100, introduced in 1978, had a fixed set of sequences and no extensibility. Modern terminals like iTerm2 are closer to web browsers than to the original VT100: they render rich media, execute custom protocols, integrate with external tools, and maintain stateful user-configurable behaviors that react to the byte stream they display.

Web browsers learned to handle this by treating all content as untrusted by default and enforcing a strict separation between content and instructions. Content Security Policy, sandboxed iframes, and the distinction between data URLs and navigation are all products of hard lessons about trusting byte streams.

Terminal emulators have not gone through the same process. There is no terminal equivalent of CSP. There is no sandboxed rendering context for escape sequences coming from files versus programs. The terminal emulator applies its full feature set to every byte, regardless of provenance.

What You Can Do

The practical mitigations are not satisfying but they are real.

In iTerm2’s preferences, you can disable “Allow clipboard access to terminal apps” under the General tab, which blocks OSC 52 writes. You can audit your Triggers configuration and remove any that execute shell commands. You can disable tmux integration if you do not use it.

For viewing untrusted files, cat -v forces non-printable characters to display as visible escape sequences rather than being interpreted. less -R with a restricted set of allowed sequences is safer than raw cat. Some people alias cat to a wrapper that strips escape sequences before display.

But the root issue is architectural. iTerm2 has accumulated a large number of features that all consume the same untrusted byte stream. The safest thing is treating files from untrusted sources the same way you would treat untrusted HTML: with a tool designed to handle the threat model, not the one that happens to be open.

If you spend time in repositories you did not write, reviewing code from strangers, or SSHing into machines you do not control, the byte stream flowing through your terminal is not always yours. The terminal emulator you choose determines what that byte stream can do to your machine.

Was this interesting?