fix: harden Sprite exec against injection via org flags and grep patterns (#2446)

- Replace word-split _sprite_org_flags() call sites with _sprite_cmd()
  helper that uses a proper bash array for the -o flag, eliminating
  injection risk from org names with spaces or shell metacharacters
- Validate _SPRITE_ORG against [A-Za-z0-9_-]+ in _sprite_validate_env
- Use grep -qF (fixed-string) instead of grep -q for app name matching
  to prevent regex metacharacters in names from causing false matches
- Use mktemp for _stderr_tmp in _sprite_exec instead of predictable
  PID-based path (/tmp/sprite-exec-err.$$) to prevent symlink attacks

Closes #2436

Agent: complexity-hunter

Co-authored-by: B <6723574+louisgv@users.noreply.github.com>
This commit is contained in:
A 2026-03-10 10:08:17 -07:00 committed by GitHub
parent 9bf3c216e8
commit 47b26deafa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -16,13 +16,26 @@ set -eo pipefail
# from concurrent sprite exec calls corrupting ~/.sprites/sprites.json.
_SPRITE_ORG=""
# Helper: build org flags array for sprite CLI calls
# Helper: build org flags for sprite CLI calls.
# Outputs "-o" and the org name as separate lines for use with _sprite_cmd.
_sprite_org_flags() {
if [ -n "${_SPRITE_ORG}" ]; then
printf '%s' "-o ${_SPRITE_ORG}"
printf '%s\n%s' "-o" "${_SPRITE_ORG}"
fi
}
# Helper: run sprite CLI with org flags safely (no word-splitting).
# Usage: _sprite_cmd [extra args...]
# Reads org flags via _sprite_org_flags and builds a proper argument array.
_sprite_cmd() {
local _args
_args=()
if [ -n "${_SPRITE_ORG}" ]; then
_args+=("-o" "${_SPRITE_ORG}")
fi
sprite "${_args[@]}" "$@"
}
# Helper: fix corrupted sprite config (double-closing-brace from concurrent writes)
_sprite_fix_config() {
local cfg="${HOME}/.sprites/sprites.json"
@ -93,6 +106,13 @@ _sprite_validate_env() {
_SPRITE_ORG="${SPRITE_ORG:-}"
fi
# Validate org name contains only safe characters (alphanumeric, dash, underscore)
# to prevent injection via crafted org names in subsequent CLI calls.
if [ -n "${_SPRITE_ORG}" ] && ! printf '%s' "${_SPRITE_ORG}" | grep -qE '^[A-Za-z0-9_-]+$'; then
log_err "Invalid Sprite org name: ${_SPRITE_ORG}"
return 1
fi
if [ -n "${_SPRITE_ORG}" ]; then
log_ok "Sprite credentials validated (org: ${_SPRITE_ORG})"
else
@ -134,15 +154,14 @@ _sprite_provision_verify() {
# Check instance exists in sprite list
_sprite_fix_config
local sprite_output
# shellcheck disable=SC2046
sprite_output=$(sprite $(_sprite_org_flags) list 2>/dev/null || true)
sprite_output=$(_sprite_cmd list 2>/dev/null || true)
if [ -z "${sprite_output}" ]; then
log_err "Could not list Sprite instances"
return 1
fi
if ! printf '%s' "${sprite_output}" | grep -q "${app}"; then
if ! printf '%s' "${sprite_output}" | grep -qF "${app}"; then
log_err "Sprite instance ${app} not found in sprite list"
return 1
fi
@ -171,14 +190,14 @@ _sprite_exec() {
local cmd="$2"
local _attempt=0
local _max=3
local _stderr_tmp="/tmp/sprite-exec-err.$$"
local _stderr_tmp
_stderr_tmp=$(mktemp /tmp/sprite-exec-err.XXXXXX) || return 1
while [ "${_attempt}" -lt "${_max}" ]; do
_sprite_fix_config
# Pipe the command via stdin to avoid interpolating it into the remote
# command string — eliminates shell injection risk.
# shellcheck disable=SC2046
printf '%s' "${cmd}" | sprite $(_sprite_org_flags) exec -s "${app}" -- bash 2>"${_stderr_tmp}"
printf '%s' "${cmd}" | _sprite_cmd exec -s "${app}" -- bash 2>"${_stderr_tmp}"
local _rc=$?
if [ "${_rc}" -eq 0 ]; then
rm -f "${_stderr_tmp}"
@ -208,18 +227,16 @@ _sprite_teardown() {
log_step "Tearing down ${app}..."
# shellcheck disable=SC2046
sprite $(_sprite_org_flags) destroy --force "${app}" >/dev/null 2>&1 || true
_sprite_cmd destroy --force "${app}" >/dev/null 2>&1 || true
# Brief wait for destruction to propagate
sleep 2
# Verify deletion
local sprite_output
# shellcheck disable=SC2046
sprite_output=$(sprite $(_sprite_org_flags) list 2>/dev/null || true)
sprite_output=$(_sprite_cmd list 2>/dev/null || true)
if printf '%s' "${sprite_output}" | grep -q "${app}"; then
if printf '%s' "${sprite_output}" | grep -qF "${app}"; then
log_warn "Sprite instance ${app} may still exist"
else
log_ok "Sprite instance ${app} torn down"
@ -241,8 +258,7 @@ _sprite_cleanup_stale() {
# List all sprites
local sprite_output
# shellcheck disable=SC2046
sprite_output=$(sprite $(_sprite_org_flags) list 2>/dev/null || true)
sprite_output=$(_sprite_cmd list 2>/dev/null || true)
if [ -z "${sprite_output}" ]; then
log_info "Could not list Sprite instances or none found — skipping cleanup"