Skip to content

fix(ssh): preserve argv boundaries for multi-arg remote commands - #1060

Merged
paulocsanz merged 1 commit into
masterfrom
pcs/ssh-quote-multi-args
Aug 8, 2026
Merged

fix(ssh): preserve argv boundaries for multi-arg remote commands#1060
paulocsanz merged 1 commit into
masterfrom
pcs/ssh-quote-multi-args

Conversation

@paulocsanz

Copy link
Copy Markdown
Collaborator

railway ssh svc sh -c 'redis-cli -a "$PW" PING && echo ok' never worked: ssh(1) concatenates every remote-command word with single spaces into one string for the remote login shell, so argv boundaries don't survive — sh -c received only redis-cli as its script, the quoted string leaked into $0/$1, and && echo ok ran as a second command.

What

Shell-quote each remote-command word (shlex::try_quote) when the caller passed more than one, so the remote shell's re-split reproduces the caller's argv exactly — docker exec/kubectl exec semantics. A single word keeps passing through raw: it IS the remote shell line (today's documented usage, e.g. railway ssh 'complex | pipeline'), and quoting it would collapse the whole line into one command word.

Applied at run_native_ssh_with_opts, the choke point — railway ssh and railway sandbox both get it; railway code passes a single prebuilt word and is unaffected (all callers audited).

Verification

  • Unit tests on the new helper, including the round-trip invariant: join-with-spaces + shlex::split must reproduce the original argv (quotes, spaces, $, ;|, empty words).
  • cargo test: 932 passed.
  • Live e2e against a Railway service:
    cargo run -- ssh -s Redis-1 sh -c 'redis-cli -a "$REDIS_PASSWORD" --no-auth-warning PING && echo chained-ok'
    → PONG
    → chained-ok
    
    (released CLI mangles this into sh -c redis-cli ...).

Same-class issue left out of scope: run_tmux_session interpolates the session name unquoted into tmux new-session -A -s {}.

ssh(1) concatenates every remote-command word with single spaces into
one string and hands it to the remote login shell, so argv boundaries
never survived the trip:

  railway ssh -s api sh -c 'redis-cli -a "$PW" PING && echo ok'

reached the remote shell as

  sh -c redis-cli -a "$PW" PING && echo ok

giving sh -c only 'redis-cli' as its script (the quoted string leaked
into $0/$1 and a stray '&& echo ok' command).

Shell-quote each word (shlex::try_quote) when the caller passed more
than one, so the remote re-split reproduces the caller's argv exactly —
docker/kubectl exec semantics. A single word keeps passing through raw:
it IS the remote shell line, today's documented usage, and quoting it
would collapse the whole line into one command word.

Applies at run_native_ssh_with_opts, so railway ssh and railway sandbox
both get it; railway code passes a single prebuilt word and is
unaffected. Round-trip unit tests included (join + shlex::split must
reproduce the original argv).
@paulocsanz paulocsanz added the release/patch Author patch release label Aug 8, 2026
@paulocsanz
paulocsanz merged commit b0ecceb into master Aug 8, 2026
9 of 10 checks passed
@paulocsanz
paulocsanz deleted the pcs/ssh-quote-multi-args branch August 8, 2026 06:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release/patch Author patch release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant