Skip to content

Fix the top ten audit findings: clipboard and git safety, idle CPU, sidebar order - #436

Merged
arzafran merged 14 commits into
mainfrom
fix/audit-2026-10-19
Oct 19, 2026
Merged

arzafran merged 14 commits into
mainfrom
fix/audit-2026-10-19

Conversation

@arzafran

Copy link
Copy Markdown
Member

What this does

Fixes the top ten items from the October 19 audit. Programs running in a terminal can no longer write the clipboard without asking, and opening a folder can no longer make the app run commands that folder's git config defines. Idle terminals stop waking the CPU ten times a second. The sidebar no longer reshuffles itself on notifications. The app drops the bundled ghostty CLI helper and the omo/omx/omc wrappers.

Summary

Suggested review order: the security commits first (11521e0, b47674f, e2c3113, 0c5a094), then the scrollback log (cb35dde, 5345052, 929f5bb), then the rest.

Security:

  • Clipboard writes (OSC 52) now ask first. The prompt shows a preview and the workspace name, with Deny, Allow, and "Always allow until this terminal closes".
    • After Deny, that terminal's writes are refused silently for 30 seconds.
    • Requests that arrive while a prompt is open are refused, and the open prompt stays. A program writing in a loop can't keep replacing it.
    • The default is set as inline config ahead of the user's files, so an explicit clipboard-write setting still wins.
  • Background git calls neutralize filter drivers defined in the repo's own config. A repo-local lfs.extension.* key also turns off filter.lfs.
    • status and diff pass --ignore-submodules=dirty, so git doesn't run status inside submodules.
    • The filter check is cached per directory. The cached answer is reused only while the inode and ctime of every .git and config that git's discovery could read are unchanged, so a repo extracted into an already-probed folder is read fresh.
  • Shell integration only sends the branch. zsh and bash no longer run git status on every prompt. The app works out whether the repo has changes with its own guarded probe.
  • Worktree commands handle an unknown branch state safely. When git can't say whether the branch exists, add refuses and the cleanup leaves the branch alone.
  • Reported working directories are checked. The OSC 7 directory check runs off the main thread.

Scrollback log:

  • No polling. Logs drain when bytes arrive instead of on a 10 Hz timer, and the heartbeat is hourly.
  • Turning scrollback saving off stops all log work. Escrowed sessions are the exception: they keep logging until they end.
  • Stale snapshots from debug and bench builds expire after 14 days. Release and staging snapshots always stay pinned.
  • Compatibility code older than the 30-day upgrade window is removed: v1 escrow, the unspecified-deny compat, and the one-time geometry migration.
    • The closed-window handling stays, because builds up to 2026-09-30 still write it.

Other:

  • Sidebar: notifications no longer move a workspace to the top, and the sidebar scrolls to the selected workspace.
  • Socket client: keeps reading the reply when setsockopt fails with EINVAL after the server closed. This was the cause of the MCP test flake.
  • CI: ui-regressions no longer waits for the unit-test shards.
  • Removals:
    • The bundled ghostty CLI goes; programa themes is Swift now, and the zig installs and signing steps for the helper are removed from CI.
    • The omo/omx/omc wrappers go; see docs/removed/omo-omx-omc-wrappers.md.
    • Dead sidebar helpers and legacy tab-color keys go.
  • Docs: CHANGELOG and README cleanups.

Accepted risks, per the 30-day decision:

  • A holder process from before August that is still running doesn't know about the scrollback-off setting.
  • A window closed in a 0.5.0-era build could reopen once.
  • Uncommitted changes inside a submodule no longer mark the parent repo as changed. A submodule moved to a new commit still does.
  • The filter-check cache doesn't follow include.path or a linked worktree's common config dir. Using either needs write access to the repo.

A security review covered every security commit up to 0c5a094 and approved them; it was static, with no tests run. A cross-model review covered the first eight commits, and its findings are fixed.

Test Plan

  • CI is green, including the new tests in ConfigFixSweepRegressionTests and TerminalAndGhosttyTests and tests/test_cli_themes.py. These have only been compiled so far.
  • In a tagged build, run printf '\e]52;c;aGk=\a' in a terminal and expect a prompt. Click Deny, run it again within 30 seconds, and expect no prompt and an unchanged clipboard.
  • Run while :; do printf '\e]52;c;aGk=\a'; done and expect one prompt that stays put. Clicking Deny should work.
  • In a repo with a local filter.x.clean = touch /tmp/marker and a .gitattributes that uses it, open a terminal there and expect /tmp/marker never to appear.
  • In bash, edit a file in a repo and expect the dirty dot within a few seconds. Only the syntax of the bash script has been checked so far; zsh was run.
  • With 8 idle workspaces, a tagged Debug build went from 0.67% to 0.20% CPU in one run on one machine. Production was not measured.

The built-in browser was removed on 2026-10-05, but [Unreleased] still
advertised default-browser support and browser cookie methods. Record the
removal instead, and label the README split shortcuts correctly.
It reuses the build-app artifact, so the unit-tests dependency only
lengthened the main-push CI tail by about four minutes.
Darwin fails SO_RCVTIMEO with EINVAL once the peer has closed, so a
server that answered and hung up quickly made the CLI and MCP bridge
report "Failed to configure socket receive timeout" instead of the
reply. Source of the MCPSocketBridgeTests shard-2 flake (run 38943517971).
…ected workspace

Notifications no longer move their workspace to the top, so Cmd+1..9 keep
pointing at the same workspaces. Selecting a workspace that is off screen
now scrolls the sidebar to it by the minimum distance.

Also removes sidebar row layouts left unreachable by the settings removed
in #415, and the legacy tab-color migration keys.
…ppers

`programa themes` now always lists themes instead of opening the
interactive picker, so the app no longer ships a second copy of the
ghostty command-line tool (about 6 MB) and builds skip its Zig compile.
List, set and clear are unchanged; an unknown theme now suggests matches.

The undocumented omo, omx and omc wrappers are removed; see
docs/removed/omo-omx-omc-wrappers.md.
…ing repo filters

Programs in a pane could replace the clipboard through OSC 52 without asking.
Clipboard writes now ask by default, with a preview of the text and an
"Always allow for this terminal" option so tmux and Neovim yanks stop
prompting. A clipboard-write line in your own config still wins.

The sidebar's background git checks no longer run filter commands defined in
a repository's own .git/config, so opening a crafted folder can't run a
command. Filters from global config, such as Git LFS, keep working. A
terminal-reported directory must be an existing absolute path before it is
probed.
Each terminal's scrollback log woke ten times a second and rewrote its
status file with an fsync every five seconds, which was most of the app's
idle CPU. It now wakes only when a terminal prints something, and the status
file is written on real changes plus an hourly liveness update. With
scrollback saving off, nothing wakes unless a session holder still needs
the liveness update.

Old terminal history is cleaned up again: settings files no longer stall
the cleanup, and a saved session from another build that has not been
touched in 14 days no longer keeps its history. The running build's own
saved session always does.

Removes upgrade code for builds older than 30 days: the first-generation
session holder, the retry for holders from before 2026-08-10, and restoring
hidden windows from old snapshots. Old window geometry keys are cleared once
at launch instead of on every save.
…s off the main thread

Only dev and bench snapshots stop pinning scrollback after 14 days, so a
dev build can't sweep production's history after a long break. The OSC 7
directory existence check now runs on a serial queue so a stalled network
mount can't freeze the app.
…lter config when a repo appears

A program sending OSC 52 in a loop replaced the open sheet on every request, so
the user could not click Deny. Requests that arrive while a prompt is open are now
denied and the sheet stays.

The filter-config cache could hand a non-repository directory's empty answer to a
repository extracted into it within 10 s. Cached answers are now tied to the
identity of every .git and config file discovery could read, so they are reused
only while those are unchanged, and the "no filters" answer is cached again.
Repository-local lfs.extension keys match regardless of case.
The filter fixture changed a.txt's size, so git status marked it modified
from the index and never ran the clean filter; the edit now keeps the size.
The sweep test stored its own snapshot outside the directory the sweep scans.
The clipboard replacement test now uses a paste, which still replaces an open
prompt, and a new test covers an OSC 52 request being denied while a prompt
is open.
@arzafran
arzafran merged commit e30f3a9 into main Oct 19, 2026
12 checks passed
@arzafran
arzafran deleted the fix/audit-2026-10-19 branch October 19, 2026 16:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant