Repository navigation
Add a tmux-socket mount word for session tongs - #96
Open
CrypticSwarm wants to merge 10 commits into
Open
CrypticSwarm wants to merge 10 commits into
CrypticSwarm wants to merge 10 commits into
Conversation
CrypticSwarm
force-pushed
the
tmux-support
branch
from
October 6, 2026 15:09
561cbee to
2486898
Compare
A tmux socket is host command execution as its owner, as strong as the docker socket, so the word comes with the narrowest grammar the mount vocabulary allows: no target, and no write access. tmux-socket and tmux-socket:ro are accepted and tmux-socket:rw is refused, by one helper that validation and argv assembly both ask, so the two cannot disagree. A client only needs connect() on the socket, which a read-only bind allows; a writable one would let the tong replace the socket the host's own clients dial. The mount binds the socket's directory rather than the socket, since tmux deletes and recreates the socket when its server restarts. It lands at the fixed /run/swarmforge-tmux, beside rather than under the secret tmpfs at /run/swarmforge, so validation never depends on the host environment, and it is reserved so a workspace target cannot overlap it. The host directory arrives as a keyword, like the docker socket path; nothing in the pure core reads the environment.
The tong gets the server through TMUX, re-pointed from the launcher's socket path to the same socket under /run/swarmforge-tmux, so a stock tmux client reaches it with no flags. TMUX_PANE gives it the caller's pane as tmux's default target, from which it can work out the window without the launcher running any tmux command, and SWARMFORGE_SESSION_HANDLE names the session it serves. A session tmux-socket tong also gets SWARMFORGE_WORKSPACE_HOST_PATH, as a session docker-socket broker does. These describe the host and the session, which a definition cannot know, so they replace a plain value its env: sets for those names, and a name the launcher has no value for is dropped rather than left to the definition. A secret reference on one of them would be exported by the FIFO wrapper after docker's -e values and win, so validation refuses it. A tong without the mount gets none of them, and the argv builder refuses the mount outright on anything but a session tong, so no caller can hand a shared container a tmux server. $TMUX is parsed strictly and the launch stops on anything unexpected. The socket must sit directly in tmux's own per-user directory (tmux-<uid>): the mount binds the socket's parent, so a socket at an arbitrary -S path could otherwise hand the tong a home directory. The path also may not carry a ':' (docker's -v separator), a ',' (tmux reads the path up to the first one), or whitespace.
The launcher reads its own TMUX and TMUX_PANE into its options and passes them, with the session handle, to each session tong it starts. The environment is read only there, in the launcher's entry layer; the pure core takes the values as arguments. A launch whose tongs mount tmux-socket stops before anything starts when the launcher is not in a tmux pane, when TMUX or TMUX_PANE is malformed or the socket is outside tmux's default directory, or when a definition makes one of the names the launcher sets a secret reference, so a bad value never leaves a half-started session to tear down. A shared tong may not mount tmux-socket, for the reason it may not mount the workspace: its container outlives the session and is reused by later ones, so it would carry one session's tmux server, and its pinning to that session's pane, into the next. Shared tongs are never handed the values even if one got that far. The approval prompt calls tmux socket access out on its own line, as it does the docker socket. The line says what the grant really is: the bind covers the whole socket directory, so every tmux server the user runs there, and through any of them any command on the host; the mount being read-only does not narrow that.
The $TMUX parse judges only the spelling of the socket path, but the mount binds the whole directory, and docker follows a symlink in a bind source to wherever it points. Before anything starts, the launcher now refuses a directory reached through a symlink, one that is not a real directory, one owned by another uid, or one open to group or others (tmux creates it 0700), and a socket path that does not hold a socket. Every directory above it must also pass OpenSSH's StrictModes rule: owned by root or the launcher's uid, and not writable by group or others unless sticky, as /tmp is. Without that, a TMUX_TMPDIR under a world-writable, non-sticky directory would let another local user swap the socket directory for a symlink between the check and docker's bind. The check runs on the host side of the launcher, so the pure core stays free of filesystem access, and run_with_tongs takes it as an injectable seam so tests can use a $TMUX that names no real socket. It takes the socket path from the same parse that yields the directory, so $TMUX is read in one place.
The mounts section gains the third word: what it grants -- every tmux server in the user's socket directory, not just the launcher's, with the pane a default target rather than a restriction -- why it is read-only and what read-only does not limit, why it is session-only, the requirement that the launcher run in a tmux pane with a socket in tmux's default directory, the on-disk checks of that directory, and the environment the tong receives, including that the launcher's names may not be secret references. The tongs overview names tmux-socket beside the workspace as a mount a shared tong may not have. The approval page lists the tmux server among what the prompt shows, and the environment reference gains the variables a tmux-socket tong is given and the two the launcher reads to give them.
validate_tong, tong_mount_specs and workspace_mount_placements each looped over mounts: with their own copy of the rules. checked_mounts is now the one walk, so a mount is accepted or refused the same way, with the same message, wherever it is checked.
run_with_tongs parsed $TMUX and $TMUX_PANE to check them, then tong_run_argv parsed them again for every tmux-socket tong. resolve_tmux_server now parses both into a TmuxServer that run_with_tongs checks and hands down. run_with_tongs no longer re-checks the secret env that validation already refused.
The shared-tong rule for workspace and tmux-socket lived in unsupported_tong_reasons and an argv raise, so `tongs validate` passed a definition the launch then refused. mount_lifecycle_error now holds it, and checked_mounts applies it everywhere.
Docstrings and docs restated the same rules in several places: the approval gate, the shared-mount rule, the tmux env, and "validation and argv agree". Each is now stated where it lives, and the others point there.
CrypticSwarm
force-pushed
the
tmux-support
branch
from
October 11, 2026 20:12
2486898 to
7b14e55
Compare
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.
Stacked on #95, which makes closing a tmux pane tear down session tongs; this mount requires the launcher to run in one.
A
sessiontong can now declaretmux-socketinmounts:to reach the host tmux server the launcher runs in. This is the groundwork for a tong that opens sibling sessions in tmux windows. The grant equalsdocker-socketin power: any command on the host as the socket's owner./run/swarmforge-tmux, always read-only. Read-only protects the directory only; it does not limit what the tong can do.TMUX(rewritten to the in-container path),TMUX_PANE(a default target, not a restriction),SWARMFORGE_SESSION_HANDLE, andSWARMFORGE_WORKSPACE_HOST_PATH. Plain values of these in the definition are replaced, and${secret:...}references to them are refused.$TMUXunset or malformed; a socket outside tmux's owntmux-<uid>directory; or that directory not passing an on-disk check. The check requires no symlinks, ownership by the launching user, no group/other permissions, and every ancestor root- or user-owned and not writable by others unless sticky.sharedtongs, which outlive the session.