Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 37 additions & 12 deletions app/assets/bundled/bootstrap/bash_body.sh
Original file line number Diff line number Diff line change
Expand Up @@ -985,6 +985,20 @@ if [ -z "$WARP_BOOTSTRAPPED" ]; then
# determine what shell is the login shell on the remote machine. We perform a preliminary check to see if
# the remote shell is the Bourne shell to avoid asking it to parse later lines that use syntax it doesn't
# support.
#
# MotD emulation note (tracks GH-1160). The heredoc below emulates the
# MotD print that SSHD normally does at login. SSHD skips MotD when
# invoked with a command, and the bash/zsh rcfile bootstrap further
# down does not reintroduce it, so before this fix bash/zsh users
# silently lost the MotD over Warp SSH. Probe order: /etc/motd,
# then /etc/motd.d (Fedora/RHEL fragments), then /run/motd.dynamic
# (Ubuntu/Debian pre-rendered MotD), then the legacy paths.
# The motd_emitted flag tracks fall-through so an empty /etc/motd.d
# does not block later candidates. Variables set by the remote
# shell (motd_emitted, the for-loop iterator) are referenced as
# backslash-dollar (e.g. \$motd_fragment) so the local shell does
# not expand the dollar-sign before the heredoc reaches the remote
# -- same escape pattern as the zshenv decode loop below.
command ssh -o ControlMaster=yes -o ControlPath=$SSH_SOCKET_DIR/$WARP_SESSION_ID \
-t "${@:1}" \
"
Expand All @@ -1000,26 +1014,37 @@ test -n '$WARP_CLI_AGENT_PROTOCOL_VERSION' && export WARP_CLI_AGENT_PROTOCOL_VER
hook="'$(printf "{\"hook\": \"SSH\", \"value\": {\"socket_path\": \"'$SSH_SOCKET_DIR/$WARP_SESSION_ID'\", \"remote_shell\": \"%s\"}}" "${SHELL##*/}" | command -p od -An -v -tx1 | command -p tr -d " \n")'"
printf '$OSC_START$DCS_JSON_MARKER$OSC_PARAM_SEPARATOR%s$OSC_END' "'$hook'"

if test "'"${SHELL##*/}" != "bash" -a "${SHELL##*/}" != "zsh"'"; then
# Emulate the SSHD logic to print the MotD. Because the Warp SSH wrapper passes
# a command to run, SSHD does a quiet login, updating utmp and other login
# state, but not printing the MotD. For bash and zsh, this is instead handled
# by our bootstrap script.
if test ! -e "'$HOME/.hushlogin'"; then
# This uses an if-else chain instead of a for-loop to avoid expansion issues on older shells.
if test -r /etc/motd; then
command -p cat /etc/motd
# GH-1160: emulate the MotD print SSHD skips for command-passing invocations.
if test ! -e "'$HOME/.hushlogin'"; then
motd_emitted=0
if test -f /etc/motd && test -r /etc/motd; then
command -p cat /etc/motd
motd_emitted=1
fi
if test "\$motd_emitted" = 0 && test -d /etc/motd.d; then
for motd_fragment in /etc/motd.d/*; do
if test -f "\$motd_fragment" && test -r "\$motd_fragment"; then
command -p cat "\$motd_fragment"
motd_emitted=1
fi
done
fi
if test "\$motd_emitted" = 0; then
if test -r /run/motd.dynamic; then
command -p cat /run/motd.dynamic
elif test -r /run/motd; then
command -p cat /run/motd
elif test -r /run/motd.dynamic; then
command -p cat /run/motd.dynamic
elif test -r /usr/lib/motd; then
command -p cat /usr/lib/motd
elif test -r /usr/lib/motd.dynamic; then
command -p cat /usr/lib/motd.dynamic
fi
fi
# Likewise, emulate a login shell by sourcing /etc/profile
fi

if test "'"${SHELL##*/}" != "bash" -a "${SHELL##*/}" != "zsh"'"; then
# Source /etc/profile for non-bash/non-zsh shells; bash and zsh load
# their own profile/rcfile chain via the case statement below.
if test -r /etc/profile; then
. /etc/profile
fi
Expand Down
49 changes: 37 additions & 12 deletions app/assets/bundled/bootstrap/fish.sh
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,20 @@ if test "$WARP_IS_LOCAL_SHELL_SESSION" = "1"
# determine what shell is the login shell on the remote machine. We perform a preliminary check to see if
# the remote shell is the Bourne shell to avoid asking it to parse later lines that use syntax it doesn't
# support.
#
# MotD emulation note (tracks GH-1160). The heredoc below emulates the
# MotD print that SSHD normally does at login. SSHD skips MotD when
# invoked with a command, and the bash/zsh rcfile bootstrap further
# down does not reintroduce it, so before this fix bash/zsh users
# silently lost the MotD over Warp SSH. Probe order: /etc/motd,
# then /etc/motd.d (Fedora/RHEL fragments), then /run/motd.dynamic
# (Ubuntu/Debian pre-rendered MotD), then the legacy paths.
# The motd_emitted flag tracks fall-through so an empty /etc/motd.d
# does not block later candidates. Variables set by the remote
# shell (motd_emitted, the for-loop iterator) are referenced as
# backslash-dollar (e.g. \$motd_fragment) so the local shell does
# not expand the dollar-sign before the heredoc reaches the remote
# -- same escape pattern as the zshenv decode loop below.
command ssh -o ControlMaster=yes -o ControlPath=$SSH_SOCKET_DIR/$WARP_SESSION_ID \
-t $argv \
"
Expand All @@ -619,26 +633,37 @@ test -n '$WARP_CLI_AGENT_PROTOCOL_VERSION' && export WARP_CLI_AGENT_PROTOCOL_VER
hook="'$(printf "{\"hook\": \"SSH\", \"value\": {\"socket_path\": \"'$SSH_SOCKET_DIR/$WARP_SESSION_ID'\", \"remote_shell\": \"%s\"}}" "${SHELL##*/}" | command od -An -v -tx1 | command tr -d " \n")'"
printf '$DCS_START$DCS_JSON_MARKER%s$DCS_END' "'$hook'"

if test "'"${SHELL##*/}" != "bash" -a "${SHELL##*/}" != "zsh"'"; then
# Emulate the SSHD logic to print the MotD. Because the Warp SSH wrapper passes
# a command to run, SSHD does a quiet login, updating utmp and other login
# state, but not printing the MotD. For bash and zsh, this is instead handled
# by our bootstrap script.
if test ! -e "'$HOME/.hushlogin'"; then
# This uses an if-else chain instead of a for-loop to avoid expansion issues on older shells.
if test -r /etc/motd; then
cat /etc/motd
# GH-1160: emulate the MotD print SSHD skips for command-passing invocations.
if test ! -e "'$HOME/.hushlogin'"; then
motd_emitted=0
if test -f /etc/motd && test -r /etc/motd; then
cat /etc/motd
motd_emitted=1
fi
if test "\$motd_emitted" = 0 && test -d /etc/motd.d; then
for motd_fragment in /etc/motd.d/*; do
if test -f "\$motd_fragment" && test -r "\$motd_fragment"; then
cat "\$motd_fragment"
motd_emitted=1
fi
done
fi
if test "\$motd_emitted" = 0; then
if test -r /run/motd.dynamic; then
cat /run/motd.dynamic
elif test -r /run/motd; then
cat /run/motd
elif test -r /run/motd.dynamic; then
cat /run/motd.dynamic
elif test -r /usr/lib/motd; then
cat /usr/lib/motd
elif test -r /usr/lib/motd.dynamic; then
cat /usr/lib/motd.dynamic
fi
fi
# Likewise, emulate a login shell by sourcing /etc/profile
fi

if test "'"${SHELL##*/}" != "bash" -a "${SHELL##*/}" != "zsh"'"; then
# Source /etc/profile for non-bash/non-zsh shells; bash and zsh load
# their own profile/rcfile chain via the case statement below.
if test -r /etc/profile; then
. /etc/profile
fi
Expand Down
49 changes: 37 additions & 12 deletions app/assets/bundled/bootstrap/zsh_body.sh
Original file line number Diff line number Diff line change
Expand Up @@ -876,6 +876,20 @@ if [[ -z $WARP_BOOTSTRAPPED ]]; then
# determine what shell is the login shell on the remote machine. We perform a preliminary check to see if
# the remote shell is the Bourne shell to avoid asking it to parse later lines that use syntax it doesn't
# support.
#
# MotD emulation note (tracks GH-1160). The heredoc below emulates the
# MotD print that SSHD normally does at login. SSHD skips MotD when
# invoked with a command, and the bash/zsh rcfile bootstrap further
# down does not reintroduce it, so before this fix bash/zsh users
# silently lost the MotD over Warp SSH. Probe order: /etc/motd,
# then /etc/motd.d (Fedora/RHEL fragments), then /run/motd.dynamic
# (Ubuntu/Debian pre-rendered MotD), then the legacy paths.
# The motd_emitted flag tracks fall-through so an empty /etc/motd.d
# does not block later candidates. Variables set by the remote
# shell (motd_emitted, the for-loop iterator) are referenced as
# backslash-dollar (e.g. \$motd_fragment) so the local shell does
# not expand the dollar-sign before the heredoc reaches the remote
# -- same escape pattern as the zshenv decode loop below.
command ssh -o ControlMaster=yes -o ControlPath=$SSH_SOCKET_DIR/$WARP_SESSION_ID \
-t "${@:1}" \
"
Expand All @@ -890,26 +904,37 @@ test -n '$WARP_CLI_AGENT_PROTOCOL_VERSION' && export WARP_CLI_AGENT_PROTOCOL_VER
hook="'$(printf "{\"hook\": \"SSH\", \"value\": {\"socket_path\": \"'$SSH_SOCKET_DIR/$WARP_SESSION_ID'\", \"remote_shell\": \"%s\"}}" "${SHELL##*/}" | command -p od -An -v -tx1 | command -p tr -d " \n")'"
printf '$OSC_START$DCS_JSON_MARKER$OSC_PARAM_SEPARATOR%s$OSC_END' "'$hook'"

if test "'"${SHELL##*/}" != "bash" -a "${SHELL##*/}" != "zsh"'"; then
# Emulate the SSHD logic to print the MotD. Because the Warp SSH wrapper passes
# a command to run, SSHD does a quiet login, updating utmp and other login
# state, but not printing the MotD. For bash and zsh, this is instead handled
# by our bootstrap script.
if test ! -e "'$HOME/.hushlogin'"; then
# This uses an if-else chain instead of a for-loop to avoid expansion issues on older shells.
if test -r /etc/motd; then
command -p cat /etc/motd
# GH-1160: emulate the MotD print SSHD skips for command-passing invocations.
if test ! -e "'$HOME/.hushlogin'"; then
motd_emitted=0
if test -f /etc/motd && test -r /etc/motd; then
command -p cat /etc/motd
motd_emitted=1
fi
if test "\$motd_emitted" = 0 && test -d /etc/motd.d; then
for motd_fragment in /etc/motd.d/*; do
if test -f "\$motd_fragment" && test -r "\$motd_fragment"; then
command -p cat "\$motd_fragment"
motd_emitted=1
fi
done
fi
if test "\$motd_emitted" = 0; then
if test -r /run/motd.dynamic; then
command -p cat /run/motd.dynamic
elif test -r /run/motd; then
command -p cat /run/motd
elif test -r /run/motd.dynamic; then
command -p cat /run/motd.dynamic
elif test -r /usr/lib/motd; then
command -p cat /usr/lib/motd
elif test -r /usr/lib/motd.dynamic; then
command -p cat /usr/lib/motd.dynamic
fi
fi
# Likewise, emulate a login shell by sourcing /etc/profile
fi

if test "'"${SHELL##*/}" != "bash" -a "${SHELL##*/}" != "zsh"'"; then
# Source /etc/profile for non-bash/non-zsh shells; bash and zsh load
# their own profile/rcfile chain via the case statement below.
if test -r /etc/profile; then
. /etc/profile
fi
Expand Down
61 changes: 61 additions & 0 deletions app/src/terminal/bootstrap_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,64 @@ fn test_trims_powershell_specifics() {
fn decode_script(bytes: &[u8]) -> &str {
std::str::from_utf8(bytes).expect("should not fail to decode")
}

/// Regression test for GH-1160.
///
/// Until this fix, the MotD-emulation block in each shell-bootstrap body was
/// nested inside an `if test "${SHELL##*/}" != "bash" -a "${SHELL##*/}" !=
/// "zsh"` guard, with a comment claiming MotD was "instead handled by our
/// bootstrap script" for bash and zsh. It wasn't — sshd skips MotD for
/// command-passing invocations and Warp's bash/zsh rcfile bootstrap doesn't
/// reintroduce it, so bash and zsh users silently lost the MotD over Warp SSH.
///
/// This test asserts the structural invariant after the fix: in each of
/// `bash_body.sh`, `zsh_body.sh`, and `fish.sh` the MotD-print branch
/// (identified by `cat /etc/motd` / `cat /run/motd.dynamic`) appears **before**
/// the `!= "bash" -a` shell-type guard, so it runs unconditionally.
#[test]
fn test_motd_emulation_is_not_gated_on_shell_type() {
const BASH_BODY: &str = include_str!("../../assets/bundled/bootstrap/bash_body.sh");
const ZSH_BODY: &str = include_str!("../../assets/bundled/bootstrap/zsh_body.sh");
const FISH_BODY: &str = include_str!("../../assets/bundled/bootstrap/fish.sh");

for (shell, body) in [
("bash_body.sh", BASH_BODY),
("zsh_body.sh", ZSH_BODY),
("fish.sh", FISH_BODY),
] {
// Marker for the actual MotD-print probe. We deliberately match the
// *executable* `test -f /etc/motd && test -r /etc/motd` line and not
// bare `/etc/motd`, because `/etc/motd` also appears in the
// surrounding explanatory comments — a pre-fix file that kept the
// comments but removed the executable probe would still pass a
// `body.find("/etc/motd")` check, defeating the test.
let motd_marker = "test -f /etc/motd && test -r /etc/motd";
// Marker for the non-bash/non-zsh guard. The `!= "bash" -a` substring
// is stable across the three heredocs.
let guard_marker = "!= \"bash\" -a";

let motd_idx = body.find(motd_marker).unwrap_or_else(|| {
panic!(
"{shell}: executable MotD probe ({motd_marker:?}) not found. \
If the probe structure changed, update this regression test \
(and verify GH-1160 doesn't regress)."
)
});
let guard_idx = body.find(guard_marker).unwrap_or_else(|| {
panic!(
"{shell}: non-bash/non-zsh guard ({guard_marker:?}) not found. \
If the heredoc structure changed, update this regression test \
(and verify GH-1160 doesn't regress)."
)
});

assert!(
motd_idx < guard_idx,
"GH-1160: in `{shell}` the executable MotD probe (offset {motd_idx}) \
must appear BEFORE the non-bash/non-zsh guard (offset {guard_idx}) \
so MotD prints for bash and zsh too. Putting the MotD block back \
inside the guard silently regresses GH-1160 (bash/zsh users will \
no longer see /etc/motd over Warp SSH)."
);
}
}