fix: Prevent Python injection in Modal provider via env vars (#127)

MODAL_SANDBOX_ID and sandbox name were interpolated directly into
Python code strings, allowing potential code injection. Now all
user-controlled values are passed via environment variables and
read with os.environ in Python.

Changes:
- create_server: pass name/image via _MODAL_NAME/_MODAL_IMAGE env vars,
  use getattr() for image lookup, add sandbox name validation
- run_server: pass sandbox ID and command via env vars
- interactive_session: pass sandbox ID and command via env vars
- destroy_server: pass sandbox ID via env var
- Add validate_sandbox_id() to enforce sb-<alphanumeric> format
- upload_file: remove printf '%q' escaping (base64 is safe)

Agent: security-auditor

Co-authored-by: A <6723574+louisgv@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
A 2026-02-09 20:22:08 -08:00 committed by GitHub
parent 009f3454fc
commit dcf41c63ea
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -67,19 +67,29 @@ create_server() {
return 1
fi
# Validate sandbox name - alphanumeric and dashes only
if [[ ! "${name}" =~ ^[a-zA-Z0-9][a-zA-Z0-9_-]*$ ]]; then
log_error "Invalid sandbox name: must be alphanumeric with dashes/underscores"
return 1
fi
log_warn "Creating Modal sandbox '${name}'..."
# Capture both stdout and stderr from Python SDK
# SECURITY: Pass name via environment variable to prevent Python injection
local create_output
local create_exitcode
create_output=$(python3 -c "
import modal, sys
create_output=$(_MODAL_NAME="${name}" _MODAL_IMAGE="${image}" python3 -c "
import modal, sys, os
try:
app = modal.App.lookup('spawn-${name}', create_if_missing=True)
name = os.environ['_MODAL_NAME']
image_name = os.environ['_MODAL_IMAGE']
app = modal.App.lookup('spawn-' + name, create_if_missing=True)
image_fn = getattr(modal.Image, image_name)
sb = modal.Sandbox.create(
app=app,
name='${name}',
image=modal.Image.${image}().apt_install('curl', 'unzip', 'git', 'zsh'),
name=name,
image=image_fn().apt_install('curl', 'unzip', 'git', 'zsh'),
timeout=3600,
)
print(sb.object_id)
@ -125,19 +135,27 @@ wait_for_cloud_init() {
log_info "Tools installed"
}
# Validate Modal sandbox ID format (sb-XXXXX)
validate_sandbox_id() {
local sid="${1}"
if [[ ! "${sid}" =~ ^sb-[a-zA-Z0-9_-]+$ ]]; then
log_error "Invalid MODAL_SANDBOX_ID format: expected sb-<alphanumeric>"
return 1
fi
}
# Modal uses Python SDK for exec
run_server() {
local cmd="${1}"
# SECURITY: Properly escape command to prevent injection
local escaped_cmd
escaped_cmd=$(printf '%q' "${cmd}")
python3 -c "
import modal, shlex
sb = modal.Sandbox.from_id('${MODAL_SANDBOX_ID}')
p = sb.exec('bash', '-c', ${escaped_cmd})
validate_sandbox_id "${MODAL_SANDBOX_ID}" || return 1
# SECURITY: Pass sandbox ID and command via environment variables to prevent Python injection
_MODAL_SB_ID="${MODAL_SANDBOX_ID}" _MODAL_CMD="${cmd}" python3 -c "
import modal, os, sys
sb = modal.Sandbox.from_id(os.environ['_MODAL_SB_ID'])
p = sb.exec('bash', '-c', os.environ['_MODAL_CMD'])
print(p.stdout.read(), end='')
if p.stderr.read():
import sys; print(p.stderr.read(), end='', file=sys.stderr)
print(p.stderr.read(), end='', file=sys.stderr)
p.wait()
"
}
@ -147,23 +165,19 @@ upload_file() {
local remote_path="${2}"
local content
content=$(base64 -w0 "${local_path}" 2>/dev/null || base64 "${local_path}")
# SECURITY: Properly escape paths and content to prevent injection
local escaped_path
escaped_path=$(printf '%q' "${remote_path}")
local escaped_content
escaped_content=$(printf '%q' "${content}")
run_server "echo ${escaped_content} | base64 -d > ${escaped_path}"
# base64 output is safe (alphanumeric + /+=) so no injection risk in the echo
# Remote path needs quoting for spaces/special chars in the remote shell
run_server "echo '${content}' | base64 -d > '${remote_path}'"
}
interactive_session() {
local cmd="${1}"
# SECURITY: Properly escape command to prevent injection
local escaped_cmd
escaped_cmd=$(printf '%q' "${cmd}")
python3 -c "
import modal, sys
sb = modal.Sandbox.from_id('${MODAL_SANDBOX_ID}')
p = sb.exec('bash', '-c', ${escaped_cmd}, pty=True)
validate_sandbox_id "${MODAL_SANDBOX_ID}" || return 1
# SECURITY: Pass sandbox ID and command via environment variables to prevent Python injection
_MODAL_SB_ID="${MODAL_SANDBOX_ID}" _MODAL_CMD="${cmd}" python3 -c "
import modal, sys, os
sb = modal.Sandbox.from_id(os.environ['_MODAL_SB_ID'])
p = sb.exec('bash', '-c', os.environ['_MODAL_CMD'], pty=True)
for line in p.stdout:
print(line, end='')
p.wait()
@ -172,10 +186,12 @@ p.wait()
destroy_server() {
local sandbox_id="${1:-${MODAL_SANDBOX_ID}}"
validate_sandbox_id "${sandbox_id}" || return 1
log_warn "Terminating sandbox..."
python3 -c "
import modal
sb = modal.Sandbox.from_id('${sandbox_id}')
# SECURITY: Pass sandbox ID via environment variable to prevent Python injection
_MODAL_SB_ID="${sandbox_id}" python3 -c "
import modal, os
sb = modal.Sandbox.from_id(os.environ['_MODAL_SB_ID'])
sb.terminate()
" 2>/dev/null || true
log_info "Sandbox terminated"