Merged
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
4 changes: 2 additions & 2 deletions aws/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,8 +17,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}
agent_env_vars() { generate_env_config "OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"; }
agent_launch_cmd() { printf 'source ~/.zshrc && aider --model openrouter/%s' "${MODEL_ID}"; }
Expand Down
4 changes: 2 additions & 2 deletions daytona/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,8 +16,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}

agent_env_vars() {
Expand Down
19 changes: 9 additions & 10 deletions daytona/lib/common.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -208,13 +208,14 @@ wait_for_cloud_init() {
log_info "Base tools installed"
}

# Daytona uses `daytona exec` for running commands in sandboxes
# SECURITY: Uses printf %q to properly escape commands to prevent injection
# Daytona uses `daytona exec` for running commands in sandboxes.
# The command string is passed directly to bash -c as a single argument.
# All callers pass trusted, hardcoded command strings (not user input).
# Do NOT use printf '%q' here — it escapes shell operators like && and ||
# into literal characters, breaking multi-part commands.
run_server() {
local cmd="${1}"
local escaped_cmd
escaped_cmd=$(printf '%q' "${cmd}")
daytona exec "${DAYTONA_SANDBOX_ID}" -- bash -c "${escaped_cmd}"
daytona exec "${DAYTONA_SANDBOX_ID}" -- bash -c "${cmd}"
}

upload_file() {
Expand DownExpand Up@@ -242,11 +243,9 @@ interactive_session() {
# Pure interactive shell via SSH
daytona ssh "${DAYTONA_SANDBOX_ID}" || session_exit=$?
else
# Run a specific command interactively via exec
# SECURITY: Properly escape command
local escaped_cmd
escaped_cmd=$(printf '%q' "${cmd}")
daytona exec "${DAYTONA_SANDBOX_ID}" -- bash -c "${escaped_cmd}" || session_exit=$?
# Run a specific command interactively via exec.
# Pass directly to bash -c — do NOT use printf '%q' (see run_server comment).
daytona exec "${DAYTONA_SANDBOX_ID}" -- bash -c "${cmd}" || session_exit=$?
fi
SERVER_NAME="${DAYTONA_SANDBOX_ID:-}" SPAWN_RECONNECT_CMD="daytona ssh ${DAYTONA_SANDBOX_ID:-}" \
_show_exec_post_session_summary
Expand Down
4 changes: 2 additions & 2 deletions digitalocean/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,8 +17,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}
agent_env_vars() { generate_env_config "OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"; }
agent_launch_cmd() { printf 'source ~/.zshrc && aider --model openrouter/%s' "${MODEL_ID}"; }
Expand Down
4 changes: 2 additions & 2 deletions fly/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,8 +16,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}

agent_env_vars() {
Expand Down
20 changes: 15 additions & 5 deletions fly/lib/common.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -362,7 +362,12 @@ run_server() {
# Ubuntu's default .bashrc returns early for non-interactive shells, so
# "source ~/.bashrc && bun ..." fails — bun's PATH line is never reached.
local full_cmd="export PATH=\"\$HOME/.local/bin:\$HOME/.bun/bin:\$PATH\" && $cmd"
# SECURITY: Properly escape command to prevent injection
# printf '%q' escapes the command so it becomes a single shell word.
# The remote shell parses "bash -c <escaped>" — the backslash escapes
# are consumed during word parsing, reconstructing the original command
# as the sole argument to bash -c.
# NOTE: Do NOT wrap $escaped_cmd in additional quotes — double-quoting
# preserves the backslashes literally, breaking operators like && and |.
local escaped_cmd
escaped_cmd=$(printf '%q' "$full_cmd")

Expand All@@ -376,12 +381,12 @@ run_server() {
elif command -v gtimeout &>/dev/null; then timeout_bin="gtimeout"
fi
if [[ -n "${timeout_bin}" ]]; then
"${timeout_bin}" "${timeout_secs}" "$fly_cmd" ssh console -a "$FLY_APP_NAME" -C "bash -c \"$escaped_cmd\"" --quiet 2>/dev/null
"${timeout_bin}" "${timeout_secs}" "$fly_cmd" ssh console -a "$FLY_APP_NAME" -C "bash -c $escaped_cmd" --quiet
return $?
fi
fi

"$fly_cmd" ssh console -a "$FLY_APP_NAME" -C "bash -c \"$escaped_cmd\"" --quiet 2>/dev/null
"$fly_cmd" ssh console -a "$FLY_APP_NAME" -C "bash -c $escaped_cmd" --quiet
}

# Upload a file to the machine via base64 encoding through exec
Expand All@@ -408,11 +413,16 @@ interactive_session() {
# Wrap in bash -c with PATH prepended (same as run_server) so shell builtins
# like "source" work — fly ssh console -C execs directly, not via a shell.
local full_cmd="export PATH=\"\$HOME/.local/bin:\$HOME/.bun/bin:\$PATH\" && $cmd"
# SECURITY: Properly escape command to prevent injection
# printf '%q' makes the command a single shell word; the remote shell
# unescapes it back into the original command for bash -c.
# Do NOT add quotes around $escaped_cmd (see run_server comment).
local escaped_cmd
escaped_cmd=$(printf '%q' "$full_cmd")
local session_exit=0
"$(_get_fly_cmd)" ssh console -a "$FLY_APP_NAME" -C "bash -c \"$escaped_cmd\"" || session_exit=$?
# --pty allocates a pseudo-terminal so interactive TUI agents (aider, claude)
# receive a proper TTY on stdin. Without it, fly ssh console -C runs the
# command without a PTY and agents see "Input is not a terminal (fd=0)".
"$(_get_fly_cmd)" ssh console -a "$FLY_APP_NAME" --pty -C "bash -c $escaped_cmd" || session_exit=$?
SERVER_NAME="${FLY_APP_NAME:-}" SPAWN_RECONNECT_CMD="fly ssh console -a ${FLY_APP_NAME:-}" \
_show_exec_post_session_summary
return "${session_exit}"
Expand Down
4 changes: 2 additions & 2 deletions gcp/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,8 +17,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}
agent_env_vars() { generate_env_config "OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"; }
agent_launch_cmd() { printf 'source ~/.zshrc && aider --model openrouter/%s' "${MODEL_ID}"; }
Expand Down
4 changes: 2 additions & 2 deletions hetzner/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,8 +17,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}
agent_env_vars() { generate_env_config "OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"; }
agent_launch_cmd() { printf 'source ~/.zshrc && aider --model openrouter/%s' "${MODEL_ID}"; }
Expand Down
4 changes: 2 additions & 2 deletions local/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,8 +16,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}

agent_env_vars() {
Expand Down
4 changes: 2 additions & 2 deletions ovh/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,8 +17,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}
agent_env_vars() { generate_env_config "OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"; }
agent_launch_cmd() { printf 'source ~/.zshrc && aider --model openrouter/%s' "${MODEL_ID}"; }
Expand Down
11 changes: 7 additions & 4 deletions shared/common.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -1627,12 +1627,12 @@ _validate_ssh_opts() {
# Default SSH options for all cloud providers
# Clouds can override this if they need provider-specific settings
if [[ -z "${SSH_OPTS:-}" ]]; then
SSH_OPTS="-o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -i ${HOME}/.ssh/id_ed25519"
SSH_OPTS="-o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -o ServerAliveInterval=15 -o ServerAliveCountMax=3 -o ConnectTimeout=10 -i ${HOME}/.ssh/id_ed25519"
else
# Validate user-provided SSH_OPTS for security
if ! _validate_ssh_opts "${SSH_OPTS}"; then
log_error "Invalid SSH_OPTS provided. Using secure defaults."
SSH_OPTS="-o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -i ${HOME}/.ssh/id_ed25519"
SSH_OPTS="-o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -o ServerAliveInterval=15 -o ServerAliveCountMax=3 -o ConnectTimeout=10 -i ${HOME}/.ssh/id_ed25519"
fi
fi

Expand DownExpand Up@@ -2166,7 +2166,7 @@ generic_ssh_wait() {
log_step "Waiting for ${description} to ${ip} (this usually takes 30-90 seconds)..."
while [[ "${attempt}" -le "${max_attempts}" ]]; do
# shellcheck disable=SC2086
if ssh ${ssh_opts} "${username}@${ip}" "${test_cmd}" >/dev/null 2>&1; then
if ssh ${ssh_opts} "${username}@${ip}" "${test_cmd}" < /dev/null >/dev/null 2>&1; then
log_info "${description} ready (took ${elapsed_time}s)"
return 0
fi
Expand DownExpand Up@@ -2215,7 +2215,10 @@ ssh_run_server() {
cmd="set -x; ${cmd}"
fi
# shellcheck disable=SC2086
ssh $SSH_OPTS "${SSH_USER:-root}@${ip}" -- "${cmd}"
# < /dev/null prevents SSH from consuming the parent script's stdin.
# Without this, sequential SSH calls can steal input meant for later
# commands (e.g., safe_read prompts), causing hangs.
ssh $SSH_OPTS "${SSH_USER:-root}@${ip}" -- "${cmd}" < /dev/null
}

# Upload a file to a remote server via SCP
Expand Down
4 changes: 2 additions & 2 deletions sprite/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,8 +16,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}

agent_env_vars() {
Expand Down
12 changes: 6 additions & 6 deletions test/fixtures/_shared_agent_assertions.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,17 +36,17 @@ assert_agent_install() {
# git clone https://github.com/gavrielc/nanoclaw.git && npm install
_assert_install_pattern "git.*clone.*nanoclaw" "installs nanoclaw via git clone" ;;
aider)
# pip install aider-chat (or pip3)
_assert_install_pattern "pip.*install.*aider" "installs aider via pip" ;;
# uv tool install --python 3.12 --upgrade aider-chat
_assert_install_pattern "uv.*tool.*install.*aider" "installs aider via uv" ;;
goose)
# curl -fsSL https://github.com/block/goose/releases/.../download_cli.sh | bash
_assert_install_pattern "goose.*download_cli" "installs goose via curl installer" ;;
codex)
# npm install -g @openai/codex
_assert_install_pattern "npm.*install.*codex" "installs codex via npm" ;;
interpreter)
# pip install open-interpreter (or pip3)
_assert_install_pattern "pip.*install.*open-interpreter" "installs interpreter via pip" ;;
# uv tool install open-interpreter --python 3.12
_assert_install_pattern "uv.*tool.*install.*open-interpreter" "installs interpreter via uv" ;;
gemini)
# npm install -g @google/gemini-cli
_assert_install_pattern "npm.*install.*gemini-cli" "installs gemini via npm" ;;
Expand All@@ -57,8 +57,8 @@ assert_agent_install() {
# npm install -g cline
_assert_install_pattern "npm.*install.*cline" "installs cline via npm" ;;
gptme)
# pip install gptme (or pip3)
_assert_install_pattern "pip.*install.*gptme" "installs gptme via pip" ;;
# uv tool install gptme
_assert_install_pattern "uv.*tool.*install.*gptme" "installs gptme via uv" ;;
opencode)
# curl to download opencode tarball (via opencode_install_cmd)
_assert_install_pattern "opencode" "installs opencode" ;;
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
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
4 changes: 2 additions & 2 deletions aws/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,8 +17,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}
agent_env_vars() { generate_env_config "OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"; }
agent_launch_cmd() { printf 'source ~/.zshrc && aider --model openrouter/%s' "${MODEL_ID}"; }
Expand Down
4 changes: 2 additions & 2 deletions daytona/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,8 +16,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}

agent_env_vars() {
Expand Down
19 changes: 9 additions & 10 deletions daytona/lib/common.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -208,13 +208,14 @@ wait_for_cloud_init() {
log_info "Base tools installed"
}

# Daytona uses `daytona exec` for running commands in sandboxes
# SECURITY: Uses printf %q to properly escape commands to prevent injection
# Daytona uses `daytona exec` for running commands in sandboxes.
# The command string is passed directly to bash -c as a single argument.
# All callers pass trusted, hardcoded command strings (not user input).
# Do NOT use printf '%q' here — it escapes shell operators like && and ||
# into literal characters, breaking multi-part commands.
run_server() {
local cmd="${1}"
local escaped_cmd
escaped_cmd=$(printf '%q' "${cmd}")
daytona exec "${DAYTONA_SANDBOX_ID}" -- bash -c "${escaped_cmd}"
daytona exec "${DAYTONA_SANDBOX_ID}" -- bash -c "${cmd}"
}

upload_file() {
Expand DownExpand Up@@ -242,11 +243,9 @@ interactive_session() {
# Pure interactive shell via SSH
daytona ssh "${DAYTONA_SANDBOX_ID}" || session_exit=$?
else
# Run a specific command interactively via exec
# SECURITY: Properly escape command
local escaped_cmd
escaped_cmd=$(printf '%q' "${cmd}")
daytona exec "${DAYTONA_SANDBOX_ID}" -- bash -c "${escaped_cmd}" || session_exit=$?
# Run a specific command interactively via exec.
# Pass directly to bash -c — do NOT use printf '%q' (see run_server comment).
daytona exec "${DAYTONA_SANDBOX_ID}" -- bash -c "${cmd}" || session_exit=$?
fi
SERVER_NAME="${DAYTONA_SANDBOX_ID:-}" SPAWN_RECONNECT_CMD="daytona ssh ${DAYTONA_SANDBOX_ID:-}" \
_show_exec_post_session_summary
Expand Down
4 changes: 2 additions & 2 deletions digitalocean/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,8 +17,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}
agent_env_vars() { generate_env_config "OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"; }
agent_launch_cmd() { printf 'source ~/.zshrc && aider --model openrouter/%s' "${MODEL_ID}"; }
Expand Down
4 changes: 2 additions & 2 deletions fly/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,8 +16,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}

agent_env_vars() {
Expand Down
20 changes: 15 additions & 5 deletions fly/lib/common.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -362,7 +362,12 @@ run_server() {
# Ubuntu's default .bashrc returns early for non-interactive shells, so
# "source ~/.bashrc && bun ..." fails — bun's PATH line is never reached.
local full_cmd="export PATH=\"\$HOME/.local/bin:\$HOME/.bun/bin:\$PATH\" && $cmd"
# SECURITY: Properly escape command to prevent injection
# printf '%q' escapes the command so it becomes a single shell word.
# The remote shell parses "bash -c <escaped>" — the backslash escapes
# are consumed during word parsing, reconstructing the original command
# as the sole argument to bash -c.
# NOTE: Do NOT wrap $escaped_cmd in additional quotes — double-quoting
# preserves the backslashes literally, breaking operators like && and |.
local escaped_cmd
escaped_cmd=$(printf '%q' "$full_cmd")

Expand All@@ -376,12 +381,12 @@ run_server() {
elif command -v gtimeout &>/dev/null; then timeout_bin="gtimeout"
fi
if [[ -n "${timeout_bin}" ]]; then
"${timeout_bin}" "${timeout_secs}" "$fly_cmd" ssh console -a "$FLY_APP_NAME" -C "bash -c \"$escaped_cmd\"" --quiet 2>/dev/null
"${timeout_bin}" "${timeout_secs}" "$fly_cmd" ssh console -a "$FLY_APP_NAME" -C "bash -c $escaped_cmd" --quiet
return $?
fi
fi

"$fly_cmd" ssh console -a "$FLY_APP_NAME" -C "bash -c \"$escaped_cmd\"" --quiet 2>/dev/null
"$fly_cmd" ssh console -a "$FLY_APP_NAME" -C "bash -c $escaped_cmd" --quiet
}

# Upload a file to the machine via base64 encoding through exec
Expand All@@ -408,11 +413,16 @@ interactive_session() {
# Wrap in bash -c with PATH prepended (same as run_server) so shell builtins
# like "source" work — fly ssh console -C execs directly, not via a shell.
local full_cmd="export PATH=\"\$HOME/.local/bin:\$HOME/.bun/bin:\$PATH\" && $cmd"
# SECURITY: Properly escape command to prevent injection
# printf '%q' makes the command a single shell word; the remote shell
# unescapes it back into the original command for bash -c.
# Do NOT add quotes around $escaped_cmd (see run_server comment).
local escaped_cmd
escaped_cmd=$(printf '%q' "$full_cmd")
local session_exit=0
"$(_get_fly_cmd)" ssh console -a "$FLY_APP_NAME" -C "bash -c \"$escaped_cmd\"" || session_exit=$?
# --pty allocates a pseudo-terminal so interactive TUI agents (aider, claude)
# receive a proper TTY on stdin. Without it, fly ssh console -C runs the
# command without a PTY and agents see "Input is not a terminal (fd=0)".
"$(_get_fly_cmd)" ssh console -a "$FLY_APP_NAME" --pty -C "bash -c $escaped_cmd" || session_exit=$?
SERVER_NAME="${FLY_APP_NAME:-}" SPAWN_RECONNECT_CMD="fly ssh console -a ${FLY_APP_NAME:-}" \
_show_exec_post_session_summary
return "${session_exit}"
Expand Down
4 changes: 2 additions & 2 deletions gcp/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,8 +17,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}
agent_env_vars() { generate_env_config "OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"; }
agent_launch_cmd() { printf 'source ~/.zshrc && aider --model openrouter/%s' "${MODEL_ID}"; }
Expand Down
4 changes: 2 additions & 2 deletions hetzner/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,8 +17,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}
agent_env_vars() { generate_env_config "OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"; }
agent_launch_cmd() { printf 'source ~/.zshrc && aider --model openrouter/%s' "${MODEL_ID}"; }
Expand Down
4 changes: 2 additions & 2 deletions local/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,8 +16,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}

agent_env_vars() {
Expand Down
4 changes: 2 additions & 2 deletions ovh/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,8 +17,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}
agent_env_vars() { generate_env_config "OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"; }
agent_launch_cmd() { printf 'source ~/.zshrc && aider --model openrouter/%s' "${MODEL_ID}"; }
Expand Down
11 changes: 7 additions & 4 deletions shared/common.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -1627,12 +1627,12 @@ _validate_ssh_opts() {
# Default SSH options for all cloud providers
# Clouds can override this if they need provider-specific settings
if [[ -z "${SSH_OPTS:-}" ]]; then
SSH_OPTS="-o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -i ${HOME}/.ssh/id_ed25519"
SSH_OPTS="-o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -o ServerAliveInterval=15 -o ServerAliveCountMax=3 -o ConnectTimeout=10 -i ${HOME}/.ssh/id_ed25519"
else
# Validate user-provided SSH_OPTS for security
if ! _validate_ssh_opts "${SSH_OPTS}"; then
log_error "Invalid SSH_OPTS provided. Using secure defaults."
SSH_OPTS="-o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -i ${HOME}/.ssh/id_ed25519"
SSH_OPTS="-o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -o ServerAliveInterval=15 -o ServerAliveCountMax=3 -o ConnectTimeout=10 -i ${HOME}/.ssh/id_ed25519"
fi
fi

Expand DownExpand Up@@ -2166,7 +2166,7 @@ generic_ssh_wait() {
log_step "Waiting for ${description} to ${ip} (this usually takes 30-90 seconds)..."
while [[ "${attempt}" -le "${max_attempts}" ]]; do
# shellcheck disable=SC2086
if ssh ${ssh_opts} "${username}@${ip}" "${test_cmd}" >/dev/null 2>&1; then
if ssh ${ssh_opts} "${username}@${ip}" "${test_cmd}" < /dev/null >/dev/null 2>&1; then
log_info "${description} ready (took ${elapsed_time}s)"
return 0
fi
Expand DownExpand Up@@ -2215,7 +2215,10 @@ ssh_run_server() {
cmd="set -x; ${cmd}"
fi
# shellcheck disable=SC2086
ssh $SSH_OPTS "${SSH_USER:-root}@${ip}" -- "${cmd}"
# < /dev/null prevents SSH from consuming the parent script's stdin.
# Without this, sequential SSH calls can steal input meant for later
# commands (e.g., safe_read prompts), causing hangs.
ssh $SSH_OPTS "${SSH_USER:-root}@${ip}" -- "${cmd}" < /dev/null
}

# Upload a file to a remote server via SCP
Expand Down
4 changes: 2 additions & 2 deletions sprite/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,8 +16,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}

agent_env_vars() {
Expand Down
12 changes: 6 additions & 6 deletions test/fixtures/_shared_agent_assertions.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,17 +36,17 @@ assert_agent_install() {
# git clone https://github.com/gavrielc/nanoclaw.git && npm install
_assert_install_pattern "git.*clone.*nanoclaw" "installs nanoclaw via git clone" ;;
aider)
# pip install aider-chat (or pip3)
_assert_install_pattern "pip.*install.*aider" "installs aider via pip" ;;
# uv tool install --python 3.12 --upgrade aider-chat
_assert_install_pattern "uv.*tool.*install.*aider" "installs aider via uv" ;;
goose)
# curl -fsSL https://github.com/block/goose/releases/.../download_cli.sh | bash
_assert_install_pattern "goose.*download_cli" "installs goose via curl installer" ;;
codex)
# npm install -g @openai/codex
_assert_install_pattern "npm.*install.*codex" "installs codex via npm" ;;
interpreter)
# pip install open-interpreter (or pip3)
_assert_install_pattern "pip.*install.*open-interpreter" "installs interpreter via pip" ;;
# uv tool install open-interpreter --python 3.12
_assert_install_pattern "uv.*tool.*install.*open-interpreter" "installs interpreter via uv" ;;
gemini)
# npm install -g @google/gemini-cli
_assert_install_pattern "npm.*install.*gemini-cli" "installs gemini via npm" ;;
Expand All@@ -57,8 +57,8 @@ assert_agent_install() {
# npm install -g cline
_assert_install_pattern "npm.*install.*cline" "installs cline via npm" ;;
gptme)
# pip install gptme (or pip3)
_assert_install_pattern "pip.*install.*gptme" "installs gptme via pip" ;;
# uv tool install gptme
_assert_install_pattern "uv.*tool.*install.*gptme" "installs gptme via uv" ;;
opencode)
# curl to download opencode tarball (via opencode_install_cmd)
_assert_install_pattern "opencode" "installs opencode" ;;
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
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
4 changes: 2 additions & 2 deletions aws/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,8 +17,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}
agent_env_vars() { generate_env_config "OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"; }
agent_launch_cmd() { printf 'source ~/.zshrc && aider --model openrouter/%s' "${MODEL_ID}"; }
Expand Down
4 changes: 2 additions & 2 deletions daytona/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,8 +16,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}

agent_env_vars() {
Expand Down
19 changes: 9 additions & 10 deletions daytona/lib/common.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -208,13 +208,14 @@ wait_for_cloud_init() {
log_info "Base tools installed"
}

# Daytona uses `daytona exec` for running commands in sandboxes
# SECURITY: Uses printf %q to properly escape commands to prevent injection
# Daytona uses `daytona exec` for running commands in sandboxes.
# The command string is passed directly to bash -c as a single argument.
# All callers pass trusted, hardcoded command strings (not user input).
# Do NOT use printf '%q' here — it escapes shell operators like && and ||
# into literal characters, breaking multi-part commands.
run_server() {
local cmd="${1}"
local escaped_cmd
escaped_cmd=$(printf '%q' "${cmd}")
daytona exec "${DAYTONA_SANDBOX_ID}" -- bash -c "${escaped_cmd}"
daytona exec "${DAYTONA_SANDBOX_ID}" -- bash -c "${cmd}"
}

upload_file() {
Expand DownExpand Up@@ -242,11 +243,9 @@ interactive_session() {
# Pure interactive shell via SSH
daytona ssh "${DAYTONA_SANDBOX_ID}" || session_exit=$?
else
# Run a specific command interactively via exec
# SECURITY: Properly escape command
local escaped_cmd
escaped_cmd=$(printf '%q' "${cmd}")
daytona exec "${DAYTONA_SANDBOX_ID}" -- bash -c "${escaped_cmd}" || session_exit=$?
# Run a specific command interactively via exec.
# Pass directly to bash -c — do NOT use printf '%q' (see run_server comment).
daytona exec "${DAYTONA_SANDBOX_ID}" -- bash -c "${cmd}" || session_exit=$?
fi
SERVER_NAME="${DAYTONA_SANDBOX_ID:-}" SPAWN_RECONNECT_CMD="daytona ssh ${DAYTONA_SANDBOX_ID:-}" \
_show_exec_post_session_summary
Expand Down
4 changes: 2 additions & 2 deletions digitalocean/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,8 +17,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}
agent_env_vars() { generate_env_config "OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"; }
agent_launch_cmd() { printf 'source ~/.zshrc && aider --model openrouter/%s' "${MODEL_ID}"; }
Expand Down
4 changes: 2 additions & 2 deletions fly/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,8 +16,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}

agent_env_vars() {
Expand Down
20 changes: 15 additions & 5 deletions fly/lib/common.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -362,7 +362,12 @@ run_server() {
# Ubuntu's default .bashrc returns early for non-interactive shells, so
# "source ~/.bashrc && bun ..." fails — bun's PATH line is never reached.
local full_cmd="export PATH=\"\$HOME/.local/bin:\$HOME/.bun/bin:\$PATH\" && $cmd"
# SECURITY: Properly escape command to prevent injection
# printf '%q' escapes the command so it becomes a single shell word.
# The remote shell parses "bash -c <escaped>" — the backslash escapes
# are consumed during word parsing, reconstructing the original command
# as the sole argument to bash -c.
# NOTE: Do NOT wrap $escaped_cmd in additional quotes — double-quoting
# preserves the backslashes literally, breaking operators like && and |.
local escaped_cmd
escaped_cmd=$(printf '%q' "$full_cmd")

Expand All@@ -376,12 +381,12 @@ run_server() {
elif command -v gtimeout &>/dev/null; then timeout_bin="gtimeout"
fi
if [[ -n "${timeout_bin}" ]]; then
"${timeout_bin}" "${timeout_secs}" "$fly_cmd" ssh console -a "$FLY_APP_NAME" -C "bash -c \"$escaped_cmd\"" --quiet 2>/dev/null
"${timeout_bin}" "${timeout_secs}" "$fly_cmd" ssh console -a "$FLY_APP_NAME" -C "bash -c $escaped_cmd" --quiet
return $?
fi
fi

"$fly_cmd" ssh console -a "$FLY_APP_NAME" -C "bash -c \"$escaped_cmd\"" --quiet 2>/dev/null
"$fly_cmd" ssh console -a "$FLY_APP_NAME" -C "bash -c $escaped_cmd" --quiet
}

# Upload a file to the machine via base64 encoding through exec
Expand All@@ -408,11 +413,16 @@ interactive_session() {
# Wrap in bash -c with PATH prepended (same as run_server) so shell builtins
# like "source" work — fly ssh console -C execs directly, not via a shell.
local full_cmd="export PATH=\"\$HOME/.local/bin:\$HOME/.bun/bin:\$PATH\" && $cmd"
# SECURITY: Properly escape command to prevent injection
# printf '%q' makes the command a single shell word; the remote shell
# unescapes it back into the original command for bash -c.
# Do NOT add quotes around $escaped_cmd (see run_server comment).
local escaped_cmd
escaped_cmd=$(printf '%q' "$full_cmd")
local session_exit=0
"$(_get_fly_cmd)" ssh console -a "$FLY_APP_NAME" -C "bash -c \"$escaped_cmd\"" || session_exit=$?
# --pty allocates a pseudo-terminal so interactive TUI agents (aider, claude)
# receive a proper TTY on stdin. Without it, fly ssh console -C runs the
# command without a PTY and agents see "Input is not a terminal (fd=0)".
"$(_get_fly_cmd)" ssh console -a "$FLY_APP_NAME" --pty -C "bash -c $escaped_cmd" || session_exit=$?
SERVER_NAME="${FLY_APP_NAME:-}" SPAWN_RECONNECT_CMD="fly ssh console -a ${FLY_APP_NAME:-}" \
_show_exec_post_session_summary
return "${session_exit}"
Expand Down
4 changes: 2 additions & 2 deletions gcp/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,8 +17,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}
agent_env_vars() { generate_env_config "OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"; }
agent_launch_cmd() { printf 'source ~/.zshrc && aider --model openrouter/%s' "${MODEL_ID}"; }
Expand Down
4 changes: 2 additions & 2 deletions hetzner/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,8 +17,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}
agent_env_vars() { generate_env_config "OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"; }
agent_launch_cmd() { printf 'source ~/.zshrc && aider --model openrouter/%s' "${MODEL_ID}"; }
Expand Down
4 changes: 2 additions & 2 deletions local/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,8 +16,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}

agent_env_vars() {
Expand Down
4 changes: 2 additions & 2 deletions ovh/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,8 +17,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}
agent_env_vars() { generate_env_config "OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"; }
agent_launch_cmd() { printf 'source ~/.zshrc && aider --model openrouter/%s' "${MODEL_ID}"; }
Expand Down
11 changes: 7 additions & 4 deletions shared/common.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -1627,12 +1627,12 @@ _validate_ssh_opts() {
# Default SSH options for all cloud providers
# Clouds can override this if they need provider-specific settings
if [[ -z "${SSH_OPTS:-}" ]]; then
SSH_OPTS="-o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -i ${HOME}/.ssh/id_ed25519"
SSH_OPTS="-o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -o ServerAliveInterval=15 -o ServerAliveCountMax=3 -o ConnectTimeout=10 -i ${HOME}/.ssh/id_ed25519"
else
# Validate user-provided SSH_OPTS for security
if ! _validate_ssh_opts "${SSH_OPTS}"; then
log_error "Invalid SSH_OPTS provided. Using secure defaults."
SSH_OPTS="-o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -i ${HOME}/.ssh/id_ed25519"
SSH_OPTS="-o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -o ServerAliveInterval=15 -o ServerAliveCountMax=3 -o ConnectTimeout=10 -i ${HOME}/.ssh/id_ed25519"
fi
fi

Expand DownExpand Up@@ -2166,7 +2166,7 @@ generic_ssh_wait() {
log_step "Waiting for ${description} to ${ip} (this usually takes 30-90 seconds)..."
while [[ "${attempt}" -le "${max_attempts}" ]]; do
# shellcheck disable=SC2086
if ssh ${ssh_opts} "${username}@${ip}" "${test_cmd}" >/dev/null 2>&1; then
if ssh ${ssh_opts} "${username}@${ip}" "${test_cmd}" < /dev/null >/dev/null 2>&1; then
log_info "${description} ready (took ${elapsed_time}s)"
return 0
fi
Expand DownExpand Up@@ -2215,7 +2215,10 @@ ssh_run_server() {
cmd="set -x; ${cmd}"
fi
# shellcheck disable=SC2086
ssh $SSH_OPTS "${SSH_USER:-root}@${ip}" -- "${cmd}"
# < /dev/null prevents SSH from consuming the parent script's stdin.
# Without this, sequential SSH calls can steal input meant for later
# commands (e.g., safe_read prompts), causing hangs.
ssh $SSH_OPTS "${SSH_USER:-root}@${ip}" -- "${cmd}" < /dev/null
}

# Upload a file to a remote server via SCP
Expand Down
4 changes: 2 additions & 2 deletions sprite/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,8 +16,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}

agent_env_vars() {
Expand Down
12 changes: 6 additions & 6 deletions test/fixtures/_shared_agent_assertions.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,17 +36,17 @@ assert_agent_install() {
# git clone https://github.com/gavrielc/nanoclaw.git && npm install
_assert_install_pattern "git.*clone.*nanoclaw" "installs nanoclaw via git clone" ;;
aider)
# pip install aider-chat (or pip3)
_assert_install_pattern "pip.*install.*aider" "installs aider via pip" ;;
# uv tool install --python 3.12 --upgrade aider-chat
_assert_install_pattern "uv.*tool.*install.*aider" "installs aider via uv" ;;
goose)
# curl -fsSL https://github.com/block/goose/releases/.../download_cli.sh | bash
_assert_install_pattern "goose.*download_cli" "installs goose via curl installer" ;;
codex)
# npm install -g @openai/codex
_assert_install_pattern "npm.*install.*codex" "installs codex via npm" ;;
interpreter)
# pip install open-interpreter (or pip3)
_assert_install_pattern "pip.*install.*open-interpreter" "installs interpreter via pip" ;;
# uv tool install open-interpreter --python 3.12
_assert_install_pattern "uv.*tool.*install.*open-interpreter" "installs interpreter via uv" ;;
gemini)
# npm install -g @google/gemini-cli
_assert_install_pattern "npm.*install.*gemini-cli" "installs gemini via npm" ;;
Expand All@@ -57,8 +57,8 @@ assert_agent_install() {
# npm install -g cline
_assert_install_pattern "npm.*install.*cline" "installs cline via npm" ;;
gptme)
# pip install gptme (or pip3)
_assert_install_pattern "pip.*install.*gptme" "installs gptme via pip" ;;
# uv tool install gptme
_assert_install_pattern "uv.*tool.*install.*gptme" "installs gptme via uv" ;;
opencode)
# curl to download opencode tarball (via opencode_install_cmd)
_assert_install_pattern "opencode" "installs opencode" ;;
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
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
4 changes: 2 additions & 2 deletions aws/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,8 +17,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}
agent_env_vars() { generate_env_config "OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"; }
agent_launch_cmd() { printf 'source ~/.zshrc && aider --model openrouter/%s' "${MODEL_ID}"; }
Expand Down
4 changes: 2 additions & 2 deletions daytona/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,8 +16,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}

agent_env_vars() {
Expand Down
19 changes: 9 additions & 10 deletions daytona/lib/common.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -208,13 +208,14 @@ wait_for_cloud_init() {
log_info "Base tools installed"
}

# Daytona uses `daytona exec` for running commands in sandboxes
# SECURITY: Uses printf %q to properly escape commands to prevent injection
# Daytona uses `daytona exec` for running commands in sandboxes.
# The command string is passed directly to bash -c as a single argument.
# All callers pass trusted, hardcoded command strings (not user input).
# Do NOT use printf '%q' here — it escapes shell operators like && and ||
# into literal characters, breaking multi-part commands.
run_server() {
local cmd="${1}"
local escaped_cmd
escaped_cmd=$(printf '%q' "${cmd}")
daytona exec "${DAYTONA_SANDBOX_ID}" -- bash -c "${escaped_cmd}"
daytona exec "${DAYTONA_SANDBOX_ID}" -- bash -c "${cmd}"
}

upload_file() {
Expand DownExpand Up@@ -242,11 +243,9 @@ interactive_session() {
# Pure interactive shell via SSH
daytona ssh "${DAYTONA_SANDBOX_ID}" || session_exit=$?
else
# Run a specific command interactively via exec
# SECURITY: Properly escape command
local escaped_cmd
escaped_cmd=$(printf '%q' "${cmd}")
daytona exec "${DAYTONA_SANDBOX_ID}" -- bash -c "${escaped_cmd}" || session_exit=$?
# Run a specific command interactively via exec.
# Pass directly to bash -c — do NOT use printf '%q' (see run_server comment).
daytona exec "${DAYTONA_SANDBOX_ID}" -- bash -c "${cmd}" || session_exit=$?
fi
SERVER_NAME="${DAYTONA_SANDBOX_ID:-}" SPAWN_RECONNECT_CMD="daytona ssh ${DAYTONA_SANDBOX_ID:-}" \
_show_exec_post_session_summary
Expand Down
4 changes: 2 additions & 2 deletions digitalocean/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,8 +17,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}
agent_env_vars() { generate_env_config "OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"; }
agent_launch_cmd() { printf 'source ~/.zshrc && aider --model openrouter/%s' "${MODEL_ID}"; }
Expand Down
4 changes: 2 additions & 2 deletions fly/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,8 +16,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}

agent_env_vars() {
Expand Down
20 changes: 15 additions & 5 deletions fly/lib/common.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -362,7 +362,12 @@ run_server() {
# Ubuntu's default .bashrc returns early for non-interactive shells, so
# "source ~/.bashrc && bun ..." fails — bun's PATH line is never reached.
local full_cmd="export PATH=\"\$HOME/.local/bin:\$HOME/.bun/bin:\$PATH\" && $cmd"
# SECURITY: Properly escape command to prevent injection
# printf '%q' escapes the command so it becomes a single shell word.
# The remote shell parses "bash -c <escaped>" — the backslash escapes
# are consumed during word parsing, reconstructing the original command
# as the sole argument to bash -c.
# NOTE: Do NOT wrap $escaped_cmd in additional quotes — double-quoting
# preserves the backslashes literally, breaking operators like && and |.
local escaped_cmd
escaped_cmd=$(printf '%q' "$full_cmd")

Expand All@@ -376,12 +381,12 @@ run_server() {
elif command -v gtimeout &>/dev/null; then timeout_bin="gtimeout"
fi
if [[ -n "${timeout_bin}" ]]; then
"${timeout_bin}" "${timeout_secs}" "$fly_cmd" ssh console -a "$FLY_APP_NAME" -C "bash -c \"$escaped_cmd\"" --quiet 2>/dev/null
"${timeout_bin}" "${timeout_secs}" "$fly_cmd" ssh console -a "$FLY_APP_NAME" -C "bash -c $escaped_cmd" --quiet
return $?
fi
fi

"$fly_cmd" ssh console -a "$FLY_APP_NAME" -C "bash -c \"$escaped_cmd\"" --quiet 2>/dev/null
"$fly_cmd" ssh console -a "$FLY_APP_NAME" -C "bash -c $escaped_cmd" --quiet
}

# Upload a file to the machine via base64 encoding through exec
Expand All@@ -408,11 +413,16 @@ interactive_session() {
# Wrap in bash -c with PATH prepended (same as run_server) so shell builtins
# like "source" work — fly ssh console -C execs directly, not via a shell.
local full_cmd="export PATH=\"\$HOME/.local/bin:\$HOME/.bun/bin:\$PATH\" && $cmd"
# SECURITY: Properly escape command to prevent injection
# printf '%q' makes the command a single shell word; the remote shell
# unescapes it back into the original command for bash -c.
# Do NOT add quotes around $escaped_cmd (see run_server comment).
local escaped_cmd
escaped_cmd=$(printf '%q' "$full_cmd")
local session_exit=0
"$(_get_fly_cmd)" ssh console -a "$FLY_APP_NAME" -C "bash -c \"$escaped_cmd\"" || session_exit=$?
# --pty allocates a pseudo-terminal so interactive TUI agents (aider, claude)
# receive a proper TTY on stdin. Without it, fly ssh console -C runs the
# command without a PTY and agents see "Input is not a terminal (fd=0)".
"$(_get_fly_cmd)" ssh console -a "$FLY_APP_NAME" --pty -C "bash -c $escaped_cmd" || session_exit=$?
SERVER_NAME="${FLY_APP_NAME:-}" SPAWN_RECONNECT_CMD="fly ssh console -a ${FLY_APP_NAME:-}" \
_show_exec_post_session_summary
return "${session_exit}"
Expand Down
4 changes: 2 additions & 2 deletions gcp/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,8 +17,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}
agent_env_vars() { generate_env_config "OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"; }
agent_launch_cmd() { printf 'source ~/.zshrc && aider --model openrouter/%s' "${MODEL_ID}"; }
Expand Down
4 changes: 2 additions & 2 deletions hetzner/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,8 +17,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}
agent_env_vars() { generate_env_config "OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"; }
agent_launch_cmd() { printf 'source ~/.zshrc && aider --model openrouter/%s' "${MODEL_ID}"; }
Expand Down
4 changes: 2 additions & 2 deletions local/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,8 +16,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}

agent_env_vars() {
Expand Down
4 changes: 2 additions & 2 deletions ovh/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,8 +17,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}
agent_env_vars() { generate_env_config "OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"; }
agent_launch_cmd() { printf 'source ~/.zshrc && aider --model openrouter/%s' "${MODEL_ID}"; }
Expand Down
11 changes: 7 additions & 4 deletions shared/common.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -1627,12 +1627,12 @@ _validate_ssh_opts() {
# Default SSH options for all cloud providers
# Clouds can override this if they need provider-specific settings
if [[ -z "${SSH_OPTS:-}" ]]; then
SSH_OPTS="-o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -i ${HOME}/.ssh/id_ed25519"
SSH_OPTS="-o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -o ServerAliveInterval=15 -o ServerAliveCountMax=3 -o ConnectTimeout=10 -i ${HOME}/.ssh/id_ed25519"
else
# Validate user-provided SSH_OPTS for security
if ! _validate_ssh_opts "${SSH_OPTS}"; then
log_error "Invalid SSH_OPTS provided. Using secure defaults."
SSH_OPTS="-o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -i ${HOME}/.ssh/id_ed25519"
SSH_OPTS="-o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -o ServerAliveInterval=15 -o ServerAliveCountMax=3 -o ConnectTimeout=10 -i ${HOME}/.ssh/id_ed25519"
fi
fi

Expand DownExpand Up@@ -2166,7 +2166,7 @@ generic_ssh_wait() {
log_step "Waiting for ${description} to ${ip} (this usually takes 30-90 seconds)..."
while [[ "${attempt}" -le "${max_attempts}" ]]; do
# shellcheck disable=SC2086
if ssh ${ssh_opts} "${username}@${ip}" "${test_cmd}" >/dev/null 2>&1; then
if ssh ${ssh_opts} "${username}@${ip}" "${test_cmd}" < /dev/null >/dev/null 2>&1; then
log_info "${description} ready (took ${elapsed_time}s)"
return 0
fi
Expand DownExpand Up@@ -2215,7 +2215,10 @@ ssh_run_server() {
cmd="set -x; ${cmd}"
fi
# shellcheck disable=SC2086
ssh $SSH_OPTS "${SSH_USER:-root}@${ip}" -- "${cmd}"
# < /dev/null prevents SSH from consuming the parent script's stdin.
# Without this, sequential SSH calls can steal input meant for later
# commands (e.g., safe_read prompts), causing hangs.
ssh $SSH_OPTS "${SSH_USER:-root}@${ip}" -- "${cmd}" < /dev/null
}

# Upload a file to a remote server via SCP
Expand Down
4 changes: 2 additions & 2 deletions sprite/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,8 +16,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}

agent_env_vars() {
Expand Down
12 changes: 6 additions & 6 deletions test/fixtures/_shared_agent_assertions.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,17 +36,17 @@ assert_agent_install() {
# git clone https://github.com/gavrielc/nanoclaw.git && npm install
_assert_install_pattern "git.*clone.*nanoclaw" "installs nanoclaw via git clone" ;;
aider)
# pip install aider-chat (or pip3)
_assert_install_pattern "pip.*install.*aider" "installs aider via pip" ;;
# uv tool install --python 3.12 --upgrade aider-chat
_assert_install_pattern "uv.*tool.*install.*aider" "installs aider via uv" ;;
goose)
# curl -fsSL https://github.com/block/goose/releases/.../download_cli.sh | bash
_assert_install_pattern "goose.*download_cli" "installs goose via curl installer" ;;
codex)
# npm install -g @openai/codex
_assert_install_pattern "npm.*install.*codex" "installs codex via npm" ;;
interpreter)
# pip install open-interpreter (or pip3)
_assert_install_pattern "pip.*install.*open-interpreter" "installs interpreter via pip" ;;
# uv tool install open-interpreter --python 3.12
_assert_install_pattern "uv.*tool.*install.*open-interpreter" "installs interpreter via uv" ;;
gemini)
# npm install -g @google/gemini-cli
_assert_install_pattern "npm.*install.*gemini-cli" "installs gemini via npm" ;;
Expand All@@ -57,8 +57,8 @@ assert_agent_install() {
# npm install -g cline
_assert_install_pattern "npm.*install.*cline" "installs cline via npm" ;;
gptme)
# pip install gptme (or pip3)
_assert_install_pattern "pip.*install.*gptme" "installs gptme via pip" ;;
# uv tool install gptme
_assert_install_pattern "uv.*tool.*install.*gptme" "installs gptme via uv" ;;
opencode)
# curl to download opencode tarball (via opencode_install_cmd)
_assert_install_pattern "opencode" "installs opencode" ;;
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
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
4 changes: 2 additions & 2 deletions aws/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,8 +17,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}
agent_env_vars() { generate_env_config "OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"; }
agent_launch_cmd() { printf 'source ~/.zshrc && aider --model openrouter/%s' "${MODEL_ID}"; }
Expand Down
4 changes: 2 additions & 2 deletions daytona/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,8 +16,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}

agent_env_vars() {
Expand Down
19 changes: 9 additions & 10 deletions daytona/lib/common.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -208,13 +208,14 @@ wait_for_cloud_init() {
log_info "Base tools installed"
}

# Daytona uses `daytona exec` for running commands in sandboxes
# SECURITY: Uses printf %q to properly escape commands to prevent injection
# Daytona uses `daytona exec` for running commands in sandboxes.
# The command string is passed directly to bash -c as a single argument.
# All callers pass trusted, hardcoded command strings (not user input).
# Do NOT use printf '%q' here — it escapes shell operators like && and ||
# into literal characters, breaking multi-part commands.
run_server() {
local cmd="${1}"
local escaped_cmd
escaped_cmd=$(printf '%q' "${cmd}")
daytona exec "${DAYTONA_SANDBOX_ID}" -- bash -c "${escaped_cmd}"
daytona exec "${DAYTONA_SANDBOX_ID}" -- bash -c "${cmd}"
}

upload_file() {
Expand DownExpand Up@@ -242,11 +243,9 @@ interactive_session() {
# Pure interactive shell via SSH
daytona ssh "${DAYTONA_SANDBOX_ID}" || session_exit=$?
else
# Run a specific command interactively via exec
# SECURITY: Properly escape command
local escaped_cmd
escaped_cmd=$(printf '%q' "${cmd}")
daytona exec "${DAYTONA_SANDBOX_ID}" -- bash -c "${escaped_cmd}" || session_exit=$?
# Run a specific command interactively via exec.
# Pass directly to bash -c — do NOT use printf '%q' (see run_server comment).
daytona exec "${DAYTONA_SANDBOX_ID}" -- bash -c "${cmd}" || session_exit=$?
fi
SERVER_NAME="${DAYTONA_SANDBOX_ID:-}" SPAWN_RECONNECT_CMD="daytona ssh ${DAYTONA_SANDBOX_ID:-}" \
_show_exec_post_session_summary
Expand Down
4 changes: 2 additions & 2 deletions digitalocean/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,8 +17,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}
agent_env_vars() { generate_env_config "OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"; }
agent_launch_cmd() { printf 'source ~/.zshrc && aider --model openrouter/%s' "${MODEL_ID}"; }
Expand Down
4 changes: 2 additions & 2 deletions fly/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,8 +16,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}

agent_env_vars() {
Expand Down
20 changes: 15 additions & 5 deletions fly/lib/common.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -362,7 +362,12 @@ run_server() {
# Ubuntu's default .bashrc returns early for non-interactive shells, so
# "source ~/.bashrc && bun ..." fails — bun's PATH line is never reached.
local full_cmd="export PATH=\"\$HOME/.local/bin:\$HOME/.bun/bin:\$PATH\" && $cmd"
# SECURITY: Properly escape command to prevent injection
# printf '%q' escapes the command so it becomes a single shell word.
# The remote shell parses "bash -c <escaped>" — the backslash escapes
# are consumed during word parsing, reconstructing the original command
# as the sole argument to bash -c.
# NOTE: Do NOT wrap $escaped_cmd in additional quotes — double-quoting
# preserves the backslashes literally, breaking operators like && and |.
local escaped_cmd
escaped_cmd=$(printf '%q' "$full_cmd")

Expand All@@ -376,12 +381,12 @@ run_server() {
elif command -v gtimeout &>/dev/null; then timeout_bin="gtimeout"
fi
if [[ -n "${timeout_bin}" ]]; then
"${timeout_bin}" "${timeout_secs}" "$fly_cmd" ssh console -a "$FLY_APP_NAME" -C "bash -c \"$escaped_cmd\"" --quiet 2>/dev/null
"${timeout_bin}" "${timeout_secs}" "$fly_cmd" ssh console -a "$FLY_APP_NAME" -C "bash -c $escaped_cmd" --quiet
return $?
fi
fi

"$fly_cmd" ssh console -a "$FLY_APP_NAME" -C "bash -c \"$escaped_cmd\"" --quiet 2>/dev/null
"$fly_cmd" ssh console -a "$FLY_APP_NAME" -C "bash -c $escaped_cmd" --quiet
}

# Upload a file to the machine via base64 encoding through exec
Expand All@@ -408,11 +413,16 @@ interactive_session() {
# Wrap in bash -c with PATH prepended (same as run_server) so shell builtins
# like "source" work — fly ssh console -C execs directly, not via a shell.
local full_cmd="export PATH=\"\$HOME/.local/bin:\$HOME/.bun/bin:\$PATH\" && $cmd"
# SECURITY: Properly escape command to prevent injection
# printf '%q' makes the command a single shell word; the remote shell
# unescapes it back into the original command for bash -c.
# Do NOT add quotes around $escaped_cmd (see run_server comment).
local escaped_cmd
escaped_cmd=$(printf '%q' "$full_cmd")
local session_exit=0
"$(_get_fly_cmd)" ssh console -a "$FLY_APP_NAME" -C "bash -c \"$escaped_cmd\"" || session_exit=$?
# --pty allocates a pseudo-terminal so interactive TUI agents (aider, claude)
# receive a proper TTY on stdin. Without it, fly ssh console -C runs the
# command without a PTY and agents see "Input is not a terminal (fd=0)".
"$(_get_fly_cmd)" ssh console -a "$FLY_APP_NAME" --pty -C "bash -c $escaped_cmd" || session_exit=$?
SERVER_NAME="${FLY_APP_NAME:-}" SPAWN_RECONNECT_CMD="fly ssh console -a ${FLY_APP_NAME:-}" \
_show_exec_post_session_summary
return "${session_exit}"
Expand Down
4 changes: 2 additions & 2 deletions gcp/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,8 +17,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}
agent_env_vars() { generate_env_config "OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"; }
agent_launch_cmd() { printf 'source ~/.zshrc && aider --model openrouter/%s' "${MODEL_ID}"; }
Expand Down
4 changes: 2 additions & 2 deletions hetzner/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,8 +17,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}
agent_env_vars() { generate_env_config "OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"; }
agent_launch_cmd() { printf 'source ~/.zshrc && aider --model openrouter/%s' "${MODEL_ID}"; }
Expand Down
4 changes: 2 additions & 2 deletions local/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,8 +16,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}

agent_env_vars() {
Expand Down
4 changes: 2 additions & 2 deletions ovh/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,8 +17,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}
agent_env_vars() { generate_env_config "OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"; }
agent_launch_cmd() { printf 'source ~/.zshrc && aider --model openrouter/%s' "${MODEL_ID}"; }
Expand Down
11 changes: 7 additions & 4 deletions shared/common.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -1627,12 +1627,12 @@ _validate_ssh_opts() {
# Default SSH options for all cloud providers
# Clouds can override this if they need provider-specific settings
if [[ -z "${SSH_OPTS:-}" ]]; then
SSH_OPTS="-o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -i ${HOME}/.ssh/id_ed25519"
SSH_OPTS="-o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -o ServerAliveInterval=15 -o ServerAliveCountMax=3 -o ConnectTimeout=10 -i ${HOME}/.ssh/id_ed25519"
else
# Validate user-provided SSH_OPTS for security
if ! _validate_ssh_opts "${SSH_OPTS}"; then
log_error "Invalid SSH_OPTS provided. Using secure defaults."
SSH_OPTS="-o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -i ${HOME}/.ssh/id_ed25519"
SSH_OPTS="-o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -o ServerAliveInterval=15 -o ServerAliveCountMax=3 -o ConnectTimeout=10 -i ${HOME}/.ssh/id_ed25519"
fi
fi

Expand DownExpand Up@@ -2166,7 +2166,7 @@ generic_ssh_wait() {
log_step "Waiting for ${description} to ${ip} (this usually takes 30-90 seconds)..."
while [[ "${attempt}" -le "${max_attempts}" ]]; do
# shellcheck disable=SC2086
if ssh ${ssh_opts} "${username}@${ip}" "${test_cmd}" >/dev/null 2>&1; then
if ssh ${ssh_opts} "${username}@${ip}" "${test_cmd}" < /dev/null >/dev/null 2>&1; then
log_info "${description} ready (took ${elapsed_time}s)"
return 0
fi
Expand DownExpand Up@@ -2215,7 +2215,10 @@ ssh_run_server() {
cmd="set -x; ${cmd}"
fi
# shellcheck disable=SC2086
ssh $SSH_OPTS "${SSH_USER:-root}@${ip}" -- "${cmd}"
# < /dev/null prevents SSH from consuming the parent script's stdin.
# Without this, sequential SSH calls can steal input meant for later
# commands (e.g., safe_read prompts), causing hangs.
ssh $SSH_OPTS "${SSH_USER:-root}@${ip}" -- "${cmd}" < /dev/null
}

# Upload a file to a remote server via SCP
Expand Down
4 changes: 2 additions & 2 deletions sprite/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,8 +16,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}

agent_env_vars() {
Expand Down
12 changes: 6 additions & 6 deletions test/fixtures/_shared_agent_assertions.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,17 +36,17 @@ assert_agent_install() {
# git clone https://github.com/gavrielc/nanoclaw.git && npm install
_assert_install_pattern "git.*clone.*nanoclaw" "installs nanoclaw via git clone" ;;
aider)
# pip install aider-chat (or pip3)
_assert_install_pattern "pip.*install.*aider" "installs aider via pip" ;;
# uv tool install --python 3.12 --upgrade aider-chat
_assert_install_pattern "uv.*tool.*install.*aider" "installs aider via uv" ;;
goose)
# curl -fsSL https://github.com/block/goose/releases/.../download_cli.sh | bash
_assert_install_pattern "goose.*download_cli" "installs goose via curl installer" ;;
codex)
# npm install -g @openai/codex
_assert_install_pattern "npm.*install.*codex" "installs codex via npm" ;;
interpreter)
# pip install open-interpreter (or pip3)
_assert_install_pattern "pip.*install.*open-interpreter" "installs interpreter via pip" ;;
# uv tool install open-interpreter --python 3.12
_assert_install_pattern "uv.*tool.*install.*open-interpreter" "installs interpreter via uv" ;;
gemini)
# npm install -g @google/gemini-cli
_assert_install_pattern "npm.*install.*gemini-cli" "installs gemini via npm" ;;
Expand All@@ -57,8 +57,8 @@ assert_agent_install() {
# npm install -g cline
_assert_install_pattern "npm.*install.*cline" "installs cline via npm" ;;
gptme)
# pip install gptme (or pip3)
_assert_install_pattern "pip.*install.*gptme" "installs gptme via pip" ;;
# uv tool install gptme
_assert_install_pattern "uv.*tool.*install.*gptme" "installs gptme via uv" ;;
opencode)
# curl to download opencode tarball (via opencode_install_cmd)
_assert_install_pattern "opencode" "installs opencode" ;;
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
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
4 changes: 2 additions & 2 deletions aws/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,8 +17,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}
agent_env_vars() { generate_env_config "OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"; }
agent_launch_cmd() { printf 'source ~/.zshrc && aider --model openrouter/%s' "${MODEL_ID}"; }
Expand Down
4 changes: 2 additions & 2 deletions daytona/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,8 +16,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}

agent_env_vars() {
Expand Down
19 changes: 9 additions & 10 deletions daytona/lib/common.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -208,13 +208,14 @@ wait_for_cloud_init() {
log_info "Base tools installed"
}

# Daytona uses `daytona exec` for running commands in sandboxes
# SECURITY: Uses printf %q to properly escape commands to prevent injection
# Daytona uses `daytona exec` for running commands in sandboxes.
# The command string is passed directly to bash -c as a single argument.
# All callers pass trusted, hardcoded command strings (not user input).
# Do NOT use printf '%q' here — it escapes shell operators like && and ||
# into literal characters, breaking multi-part commands.
run_server() {
local cmd="${1}"
local escaped_cmd
escaped_cmd=$(printf '%q' "${cmd}")
daytona exec "${DAYTONA_SANDBOX_ID}" -- bash -c "${escaped_cmd}"
daytona exec "${DAYTONA_SANDBOX_ID}" -- bash -c "${cmd}"
}

upload_file() {
Expand DownExpand Up@@ -242,11 +243,9 @@ interactive_session() {
# Pure interactive shell via SSH
daytona ssh "${DAYTONA_SANDBOX_ID}" || session_exit=$?
else
# Run a specific command interactively via exec
# SECURITY: Properly escape command
local escaped_cmd
escaped_cmd=$(printf '%q' "${cmd}")
daytona exec "${DAYTONA_SANDBOX_ID}" -- bash -c "${escaped_cmd}" || session_exit=$?
# Run a specific command interactively via exec.
# Pass directly to bash -c — do NOT use printf '%q' (see run_server comment).
daytona exec "${DAYTONA_SANDBOX_ID}" -- bash -c "${cmd}" || session_exit=$?
fi
SERVER_NAME="${DAYTONA_SANDBOX_ID:-}" SPAWN_RECONNECT_CMD="daytona ssh ${DAYTONA_SANDBOX_ID:-}" \
_show_exec_post_session_summary
Expand Down
4 changes: 2 additions & 2 deletions digitalocean/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,8 +17,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}
agent_env_vars() { generate_env_config "OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"; }
agent_launch_cmd() { printf 'source ~/.zshrc && aider --model openrouter/%s' "${MODEL_ID}"; }
Expand Down
4 changes: 2 additions & 2 deletions fly/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,8 +16,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}

agent_env_vars() {
Expand Down
20 changes: 15 additions & 5 deletions fly/lib/common.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -362,7 +362,12 @@ run_server() {
# Ubuntu's default .bashrc returns early for non-interactive shells, so
# "source ~/.bashrc && bun ..." fails — bun's PATH line is never reached.
local full_cmd="export PATH=\"\$HOME/.local/bin:\$HOME/.bun/bin:\$PATH\" && $cmd"
# SECURITY: Properly escape command to prevent injection
# printf '%q' escapes the command so it becomes a single shell word.
# The remote shell parses "bash -c <escaped>" — the backslash escapes
# are consumed during word parsing, reconstructing the original command
# as the sole argument to bash -c.
# NOTE: Do NOT wrap $escaped_cmd in additional quotes — double-quoting
# preserves the backslashes literally, breaking operators like && and |.
local escaped_cmd
escaped_cmd=$(printf '%q' "$full_cmd")

Expand All@@ -376,12 +381,12 @@ run_server() {
elif command -v gtimeout &>/dev/null; then timeout_bin="gtimeout"
fi
if [[ -n "${timeout_bin}" ]]; then
"${timeout_bin}" "${timeout_secs}" "$fly_cmd" ssh console -a "$FLY_APP_NAME" -C "bash -c \"$escaped_cmd\"" --quiet 2>/dev/null
"${timeout_bin}" "${timeout_secs}" "$fly_cmd" ssh console -a "$FLY_APP_NAME" -C "bash -c $escaped_cmd" --quiet
return $?
fi
fi

"$fly_cmd" ssh console -a "$FLY_APP_NAME" -C "bash -c \"$escaped_cmd\"" --quiet 2>/dev/null
"$fly_cmd" ssh console -a "$FLY_APP_NAME" -C "bash -c $escaped_cmd" --quiet
}

# Upload a file to the machine via base64 encoding through exec
Expand All@@ -408,11 +413,16 @@ interactive_session() {
# Wrap in bash -c with PATH prepended (same as run_server) so shell builtins
# like "source" work — fly ssh console -C execs directly, not via a shell.
local full_cmd="export PATH=\"\$HOME/.local/bin:\$HOME/.bun/bin:\$PATH\" && $cmd"
# SECURITY: Properly escape command to prevent injection
# printf '%q' makes the command a single shell word; the remote shell
# unescapes it back into the original command for bash -c.
# Do NOT add quotes around $escaped_cmd (see run_server comment).
local escaped_cmd
escaped_cmd=$(printf '%q' "$full_cmd")
local session_exit=0
"$(_get_fly_cmd)" ssh console -a "$FLY_APP_NAME" -C "bash -c \"$escaped_cmd\"" || session_exit=$?
# --pty allocates a pseudo-terminal so interactive TUI agents (aider, claude)
# receive a proper TTY on stdin. Without it, fly ssh console -C runs the
# command without a PTY and agents see "Input is not a terminal (fd=0)".
"$(_get_fly_cmd)" ssh console -a "$FLY_APP_NAME" --pty -C "bash -c $escaped_cmd" || session_exit=$?
SERVER_NAME="${FLY_APP_NAME:-}" SPAWN_RECONNECT_CMD="fly ssh console -a ${FLY_APP_NAME:-}" \
_show_exec_post_session_summary
return "${session_exit}"
Expand Down
4 changes: 2 additions & 2 deletions gcp/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,8 +17,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}
agent_env_vars() { generate_env_config "OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"; }
agent_launch_cmd() { printf 'source ~/.zshrc && aider --model openrouter/%s' "${MODEL_ID}"; }
Expand Down
4 changes: 2 additions & 2 deletions hetzner/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,8 +17,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}
agent_env_vars() { generate_env_config "OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"; }
agent_launch_cmd() { printf 'source ~/.zshrc && aider --model openrouter/%s' "${MODEL_ID}"; }
Expand Down
4 changes: 2 additions & 2 deletions local/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,8 +16,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}

agent_env_vars() {
Expand Down
4 changes: 2 additions & 2 deletions ovh/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,8 +17,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}
agent_env_vars() { generate_env_config "OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"; }
agent_launch_cmd() { printf 'source ~/.zshrc && aider --model openrouter/%s' "${MODEL_ID}"; }
Expand Down
11 changes: 7 additions & 4 deletions shared/common.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -1627,12 +1627,12 @@ _validate_ssh_opts() {
# Default SSH options for all cloud providers
# Clouds can override this if they need provider-specific settings
if [[ -z "${SSH_OPTS:-}" ]]; then
SSH_OPTS="-o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -i ${HOME}/.ssh/id_ed25519"
SSH_OPTS="-o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -o ServerAliveInterval=15 -o ServerAliveCountMax=3 -o ConnectTimeout=10 -i ${HOME}/.ssh/id_ed25519"
else
# Validate user-provided SSH_OPTS for security
if ! _validate_ssh_opts "${SSH_OPTS}"; then
log_error "Invalid SSH_OPTS provided. Using secure defaults."
SSH_OPTS="-o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -i ${HOME}/.ssh/id_ed25519"
SSH_OPTS="-o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -o ServerAliveInterval=15 -o ServerAliveCountMax=3 -o ConnectTimeout=10 -i ${HOME}/.ssh/id_ed25519"
fi
fi

Expand DownExpand Up@@ -2166,7 +2166,7 @@ generic_ssh_wait() {
log_step "Waiting for ${description} to ${ip} (this usually takes 30-90 seconds)..."
while [[ "${attempt}" -le "${max_attempts}" ]]; do
# shellcheck disable=SC2086
if ssh ${ssh_opts} "${username}@${ip}" "${test_cmd}" >/dev/null 2>&1; then
if ssh ${ssh_opts} "${username}@${ip}" "${test_cmd}" < /dev/null >/dev/null 2>&1; then
log_info "${description} ready (took ${elapsed_time}s)"
return 0
fi
Expand DownExpand Up@@ -2215,7 +2215,10 @@ ssh_run_server() {
cmd="set -x; ${cmd}"
fi
# shellcheck disable=SC2086
ssh $SSH_OPTS "${SSH_USER:-root}@${ip}" -- "${cmd}"
# < /dev/null prevents SSH from consuming the parent script's stdin.
# Without this, sequential SSH calls can steal input meant for later
# commands (e.g., safe_read prompts), causing hangs.
ssh $SSH_OPTS "${SSH_USER:-root}@${ip}" -- "${cmd}" < /dev/null
}

# Upload a file to a remote server via SCP
Expand Down
4 changes: 2 additions & 2 deletions sprite/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,8 +16,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}

agent_env_vars() {
Expand Down
12 changes: 6 additions & 6 deletions test/fixtures/_shared_agent_assertions.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,17 +36,17 @@ assert_agent_install() {
# git clone https://github.com/gavrielc/nanoclaw.git && npm install
_assert_install_pattern "git.*clone.*nanoclaw" "installs nanoclaw via git clone" ;;
aider)
# pip install aider-chat (or pip3)
_assert_install_pattern "pip.*install.*aider" "installs aider via pip" ;;
# uv tool install --python 3.12 --upgrade aider-chat
_assert_install_pattern "uv.*tool.*install.*aider" "installs aider via uv" ;;
goose)
# curl -fsSL https://github.com/block/goose/releases/.../download_cli.sh | bash
_assert_install_pattern "goose.*download_cli" "installs goose via curl installer" ;;
codex)
# npm install -g @openai/codex
_assert_install_pattern "npm.*install.*codex" "installs codex via npm" ;;
interpreter)
# pip install open-interpreter (or pip3)
_assert_install_pattern "pip.*install.*open-interpreter" "installs interpreter via pip" ;;
# uv tool install open-interpreter --python 3.12
_assert_install_pattern "uv.*tool.*install.*open-interpreter" "installs interpreter via uv" ;;
gemini)
# npm install -g @google/gemini-cli
_assert_install_pattern "npm.*install.*gemini-cli" "installs gemini via npm" ;;
Expand All@@ -57,8 +57,8 @@ assert_agent_install() {
# npm install -g cline
_assert_install_pattern "npm.*install.*cline" "installs cline via npm" ;;
gptme)
# pip install gptme (or pip3)
_assert_install_pattern "pip.*install.*gptme" "installs gptme via pip" ;;
# uv tool install gptme
_assert_install_pattern "uv.*tool.*install.*gptme" "installs gptme via uv" ;;
opencode)
# curl to download opencode tarball (via opencode_install_cmd)
_assert_install_pattern "opencode" "installs opencode" ;;
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
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
4 changes: 2 additions & 2 deletions aws/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,8 +17,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}
agent_env_vars() { generate_env_config "OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"; }
agent_launch_cmd() { printf 'source ~/.zshrc && aider --model openrouter/%s' "${MODEL_ID}"; }
Expand Down
4 changes: 2 additions & 2 deletions daytona/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,8 +16,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}

agent_env_vars() {
Expand Down
19 changes: 9 additions & 10 deletions daytona/lib/common.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -208,13 +208,14 @@ wait_for_cloud_init() {
log_info "Base tools installed"
}

# Daytona uses `daytona exec` for running commands in sandboxes
# SECURITY: Uses printf %q to properly escape commands to prevent injection
# Daytona uses `daytona exec` for running commands in sandboxes.
# The command string is passed directly to bash -c as a single argument.
# All callers pass trusted, hardcoded command strings (not user input).
# Do NOT use printf '%q' here — it escapes shell operators like && and ||
# into literal characters, breaking multi-part commands.
run_server() {
local cmd="${1}"
local escaped_cmd
escaped_cmd=$(printf '%q' "${cmd}")
daytona exec "${DAYTONA_SANDBOX_ID}" -- bash -c "${escaped_cmd}"
daytona exec "${DAYTONA_SANDBOX_ID}" -- bash -c "${cmd}"
}

upload_file() {
Expand DownExpand Up@@ -242,11 +243,9 @@ interactive_session() {
# Pure interactive shell via SSH
daytona ssh "${DAYTONA_SANDBOX_ID}" || session_exit=$?
else
# Run a specific command interactively via exec
# SECURITY: Properly escape command
local escaped_cmd
escaped_cmd=$(printf '%q' "${cmd}")
daytona exec "${DAYTONA_SANDBOX_ID}" -- bash -c "${escaped_cmd}" || session_exit=$?
# Run a specific command interactively via exec.
# Pass directly to bash -c — do NOT use printf '%q' (see run_server comment).
daytona exec "${DAYTONA_SANDBOX_ID}" -- bash -c "${cmd}" || session_exit=$?
fi
SERVER_NAME="${DAYTONA_SANDBOX_ID:-}" SPAWN_RECONNECT_CMD="daytona ssh ${DAYTONA_SANDBOX_ID:-}" \
_show_exec_post_session_summary
Expand Down
4 changes: 2 additions & 2 deletions digitalocean/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,8 +17,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}
agent_env_vars() { generate_env_config "OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"; }
agent_launch_cmd() { printf 'source ~/.zshrc && aider --model openrouter/%s' "${MODEL_ID}"; }
Expand Down
4 changes: 2 additions & 2 deletions fly/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,8 +16,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}

agent_env_vars() {
Expand Down
20 changes: 15 additions & 5 deletions fly/lib/common.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -362,7 +362,12 @@ run_server() {
# Ubuntu's default .bashrc returns early for non-interactive shells, so
# "source ~/.bashrc && bun ..." fails — bun's PATH line is never reached.
local full_cmd="export PATH=\"\$HOME/.local/bin:\$HOME/.bun/bin:\$PATH\" && $cmd"
# SECURITY: Properly escape command to prevent injection
# printf '%q' escapes the command so it becomes a single shell word.
# The remote shell parses "bash -c <escaped>" — the backslash escapes
# are consumed during word parsing, reconstructing the original command
# as the sole argument to bash -c.
# NOTE: Do NOT wrap $escaped_cmd in additional quotes — double-quoting
# preserves the backslashes literally, breaking operators like && and |.
local escaped_cmd
escaped_cmd=$(printf '%q' "$full_cmd")

Expand All@@ -376,12 +381,12 @@ run_server() {
elif command -v gtimeout &>/dev/null; then timeout_bin="gtimeout"
fi
if [[ -n "${timeout_bin}" ]]; then
"${timeout_bin}" "${timeout_secs}" "$fly_cmd" ssh console -a "$FLY_APP_NAME" -C "bash -c \"$escaped_cmd\"" --quiet 2>/dev/null
"${timeout_bin}" "${timeout_secs}" "$fly_cmd" ssh console -a "$FLY_APP_NAME" -C "bash -c $escaped_cmd" --quiet
return $?
fi
fi

"$fly_cmd" ssh console -a "$FLY_APP_NAME" -C "bash -c \"$escaped_cmd\"" --quiet 2>/dev/null
"$fly_cmd" ssh console -a "$FLY_APP_NAME" -C "bash -c $escaped_cmd" --quiet
}

# Upload a file to the machine via base64 encoding through exec
Expand All@@ -408,11 +413,16 @@ interactive_session() {
# Wrap in bash -c with PATH prepended (same as run_server) so shell builtins
# like "source" work — fly ssh console -C execs directly, not via a shell.
local full_cmd="export PATH=\"\$HOME/.local/bin:\$HOME/.bun/bin:\$PATH\" && $cmd"
# SECURITY: Properly escape command to prevent injection
# printf '%q' makes the command a single shell word; the remote shell
# unescapes it back into the original command for bash -c.
# Do NOT add quotes around $escaped_cmd (see run_server comment).
local escaped_cmd
escaped_cmd=$(printf '%q' "$full_cmd")
local session_exit=0
"$(_get_fly_cmd)" ssh console -a "$FLY_APP_NAME" -C "bash -c \"$escaped_cmd\"" || session_exit=$?
# --pty allocates a pseudo-terminal so interactive TUI agents (aider, claude)
# receive a proper TTY on stdin. Without it, fly ssh console -C runs the
# command without a PTY and agents see "Input is not a terminal (fd=0)".
"$(_get_fly_cmd)" ssh console -a "$FLY_APP_NAME" --pty -C "bash -c $escaped_cmd" || session_exit=$?
SERVER_NAME="${FLY_APP_NAME:-}" SPAWN_RECONNECT_CMD="fly ssh console -a ${FLY_APP_NAME:-}" \
_show_exec_post_session_summary
return "${session_exit}"
Expand Down
4 changes: 2 additions & 2 deletions gcp/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,8 +17,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}
agent_env_vars() { generate_env_config "OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"; }
agent_launch_cmd() { printf 'source ~/.zshrc && aider --model openrouter/%s' "${MODEL_ID}"; }
Expand Down
4 changes: 2 additions & 2 deletions hetzner/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,8 +17,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}
agent_env_vars() { generate_env_config "OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"; }
agent_launch_cmd() { printf 'source ~/.zshrc && aider --model openrouter/%s' "${MODEL_ID}"; }
Expand Down
4 changes: 2 additions & 2 deletions local/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,8 +16,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}

agent_env_vars() {
Expand Down
4 changes: 2 additions & 2 deletions ovh/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,8 +17,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}
agent_env_vars() { generate_env_config "OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"; }
agent_launch_cmd() { printf 'source ~/.zshrc && aider --model openrouter/%s' "${MODEL_ID}"; }
Expand Down
11 changes: 7 additions & 4 deletions shared/common.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -1627,12 +1627,12 @@ _validate_ssh_opts() {
# Default SSH options for all cloud providers
# Clouds can override this if they need provider-specific settings
if [[ -z "${SSH_OPTS:-}" ]]; then
SSH_OPTS="-o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -i ${HOME}/.ssh/id_ed25519"
SSH_OPTS="-o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -o ServerAliveInterval=15 -o ServerAliveCountMax=3 -o ConnectTimeout=10 -i ${HOME}/.ssh/id_ed25519"
else
# Validate user-provided SSH_OPTS for security
if ! _validate_ssh_opts "${SSH_OPTS}"; then
log_error "Invalid SSH_OPTS provided. Using secure defaults."
SSH_OPTS="-o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -i ${HOME}/.ssh/id_ed25519"
SSH_OPTS="-o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -o ServerAliveInterval=15 -o ServerAliveCountMax=3 -o ConnectTimeout=10 -i ${HOME}/.ssh/id_ed25519"
fi
fi

Expand DownExpand Up@@ -2166,7 +2166,7 @@ generic_ssh_wait() {
log_step "Waiting for ${description} to ${ip} (this usually takes 30-90 seconds)..."
while [[ "${attempt}" -le "${max_attempts}" ]]; do
# shellcheck disable=SC2086
if ssh ${ssh_opts} "${username}@${ip}" "${test_cmd}" >/dev/null 2>&1; then
if ssh ${ssh_opts} "${username}@${ip}" "${test_cmd}" < /dev/null >/dev/null 2>&1; then
log_info "${description} ready (took ${elapsed_time}s)"
return 0
fi
Expand DownExpand Up@@ -2215,7 +2215,10 @@ ssh_run_server() {
cmd="set -x; ${cmd}"
fi
# shellcheck disable=SC2086
ssh $SSH_OPTS "${SSH_USER:-root}@${ip}" -- "${cmd}"
# < /dev/null prevents SSH from consuming the parent script's stdin.
# Without this, sequential SSH calls can steal input meant for later
# commands (e.g., safe_read prompts), causing hangs.
ssh $SSH_OPTS "${SSH_USER:-root}@${ip}" -- "${cmd}" < /dev/null
}

# Upload a file to a remote server via SCP
Expand Down
4 changes: 2 additions & 2 deletions sprite/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,8 +16,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}

agent_env_vars() {
Expand Down
12 changes: 6 additions & 6 deletions test/fixtures/_shared_agent_assertions.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,17 +36,17 @@ assert_agent_install() {
# git clone https://github.com/gavrielc/nanoclaw.git && npm install
_assert_install_pattern "git.*clone.*nanoclaw" "installs nanoclaw via git clone" ;;
aider)
# pip install aider-chat (or pip3)
_assert_install_pattern "pip.*install.*aider" "installs aider via pip" ;;
# uv tool install --python 3.12 --upgrade aider-chat
_assert_install_pattern "uv.*tool.*install.*aider" "installs aider via uv" ;;
goose)
# curl -fsSL https://github.com/block/goose/releases/.../download_cli.sh | bash
_assert_install_pattern "goose.*download_cli" "installs goose via curl installer" ;;
codex)
# npm install -g @openai/codex
_assert_install_pattern "npm.*install.*codex" "installs codex via npm" ;;
interpreter)
# pip install open-interpreter (or pip3)
_assert_install_pattern "pip.*install.*open-interpreter" "installs interpreter via pip" ;;
# uv tool install open-interpreter --python 3.12
_assert_install_pattern "uv.*tool.*install.*open-interpreter" "installs interpreter via uv" ;;
gemini)
# npm install -g @google/gemini-cli
_assert_install_pattern "npm.*install.*gemini-cli" "installs gemini via npm" ;;
Expand All@@ -57,8 +57,8 @@ assert_agent_install() {
# npm install -g cline
_assert_install_pattern "npm.*install.*cline" "installs cline via npm" ;;
gptme)
# pip install gptme (or pip3)
_assert_install_pattern "pip.*install.*gptme" "installs gptme via pip" ;;
# uv tool install gptme
_assert_install_pattern "uv.*tool.*install.*gptme" "installs gptme via uv" ;;
opencode)
# curl to download opencode tarball (via opencode_install_cmd)
_assert_install_pattern "opencode" "installs opencode" ;;
Expand Down
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
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
4 changes: 2 additions & 2 deletions aws/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,8 +17,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}
agent_env_vars() { generate_env_config "OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"; }
agent_launch_cmd() { printf 'source ~/.zshrc && aider --model openrouter/%s' "${MODEL_ID}"; }
Expand Down
4 changes: 2 additions & 2 deletions daytona/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,8 +16,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}

agent_env_vars() {
Expand Down
19 changes: 9 additions & 10 deletions daytona/lib/common.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -208,13 +208,14 @@ wait_for_cloud_init() {
log_info "Base tools installed"
}

# Daytona uses `daytona exec` for running commands in sandboxes
# SECURITY: Uses printf %q to properly escape commands to prevent injection
# Daytona uses `daytona exec` for running commands in sandboxes.
# The command string is passed directly to bash -c as a single argument.
# All callers pass trusted, hardcoded command strings (not user input).
# Do NOT use printf '%q' here — it escapes shell operators like && and ||
# into literal characters, breaking multi-part commands.
run_server() {
local cmd="${1}"
local escaped_cmd
escaped_cmd=$(printf '%q' "${cmd}")
daytona exec "${DAYTONA_SANDBOX_ID}" -- bash -c "${escaped_cmd}"
daytona exec "${DAYTONA_SANDBOX_ID}" -- bash -c "${cmd}"
}

upload_file() {
Expand DownExpand Up@@ -242,11 +243,9 @@ interactive_session() {
# Pure interactive shell via SSH
daytona ssh "${DAYTONA_SANDBOX_ID}" || session_exit=$?
else
# Run a specific command interactively via exec
# SECURITY: Properly escape command
local escaped_cmd
escaped_cmd=$(printf '%q' "${cmd}")
daytona exec "${DAYTONA_SANDBOX_ID}" -- bash -c "${escaped_cmd}" || session_exit=$?
# Run a specific command interactively via exec.
# Pass directly to bash -c — do NOT use printf '%q' (see run_server comment).
daytona exec "${DAYTONA_SANDBOX_ID}" -- bash -c "${cmd}" || session_exit=$?
fi
SERVER_NAME="${DAYTONA_SANDBOX_ID:-}" SPAWN_RECONNECT_CMD="daytona ssh ${DAYTONA_SANDBOX_ID:-}" \
_show_exec_post_session_summary
Expand Down
4 changes: 2 additions & 2 deletions digitalocean/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,8 +17,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}
agent_env_vars() { generate_env_config "OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"; }
agent_launch_cmd() { printf 'source ~/.zshrc && aider --model openrouter/%s' "${MODEL_ID}"; }
Expand Down
4 changes: 2 additions & 2 deletions fly/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,8 +16,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}

agent_env_vars() {
Expand Down
20 changes: 15 additions & 5 deletions fly/lib/common.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -362,7 +362,12 @@ run_server() {
# Ubuntu's default .bashrc returns early for non-interactive shells, so
# "source ~/.bashrc && bun ..." fails — bun's PATH line is never reached.
local full_cmd="export PATH=\"\$HOME/.local/bin:\$HOME/.bun/bin:\$PATH\" && $cmd"
# SECURITY: Properly escape command to prevent injection
# printf '%q' escapes the command so it becomes a single shell word.
# The remote shell parses "bash -c <escaped>" — the backslash escapes
# are consumed during word parsing, reconstructing the original command
# as the sole argument to bash -c.
# NOTE: Do NOT wrap $escaped_cmd in additional quotes — double-quoting
# preserves the backslashes literally, breaking operators like && and |.
local escaped_cmd
escaped_cmd=$(printf '%q' "$full_cmd")

Expand All@@ -376,12 +381,12 @@ run_server() {
elif command -v gtimeout &>/dev/null; then timeout_bin="gtimeout"
fi
if [[ -n "${timeout_bin}" ]]; then
"${timeout_bin}" "${timeout_secs}" "$fly_cmd" ssh console -a "$FLY_APP_NAME" -C "bash -c \"$escaped_cmd\"" --quiet 2>/dev/null
"${timeout_bin}" "${timeout_secs}" "$fly_cmd" ssh console -a "$FLY_APP_NAME" -C "bash -c $escaped_cmd" --quiet
return $?
fi
fi

"$fly_cmd" ssh console -a "$FLY_APP_NAME" -C "bash -c \"$escaped_cmd\"" --quiet 2>/dev/null
"$fly_cmd" ssh console -a "$FLY_APP_NAME" -C "bash -c $escaped_cmd" --quiet
}

# Upload a file to the machine via base64 encoding through exec
Expand All@@ -408,11 +413,16 @@ interactive_session() {
# Wrap in bash -c with PATH prepended (same as run_server) so shell builtins
# like "source" work — fly ssh console -C execs directly, not via a shell.
local full_cmd="export PATH=\"\$HOME/.local/bin:\$HOME/.bun/bin:\$PATH\" && $cmd"
# SECURITY: Properly escape command to prevent injection
# printf '%q' makes the command a single shell word; the remote shell
# unescapes it back into the original command for bash -c.
# Do NOT add quotes around $escaped_cmd (see run_server comment).
local escaped_cmd
escaped_cmd=$(printf '%q' "$full_cmd")
local session_exit=0
"$(_get_fly_cmd)" ssh console -a "$FLY_APP_NAME" -C "bash -c \"$escaped_cmd\"" || session_exit=$?
# --pty allocates a pseudo-terminal so interactive TUI agents (aider, claude)
# receive a proper TTY on stdin. Without it, fly ssh console -C runs the
# command without a PTY and agents see "Input is not a terminal (fd=0)".
"$(_get_fly_cmd)" ssh console -a "$FLY_APP_NAME" --pty -C "bash -c $escaped_cmd" || session_exit=$?
SERVER_NAME="${FLY_APP_NAME:-}" SPAWN_RECONNECT_CMD="fly ssh console -a ${FLY_APP_NAME:-}" \
_show_exec_post_session_summary
return "${session_exit}"
Expand Down
4 changes: 2 additions & 2 deletions gcp/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,8 +17,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}
agent_env_vars() { generate_env_config "OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"; }
agent_launch_cmd() { printf 'source ~/.zshrc && aider --model openrouter/%s' "${MODEL_ID}"; }
Expand Down
4 changes: 2 additions & 2 deletions hetzner/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,8 +17,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}
agent_env_vars() { generate_env_config "OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"; }
agent_launch_cmd() { printf 'source ~/.zshrc && aider --model openrouter/%s' "${MODEL_ID}"; }
Expand Down
4 changes: 2 additions & 2 deletions local/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,8 +16,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}

agent_env_vars() {
Expand Down
4 changes: 2 additions & 2 deletions ovh/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,8 +17,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}
agent_env_vars() { generate_env_config "OPENROUTER_API_KEY=${OPENROUTER_API_KEY}"; }
agent_launch_cmd() { printf 'source ~/.zshrc && aider --model openrouter/%s' "${MODEL_ID}"; }
Expand Down
11 changes: 7 additions & 4 deletions shared/common.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -1627,12 +1627,12 @@ _validate_ssh_opts() {
# Default SSH options for all cloud providers
# Clouds can override this if they need provider-specific settings
if [[ -z "${SSH_OPTS:-}" ]]; then
SSH_OPTS="-o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -i ${HOME}/.ssh/id_ed25519"
SSH_OPTS="-o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -o ServerAliveInterval=15 -o ServerAliveCountMax=3 -o ConnectTimeout=10 -i ${HOME}/.ssh/id_ed25519"
else
# Validate user-provided SSH_OPTS for security
if ! _validate_ssh_opts "${SSH_OPTS}"; then
log_error "Invalid SSH_OPTS provided. Using secure defaults."
SSH_OPTS="-o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -i ${HOME}/.ssh/id_ed25519"
SSH_OPTS="-o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -o ServerAliveInterval=15 -o ServerAliveCountMax=3 -o ConnectTimeout=10 -i ${HOME}/.ssh/id_ed25519"
fi
fi

Expand DownExpand Up@@ -2166,7 +2166,7 @@ generic_ssh_wait() {
log_step "Waiting for ${description} to ${ip} (this usually takes 30-90 seconds)..."
while [[ "${attempt}" -le "${max_attempts}" ]]; do
# shellcheck disable=SC2086
if ssh ${ssh_opts} "${username}@${ip}" "${test_cmd}" >/dev/null 2>&1; then
if ssh ${ssh_opts} "${username}@${ip}" "${test_cmd}" < /dev/null >/dev/null 2>&1; then
log_info "${description} ready (took ${elapsed_time}s)"
return 0
fi
Expand DownExpand Up@@ -2215,7 +2215,10 @@ ssh_run_server() {
cmd="set -x; ${cmd}"
fi
# shellcheck disable=SC2086
ssh $SSH_OPTS "${SSH_USER:-root}@${ip}" -- "${cmd}"
# < /dev/null prevents SSH from consuming the parent script's stdin.
# Without this, sequential SSH calls can steal input meant for later
# commands (e.g., safe_read prompts), causing hangs.
ssh $SSH_OPTS "${SSH_USER:-root}@${ip}" -- "${cmd}" < /dev/null
}

# Upload a file to a remote server via SCP
Expand Down
4 changes: 2 additions & 2 deletions sprite/aider.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -16,8 +16,8 @@ AGENT_MODEL_PROMPT=1
AGENT_MODEL_DEFAULT="openrouter/auto"

agent_install() {
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --upgrade --with audioop-lts aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --upgrade --with audioop-lts --reinstall aider-chat" cloud_run
install_agent "Aider" "command -v uv >/dev/null || { command -v brew >/dev/null && brew install uv || curl -LsSf https://astral.sh/uv/install.sh | sh; } && echo Installing aider-chat this may take a few minutes... && uv tool install --python 3.12 --upgrade aider-chat" cloud_run
verify_agent "Aider" "export PATH=\"\$HOME/.local/bin:\$PATH\" && command -v aider" "uv tool install --python 3.12 --upgrade --reinstall aider-chat" cloud_run
}

agent_env_vars() {
Expand Down
12 changes: 6 additions & 6 deletions test/fixtures/_shared_agent_assertions.sh
Original file line numberDiff line numberDiff line change
Expand Up@@ -36,17 +36,17 @@ assert_agent_install() {
# git clone https://github.com/gavrielc/nanoclaw.git && npm install
_assert_install_pattern "git.*clone.*nanoclaw" "installs nanoclaw via git clone" ;;
aider)
# pip install aider-chat (or pip3)
_assert_install_pattern "pip.*install.*aider" "installs aider via pip" ;;
# uv tool install --python 3.12 --upgrade aider-chat
_assert_install_pattern "uv.*tool.*install.*aider" "installs aider via uv" ;;
goose)
# curl -fsSL https://github.com/block/goose/releases/.../download_cli.sh | bash
_assert_install_pattern "goose.*download_cli" "installs goose via curl installer" ;;
codex)
# npm install -g @openai/codex
_assert_install_pattern "npm.*install.*codex" "installs codex via npm" ;;
interpreter)
# pip install open-interpreter (or pip3)
_assert_install_pattern "pip.*install.*open-interpreter" "installs interpreter via pip" ;;
# uv tool install open-interpreter --python 3.12
_assert_install_pattern "uv.*tool.*install.*open-interpreter" "installs interpreter via uv" ;;
gemini)
# npm install -g @google/gemini-cli
_assert_install_pattern "npm.*install.*gemini-cli" "installs gemini via npm" ;;
Expand All@@ -57,8 +57,8 @@ assert_agent_install() {
# npm install -g cline
_assert_install_pattern "npm.*install.*cline" "installs cline via npm" ;;
gptme)
# pip install gptme (or pip3)
_assert_install_pattern "pip.*install.*gptme" "installs gptme via pip" ;;
# uv tool install gptme
_assert_install_pattern "uv.*tool.*install.*gptme" "installs gptme via uv" ;;
opencode)
# curl to download opencode tarball (via opencode_install_cmd)
_assert_install_pattern "opencode" "installs opencode" ;;
Expand Down