From f06c599ea7d85f9df98e8734ab1457bd3e5977bb Mon Sep 17 00:00:00 2001 From: veg Date: Sat, 4 Jul 2026 07:51:38 +0000 Subject: [PATCH] fix: reject '.' and leading '-' in party names tmux session_check_name() silently rewrites '.' to '_', so a dotted party name desyncs from its tmux session and every -t lookup fails (verified on tmux 3.3a through 3.6b). Leading-dash names can never be addressed through the CLI parsers. --- party | 39 ++++++++++++++++++++++----------------- tests/20-helpers.bats | 15 +++++++++++++++ 2 files changed, 37 insertions(+), 17 deletions(-) diff --git a/party b/party index 07745c0..d74d07a 100644 --- a/party +++ b/party @@ -65,30 +65,32 @@ EOF # Helpers # ======= -# Validate a party name. Accepts [A-Za-z0-9._-]{1,63}. Reject empty, -# paths, shell metas, and overlong names: the name lands in a file -# path component, a tmux session name, and shell command lines. +# Validate a party name. Accepts [A-Za-z0-9_-]{1,63}, not starting with +# '-'. Reject empty, paths, shell metas, and overlong names: the name +# lands in a file path component, a tmux session name, and shell +# command lines. Dots are rejected because tmux's session_check_name() +# silently rewrites '.' (and ':') to '_', which would desync the party +# name from its tmux session and break every later `-t ` target. +# Leading '-' is rejected because every subcommand parser would eat the +# name as a flag, so such a party could never be addressed. validate_party_name() { name="$1" case "$name" in '' ) echo "party: name must not be empty" >&2; return 2 ;; esac - # Length cap: 63 keeps the full /tmp/party-${USER}-${name} comfortably - # below the 108-char sun_path limit for typical USERs. + # Length cap: 63 keeps the full socket path comfortably below the + # 108-char sun_path limit for typical USERs. if [ "${#name}" -gt 63 ]; then echo "party: name '$name' too long (max 63 chars)" >&2 return 2 fi case "$name" in - *[!A-Za-z0-9._-]* ) - echo "party: name '$name' has invalid characters" >&2 - echo " allowed: letters, digits, '.', '_', '-'" >&2 + -* ) + echo "party: name must not start with '-'" >&2 return 2 ;; - esac - # Reject ".", "..", and any leading-dot variant we don't want. - case "$name" in - .|.. ) - echo "party: name '$name' is reserved" >&2 + *[!A-Za-z0-9_-]* ) + echo "party: name '$name' has invalid characters" >&2 + echo " allowed: letters, digits, '_', '-'" >&2 return 2 ;; esac return 0 @@ -237,10 +239,13 @@ roster_read() { [ -n "$RR_PARTY_NAME" ] || return 1 # Pattern parity with cmd_host. The basename glob (party-*:*.d) is - # permissive, names like '.' (basename party-USER:..d) or 'a b' - # match the glob but validate_party_name rejects them. Run the - # canonical validator so the invariant cmd_host enforces on write - # is also checked on read. + # permissive — names like 'a b' or 'my.party' match the glob but + # validate_party_name rejects them (dots are banned: tmux rewrites + # them in session names). Run the canonical validator so the + # invariant cmd_host enforces on write is also checked on read. + # Side effect: pre-existing dotted-party dirs from older builds + # become undiscoverable, which is the pre-release migration policy + # (README §Status). validate_party_name "$RR_PARTY_NAME" 2>/dev/null || return 1 # Both the dir and its sock must be owned by the user named in the diff --git a/tests/20-helpers.bats b/tests/20-helpers.bats index 999bb78..805c7be 100644 --- a/tests/20-helpers.bats +++ b/tests/20-helpers.bats @@ -42,6 +42,21 @@ setup() { done } +@test "validate_party_name rejects dots (tmux rewrites '.' in session names)" { + # tmux session_check_name() silently maps '.' and ':' to '_', so a + # dotted party name desyncs from its tmux session and every later + # `-t ` target fails. Verified on tmux 3.3a through 3.6b. + for bad in "my.party" "." ".." "a.d"; do + run validate_party_name "$bad" + [ "$status" -ne 0 ] || { echo "should have rejected: $bad"; false; } + done +} + +@test "validate_party_name rejects leading dash (unusable through the CLI)" { + run validate_party_name "-flag" + [ "$status" -ne 0 ] +} + # time # ====