feat(pty-proxy): mirror inner shell cwd via OSC 7#3461
Draft
xav-ie wants to merge 1 commit into
Draft
Conversation
xav-ie
added a commit
to xav-ie/atuin
that referenced
this pull request
Jun 16, 2026
Add an OSC 7 (current working directory) path so terminals and
multiplexers that read a pane's cwd via process introspection (e.g. tmux
pane_current_path, Ghostty, Kitty) see the inner shell's directory rather
than the proxy's startup directory.
- New streaming osc7::Parser (mirrors osc133.rs): parses
`ESC ] 7 ; file://<host>/<path> ST`, percent-decodes the path, and
rejects relative paths and `..` traversal (the latter guards against
attacker-controlled pty output redirecting the proxy's cwd).
- The pty output loop feeds bytes to the parser and calls
set_current_dir on each cwd event (silently ignoring non-existent
paths, e.g. an OSC 7 forwarded from an SSH'd remote).
- New `atuin pty-proxy emit-osc7` subcommand does the path encoding in
Rust; each shell's init wires a PWD-change hook (bash PROMPT_COMMAND,
zsh precmd, fish --on-variable PWD, nu env_change.PWD) that shells out
to it, gated on ATUIN_PTY_PROXY_SOCKET.
Ported from atuinsh#3461 onto the renamed atuin-pty-proxy crate (was atuin-hex);
the --shell changes that PR was stacked on are intentionally excluded so
this stays focused on OSC 7.
Contributor
Author
|
Rebased and force-pushed: ported onto the renamed Scoped down to just the OSC 7 feature — this branch was previously stacked on the The OSC 7 streaming parser ( |
Add an OSC 7 (current working directory) path so terminals and
multiplexers that read a pane's cwd via process introspection (e.g. tmux
pane_current_path, Ghostty, Kitty) see the inner shell's directory rather
than the proxy's startup directory.
- New streaming osc7::Parser (mirrors osc133.rs): parses
`ESC ] 7 ; file://<host>/<path> ST`, percent-decodes the path, and
rejects relative paths and `..` traversal (the latter guards against
attacker-controlled pty output redirecting the proxy's cwd).
- The pty output loop feeds bytes to the parser and calls
set_current_dir on each cwd event (silently ignoring non-existent
paths, e.g. an OSC 7 forwarded from an SSH'd remote).
- New `atuin pty-proxy emit-osc7` subcommand does the path encoding in
Rust; each shell's init wires a PWD-change hook (bash PROMPT_COMMAND,
zsh precmd, fish --on-variable PWD, nu env_change.PWD) that shells out
to it, gated on ATUIN_PTY_PROXY_SOCKET.
Ported from atuinsh#3461 onto the renamed atuin-pty-proxy crate (was atuin-hex);
the --shell changes that PR was stacked on are intentionally excluded so
this stays focused on OSC 7.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #3296.
Partially addresses #3337 (cwd-tracking portion; shell-integration hooks are a separate problem).
The bug
Terminals and multiplexers (tmux's
pane_current_path, Ghostty / Kitty / WezTerm "open in same dir", etc.) read a pane's cwd by inspecting the foreground process viaproc_pidinfo(macOS) or/proc/<pid>/cwd(Linux). Whenatuin pty-proxyis active, the foreground process is the proxy — not the inner shell where the user actually runscd. The inner shell lives on a private PTY invisible to the multiplexer.Result: terminal features that depend on knowing the current directory always see the proxy's startup directory, not wherever the user
cd-ed to.The fix
Inner shells emit OSC 7 (
\e]7;file://<host><path>\e\\) on every prompt change. The proxy parses the wire payload from the inner-PTY byte stream andchdirs itself to match. Subsequentproc_pidinfo//proclookups by the terminal then see the correct cwd.All path encoding lives in a new
atuin pty-proxy emit-osc7subcommand — each shell's init only needs a one-line hook that calls it. This avoids per-shell URL encoders (4 implementations with subtly different RFC 3986 edge cases) in favor of one well-tested Rust source of truth, while keeping the wire format strictly RFC 3986–compliant for any other terminal listening to OSC 7.Per-shell hooks:
_atuin_pty_proxy_osc7runs inprecmd(zsh) orPROMPT_COMMAND(bash), gated on$PWDchange.--on-variable PWDevent handler.env_change.PWDhook.Per-prompt cost
The hooks fork+exec
atuin pty-proxy emit-osc7percd(gated on$PWDchange). The atuin shell integration already synchronously forks+execsatuin history starton every preexec, plus a backgroundedatuin history endper command. The OSC 7 emission piggybacks on this existing budget — backgrounded, gated on PWD change — so it adds at most one extraatuininvocation per directory change, strictly less than the existing per-command cost.Trust boundary
The proxy trusts the OSC 7 wire payload and
chdirs based on it. Anyone who can write to the inner PTY can drive the proxy's cwd to any path the user can alreadychdirto themselves. This is the same trust model as every other OSC sequence terminals honor (OSC 8 hyperlinks, OSC 52 clipboard, etc.) — if attacker-controlled output (e.g.cat /random/file) can spew bytes into your terminal, you've already lost.Defense-in-depth in the parser:
..components (defends againstcat /attacker/fileredirecting cwd via traversal).Alternative considered
Kernel-side cwd lookup (
proc_pidinfo//proc/<inner-shell-pid>/cwd) instead of trusting the wire payload would eliminate the trust boundary entirely — the proxy would query the kernel directly and the wire payload would be a decorative refresh trigger. I explored this on a separate branch and it works, but it adds OS-specific code paths (libproc on macOS, procfs on Linux) and the security delta is small relative to the existing OSC trust model. Happy to revisit if you'd prefer that approach.Testing
crates/atuin-pty-proxy/src/osc7.rs, including: streaming chunk boundaries, BEL vs ESC \ terminators, percent-encoded multibyte UTF-8, malformed URI rejection, relative-path rejection,..-traversal rejection, over-long payload drop.cdin the inner shell, thentmux split-windowopens a new pane in the right directory.#[cfg(unix)]so Windows builds (atuinwith default features includingpty-proxy) remain unaffected.Checks