Commit Graph

16940 Commits

Author SHA1 Message Date
Mitchell Hashimoto
7a047553c7 macos: avoid IOSurface leak on automated surface creation
Fixes #13444

A close while AppKit temporarily cleared/changed a surface's window would
leak the surface in the controller's pslit tree. This retained surface kept
a bunch of resources around, particularly large IOSurfaces.

This seems to only be reproducible under scripted load: rapid terminal
creation/destruction so that destruction happens just while there is a nil
window on a surface view.

Track surface ownership in a weak controller map updated alongside the split
tree, with validated fallbacks for existing attachment state. Resolve
scripted and App Intent operations through that ownership, and route
non-confirming root closes directly through the immediate tab or window close
path so teardown always reaches the renderer.
2026-08-05 13:39:48 -07:00
Mitchell Hashimoto
54fe8e1885 macos: handled untrusted OSC8 hyperlinks more carefully (#13634)
OSC8 hyperlinks previously executed directly via the NSWorkspace opener
so a malicious application can just do whatever it wanted and trick the
user into opening something through Launch Services.

This PR notifies apprt of OSC8 hyperlinks so they can be handled
specially. In this PR, I added macOS-specific handling of OSC8 through a
variety of improvements:

  - Preview text is sanitized, so invisible Unicode characters now show.
- Questionable-looking URLs require confirmation to open, but a user can
confirm to open.
- Very questionable or definitely unsafe URLs are blocked with an alert
that only allows the user to copy the link. The alert also notifies the
user why.

This PR also adds an explicit `link-osc8` config (default true) that
users can use to disable osc8 completely.

## Demos

### Custom URL Schemes (Confirm)

<img width="1432" height="1110" alt="CleanShot 2026-08-05 at 10 25
57@2x"
src="https://github.com/user-attachments/assets/f7773ca2-3389-4749-a5c9-393ae097c044"
/>

### Invisible Characters (Block)

<img width="1432" height="1110" alt="CleanShot 2026-08-05 at 10 26
44@2x"
src="https://github.com/user-attachments/assets/bd2d0f33-f128-46e8-9bdb-227afecbb942"
/>

### Executable Target (Block)

<img width="1432" height="1110" alt="CleanShot 2026-08-05 at 10 27
31@2x"
src="https://github.com/user-attachments/assets/080c0524-2c8e-4931-892f-d2643a5d0d4e"
/>
2026-08-05 10:44:56 -07:00
Mitchell Hashimoto
77537c8065 macos: handled untrusted OSC8 hyperlinks more carefully
OSC8 hyperlinks previously executed directly via the NSWorkspace opener
so a malicious application can just do whatever it wanted and trick the
user into opening something through Launch Services.

This PR notifies apprt of OSC8 hyperlinks so they can be handled
specially. In this PR, I added macOS-specific handling of OSC8 through a
variety of improvements:

  - Preview text is sanitized, so invisible Unicode characters now show.
  - Questionable-looking URLs require confirmation to open, but a user
    can confirm to open.
  - Very questionable or definitely unsafe URLs are blocked with an 
    alert that only allows the user to copy the link. The alert also
    notifies the user why.
2026-08-05 10:22:28 -07:00
Mitchell Hashimoto
46767b5213 terminal: bound OSC and grapheme allocations (#13633)
Cap allocating OSC payloads at 8 MiB and retain at most 64 grapheme
suffix codepoints per cell. Our limits are generous compared to other
terminals and this prevents an easy DoS.

When the grapheme codepoint max is hit we just ignore any remainders.
This can result in real broken graphemes because Unicode spec is really
unbounded on them but for all practical use cases its reasonable.

Compared to other terminals:

| Terminal | OSC capture limit | Cell codepoints | 
| --- | ---: | ---: |
| Ghostty | 8 MiB | 65 |
| kitty | ~256 KiB ordinary | 24 |
| VTE | 4,096 scalars | 11 |
| xterm | 20 or 600 KB | 3 default, 6 max |
| Alacritty | unbounded | unbounded |
| WezTerm | unbounded | no explicit limit |
2026-08-05 09:52:11 -07:00
Mitchell Hashimoto
ad27c989a4 libghostty-vt: require opt-in for title reports (#13632)
Add an explicit libghostty-vt title-report option and keep CSI 21 t
disabled unless an embedder enables it.

Previously, registering the general PTY write callback also caused the
terminal to echo attacker-controlled window titles. This exposed
embedders to command injection after user interaction. Ghostty fixed
this a long time ago by making CSI 21 t an opt-in in the config. Do the
same but with our C/Zig API.
2026-08-05 09:32:45 -07:00
Mitchell Hashimoto
727b8a02f8 terminal: bound OSC and grapheme allocations
Cap allocating OSC payloads at 8 MiB and retain at most 64 grapheme
suffix codepoints per cell. Our limits are generous compared to other
terminals and this prevents an easy DoS.

When the grapheme codepoint max is hit we just ignore any remainders.
This can result in real broken graphemes because Unicode spec is really
unbounded on them but for all practical use cases its reasonable.

Compared to other terminals:

| Terminal | OSC capture limit | Cell codepoints |
| --- | ---: | ---: |
| Ghostty | 8 MiB | 65 |
| kitty | ~256 KiB ordinary | 24 |
| VTE | 4,096 scalars | 11 |
| xterm | 20 or 600 KB | 3 default, 6 max |
| Alacritty | unbounded | unbounded |
| WezTerm | unbounded | no explicit limit |
2026-08-05 09:30:24 -07:00
Mitchell Hashimoto
38e891e6c0 terminal: require opt-in for title reports
Add an explicit libghostty-vt title-report option and keep CSI 21 t
disabled unless an embedder enables it.

Previously, registering the general PTY write callback also caused the
terminal to echo attacker-controlled window titles. This exposed
embedders to command injection after user interaction.

Gate the response in the shared terminal stream, append the C API
option without renumbering existing values, and cover the default,
opt-in, and reset behavior in Zig and C API tests.
2026-08-05 09:17:03 -07:00
Mitchell Hashimoto
bd21ff153e terminal: avoid VS15 cursor underflow (#13631)
Handle VS15 width changes when the wide grapheme base is directly under
the cursor.

A zero cursor distance previously underflowed while locating the spacer
tail. Debug builds panicked and ReleaseFast computed an out-of-bounds
cell pointer before updating it.
2026-08-05 09:10:46 -07:00
Mitchell Hashimoto
fe98aef21c terminal: report DECECM as permanently reset (#12660)
Closes #12505 

This PR allows Ghostty to respond to DECRQM queries for DECECM with the
"permanently reset".

AI disclosure: I used Codex to help inspect the relevant code path and
explain the issue, but I reviewed and made the code changes myself.
2026-08-05 09:03:15 -07:00
Mitchell Hashimoto
33d34cf5ce terminal: avoid VS15 cursor underflow
Handle VS15 width changes when the wide grapheme base is directly under
the cursor. Cover both disabled wraparound and restored pending-wrap
cursor states.

A zero cursor distance previously underflowed while locating the spacer
tail. Debug builds panicked and ReleaseFast computed an out-of-bounds
cell pointer before updating it.

Find the spacer from the wide base instead of subtracting from the
cursor distance. Reposition the cursor from the base column and clamp it
to the active right margin.
2026-08-05 08:55:21 -07:00
Mitchell Hashimoto
dd035284c2 Kitty graphics protocol bugs (#13630)
Specifics in each commit message. This will be part of a security
advisory in 1.4.0 since these patches issues related to overflows, DoS,
unbounded memory allocation, etc.
2026-08-05 08:53:44 -07:00
Jeffrey C. Ollie
e2065583a4 core: transfer long key encoding buffer (#13628)
The long-preedit fallback introduced in e95b1707c intentionally
allocated twice. The encoder wrote into an oversized caller-owned buffer
and returned only the written subslice, so transferring it required
manually shrinking the allocation or tracking its original capacity. The
copy kept that rare path simple.

The key encoder moved to std.Io.Writer.Allocating in 44496df899. Its
toOwnedSlice method handles shrinking and ownership transfer, remapping
when the allocator supports it and falling back to an allocation and
copy when it does not. Use it directly for WriteReq.alloc to remove the
guaranteed second allocation while preserving cleanup on failure.
2026-08-05 10:44:16 -05:00
Mitchell Hashimoto
402b9227de terminal/kitty: reclaim pruned placements
Reclaim pin-backed Kitty graphics placements after their tracked screen
content is pruned. Treat garbage pins as non-renderable until the next
placement command sweeps them.

Placements that scrolled beyond retained history previously remained in
the placement map and tracked-pin set. Long-running graphics output could
accumulate stale state, and remapped garbage pins could appear at an
unrelated fallback location.

Sweep garbage placements before growing the placement map, releasing each
tracked pin while preserving virtual placements. Return no geometry or
visible render position for garbage pins and cover both storage and C API
behavior with regression tests.
2026-08-05 08:37:45 -07:00
Mitchell Hashimoto
d0c516f8f3 terminal/kitty: release replaced placement pins
Release a Kitty graphics placement's tracked pin before replacement.

Repeated updates to an external placement previously leaked tracked pins.

Pass the owning screen to storage and deinitialize the old placement.
2026-08-05 08:28:13 -07:00
Mitchell Hashimoto
590d669c4a terminal/kitty: limit png decoder allocations
Limit individual allocator requests made by PNG decoders to the Kitty
graphics protocol's 400 MiB image ceiling. Add a reusable allocator
wrapper for callers that need per-request bounds.

PNG decoding previously used Wuffs' 4 GiB package limit and checked
the result only after allocation. A tiny PNG with oversized dimensions
could cause a multi-gigabyte RSS spike before being rejected.

Wrap decoder allocators with LimitedAllocator and translate limit
rejections to invalid image data while preserving genuine out-of-memory
errors. Add allocator boundary tests and regression coverage for a
crafted PNG below Wuffs' limit.
2026-08-05 08:24:42 -07:00
Mitchell Hashimoto
f766f303a7 terminal/kitty: validate shared memory ranges
Validate Kitty shared memory byte ranges before mapping and copying
image data. Interpret S as a byte count from O and preserve default
raw-image sizing.

Shared memory transmissions previously multiplied untrusted u32
dimensions before the limit check and sliced mappings with an unchecked
offset. Malformed commands could panic in safe builds or request a
wrapped allocation in fast builds.

Reject oversized dimensions before widening size arithmetic, derive
bounded ranges from the stat size, and enforce max_size before
constructing a slice. Add regression tests for explicit and implicit
offsets, out-of-bounds offsets, and maximum dimensions.
2026-08-05 08:20:41 -07:00
Mitchell Hashimoto
ec04900ab9 terminal/kitty: validate opened image file paths
Validate Kitty file transmissions against a canonical path derived from
the open file handle. Keep temporary file policy and cleanup keyed to
that handle path.

Path validation previously ran before opening, so a local cooperating
process could replace a symlink or directory entry and make Ghostty
read a blocklisted file.

Open the submitted path once, derive its canonical path from the handle,
and use the same handle for stat and reads. Add a regression test that
replaces a blocked symlink after open and verifies the pinned target is
still rejected.
2026-08-05 08:14:47 -07:00
Jon Parise
fb4c56159f core: transfer long key encoding buffer
The long-preedit fallback introduced in e95b1707c intentionally allocated
twice. The encoder wrote into an oversized caller-owned buffer and returned
only the written subslice, so transferring it required manually shrinking
the allocation or tracking its original capacity. The copy kept that rare
path simple.

The key encoder moved to std.Io.Writer.Allocating in 44496df899. Its
toOwnedSlice method handles shrinking and ownership transfer, remapping when
the allocator supports it and falling back to an allocation and copy when it
does not. Use it directly for WriteReq.alloc to remove the guaranteed second
allocation while preserving cleanup on failure.
2026-08-05 11:11:30 -04:00
Mitchell Hashimoto
e5840bb9ba terminal/kitty: harden placement geometry
Treat Kitty placement dimensions and offsets as untrusted values when
calculating pixel, grid, and rectangle geometry. Saturate results that
do not fit and return no rectangle when missing pixel metrics produces
an empty grid.

Unchecked u32 arithmetic previously panicked in safe builds and wrapped
in fast builds. A zero row count could underflow into a maximum-size
page traversal, while maximum dimensions could spin cursor movement or
overflow render visibility calculations.

Use checked integer scaling instead of floating-point casts, saturating
arithmetic for extents and cursor columns, and bound off-screen cursor
work to the terminal row count. Compute C API visibility in i64 and
cover maximum protocol values in storage, execution, and render-info
tests.
2026-08-05 08:07:25 -07:00
Mitchell Hashimoto
af2faa311a terminal/kitty: restrict temporary image file paths
Require temporary image file paths to match complete directory
components when checking /tmp, /dev/shm, the configured temporary
directory, and its resolved path.

The previous byte-prefix checks accepted similarly named sibling
directories such as /tmpX. A temporary-file transmission could read
and unlink a file outside the permitted temporary directories.

Add a component-boundary helper and regression coverage for built-in
and configured directory prefixes. An integration test also verifies
that a rejected file remains on disk.
2026-08-05 08:07:25 -07:00
Mitchell Hashimoto
d866fa4553 terminal/kitty: fix graphics range deletion
Use inclusive image ID bounds for the Kitty graphics protocol range 
delete operation.

Range deletion previously joined the lower and upper bound checks with or, 
which matched every placement for any valid range. A targeted delete could 
therefore remove every graphics placement.

Join the bounds with and and update the lowercase and uppercase range tests 
to keep placements below and above the selected interval.
2026-08-05 08:05:27 -07:00
Mitchell Hashimoto
5944ab286d macOS: suppress restart tips for auto update (#13623)
With this, users will not be prompted to restart the app when an
automatic update is ready. Relaunching with this state will have no
visible difference to users.

If the user checks for updates manually, either by menu, command
palette, or keybind, a restart alert will be prompted.

Closes #13478

Auto updates:


https://github.com/user-attachments/assets/f77f65a1-6e2f-4002-961c-fee4b73d447e

Manual Updates: 


https://github.com/user-attachments/assets/8a52a5a0-2022-413f-97c5-6f9a2eb26e7a
2026-08-05 06:57:18 -07:00
Mitchell Hashimoto
3f8b99bb68 terminal: print repeated characters through printSlice (#13625)
`printRepeat` (CSI `b`, repeat the previous character N times) calls
`print()` once per repeat, so something like `\x1b[2000b` ran grapheme
checks, width lookups, wrap handling, and the integrity assert 2000
times for what is usually the same character on the same row.

`Terminal.print` was 24% of samples on a REP-heavy micro benchmark.

This PR just aims to add a fast path by introducing a chunking
mechanism. anything that needs care (insert mode, grapheme clustering,
hyperlinks) still falls back to per-codepoint print() inside printSlice,
so behavior *should* stay unchanged.

Some profiling data:

Generated with some plain stupid logic:

```py
D = "benchdata"
parts, total = [], 0
while total < 40_000_000:
    line = "x" + "\x1b[80b" + "y" + "\x1b[35b" + "\r\n"
    parts.append(line); total += len(line)
open(f"{D}/rep.bin", "wb").write("".join(parts).encode())
```
**macOS (hyperfine, 15 runs, warmup 3):**

| | mean |
|---|---|
| before | 2.360 s |
| after | 1.166 s |


And now the really interesting and promising stuff

**Linux, 24-core NixOS x86_64 (poop, 6s sampling):**

| | wall_time | instructions | branch_misses | peak_rss |
|---|---|---|---|---|
| before | 1.51 s | 50.9 G | 9.41 M | 6.82 MB |
| after | 562 ms | 9.07 G | 114 K | 6.74 MB |
2026-08-05 06:56:01 -07:00
Mitchell Hashimoto
19e9f49089 surface: use id instead of intFromPtr (#13620)
intFromPtr was always a hack that we had to use before we had stable
surface IDs, and it was always slightly unsafe. Let's do it properly
this time.
2026-08-05 06:55:18 -07:00
Uzair Aftab
5b70f208bc terminal: print repeated characters through printSlice
While doing some work on my tmux fork I noticed multiple parts of
libghostty-vt was slower than tmux equivalents(isolated). Turns out they
do some smart stuff there.

printRepeat called print() once per repeat, so something like \x1b[2000b
ran grapheme checks, width lookups, wrap handling, etc etc 2000 times.

printSlice is already documented as semantically identical to
calling print per codepoint, so this just feeds the repeated
codepoint through it in 4096-entry stack chunks. Simple runs take
the batched fast path, and anything that needs care falls back to the
previous behaviour.
2026-08-05 15:43:25 +02:00
Lukas
c93752a008 macOS: suppress restart tips for auto update 2026-08-05 15:15:40 +02:00
Leah Amelia Chen
f17b425aac surface: use id instead of intFromPtr
intFromPtr was always a hack that we had to use before we had stable
surface IDs, and it was always slightly unsafe. Let's do it properly
this time.
2026-08-05 14:40:45 +08:00
Leah Amelia Chen
9e30f70f23 gtk: do not set bell ringing if already focused (#13597) 2026-08-05 14:18:45 +08:00
ghostty-vouch[bot]
2346c4fe47 Update VOUCHED list (#13617)
Triggered by
[comment](https://github.com/ghostty-org/ghostty/issues/12984#issuecomment-5187316604)
from @mitchellh.

Denounce: @jamesarch

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-05 03:50:37 +00:00
Mitchell Hashimoto
d7f7a4e736 macOS: rename UpdateState.isIdle to isHidden (#13613) 2026-08-04 19:28:05 -07:00
Mitchell Hashimoto
d2f08f1589 config: limit command translations to GTK (#13615)
Fixes #13614

Only translate the shared default commands when building the GTK
runtime. macOS now use the source strings until we do broader
localization.
2026-08-04 19:27:52 -07:00
Mitchell Hashimoto
a00d155e9c config: limit command translations to GTK
Fixes #13614

Only translate the shared default commands when building the GTK runtime. 
macOS now use the source strings until we do broader localization.
2026-08-04 15:23:09 -07:00
Mitchell Hashimoto
ccb08f35f6 macOS: show cancel update option when its actually cancellable (#13612)
`extracting` and `installing` state aren't cancellable by us.

> Recommend reviewing with whitespace hidden
2026-08-04 14:07:38 -07:00
Lukas
a86c49d7af macOS: rename UpdateState.isIdle to isHidden 2026-08-04 22:38:35 +02:00
Lukas
63d08c0342 macOS: show cancel update option when its actually cancellable
`extracting` and `installing` state aren't cancellable by us
2026-08-04 22:18:35 +02:00
Mitchell Hashimoto
b8ab2ff168 macOS: show/search description when subtitle missing in CommandPalette (#13610)
<img width="1125" height="552" alt="image"
src="https://github.com/user-attachments/assets/09866c9b-d5c4-422f-860b-de4de4cca055"
/>
2026-08-04 11:20:01 -07:00
Mitchell Hashimoto
760a250029 config: formatted action should be parsable into the original (#13609)
This fixes the issue where an action with string as it's parameter is
not working correctly in CommandPalette, found in #9671. For example:

```
command-palette-entry = title:"Set Ghostty Title",description:test sending text.,action:set_tab_title:👻
keybind=cmd+r=set_tab_title:👻
```

Keybind works perfectly, but the title is escaped when triggering in
CommandPalette.

> Introduced in
[#8873](https://github.com/ghostty-org/ghostty/pull/8873/changes#diff-9e7936787320bcf70e332c868125039d8c0a7f96c4a88f2af0af21d952c6830dR1216),
I tested the fixed issue as well, the following config still parses
correctly, mentioned in
https://github.com/ghostty-org/ghostty/issues/8849#issuecomment-3322018212.

```
command-palette-entry = title:Focus Split: Next,description:"Focus the next split, if any.",action:goto_split:next
```

Also `ghostty +show-config` now will also output the readable strings as
well.
<img width="1078" height="428" alt="image"
src="https://github.com/user-attachments/assets/f9dc1447-7b4e-44f4-8362-b54f4d805c7a"
/>
2026-08-04 11:19:37 -07:00
Jeffrey C. Ollie
51cf099678 datastruct: remove unused LRU implementation (#13607) 2026-08-04 13:18:34 -05:00
Lukas
066a0b7c45 macOS: show description when subtitle missing in CommandPalette 2026-08-04 20:03:04 +02:00
Lukas
b67f8ef51d config: don't escape Binding.Action.String 2026-08-04 19:49:37 +02:00
Lukas
8cfbaf545a config: formatted action should be parsable into the original 2026-08-04 19:49:37 +02:00
Uzair Aftab
02f34835ea datastruct: remove unused LRU implementation 2026-08-04 19:46:26 +02:00
Mitchell Hashimoto
48d85eaeb0 core: fix mouse reporting mutex lock 2026-08-04 09:10:14 -07:00
Mitchell Hashimoto
ca56412bf2 gtk: forward middle click to TUIs with mouse reporting (#13108)
Fix for Issue #12940 
I actually do not know if this has already been resolved and the issue
is just still open. Either way, here's a fix. Now we run a check to see
if the current program is accepting mouse events before discarding the
middle click.
2026-08-04 08:53:39 -07:00
ghostty-vouch[bot]
08342c9244 Update VOUCHED list (#13603)
Triggered by [discussion
comment](https://github.com/ghostty-org/ghostty/discussions/13602#discussioncomment-17895866)
from @jcollie.

Vouch: @UnsaltedScholar

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-04 15:33:09 +00:00
Mitchell Hashimoto
594ee212bc macos: defer transparent titlebar KVO rebinding (#13601)
Fixes #13386, based on
https://github.com/mustafa0x/ghostty/commit/a8c090

Defer transparent-titlebar KVO rebinding to the next main-queue turn.
Track the observed tab group so unchanged bindings are preserved.

Previously, a tab-group callback could invalidate and recreate its own
observation before returning, leaving closed terminal windows registered
with AppKit after the undo timeout. These windows accumulated titlebar
and layer state, increasing memory use and WindowServer CPU with tab
churn.

Validated with an AppDelegate change that sat and created/closed tabs in
a loop, then counted weak controllers/windows/nsapp window.
2026-08-04 07:20:05 -07:00
Mitchell Hashimoto
363e6e6b42 i18n: translation support for command palete (#11641)
Most obvious next step in translating Ghostty is the command palette.
Added support for i18n.N_ (https://docs.gtk.org/glib/i18n.html#macros).
Made a Latvian translation for the command palette to test. Codex did
bulk of the translations but I verified them.
2026-08-04 06:43:38 -07:00
Mitchell Hashimoto
cfa0ca7106 macos: defer transparent titlebar KVO rebinding
Fixes #13386

Defer transparent-titlebar KVO rebinding to the next main-queue turn.
Track the observed tab group so unchanged bindings are preserved.

Previously, a tab-group callback could invalidate and recreate its own
observation before returning, leaving closed terminal windows registered
with AppKit after the undo timeout. These windows accumulated titlebar and
layer state, increasing memory use and WindowServer CPU with tab churn.

Validated with an AppDelegate change that sat and created/closed tabs
in a loop, then counted weak controllers/windows/nsapp window.

Co-authored-by: Mustafa J <mustafa.0x@gmail.com>
2026-08-04 06:40:41 -07:00
Jon Parise
1f6e26642e config: clarify cursor-click-to-move's relation to shell-integration (#13589) 2026-08-04 08:54:06 -04:00
Lauri Tirkkonen
85083d23cd config: clarify cursor-click-to-move's relation to shell-integration
the original wording is a bit confusing; I thought cursor-click-to-move
required shell-integration to be enabled, and was confused when the
mouse was still moving my cursor in fish even with
shell-integration=none.
2026-08-04 20:59:52 +09:00