Root cause
Injection & unsafe execution
fix: add Windows support with minimal changes
How AI contributed: Flawed AI-written codePublished Apr 10, 2026 in
mervinpraison/praisonaiAll 19 findings in this repository
In src/praisonai/praisonai/deploy.py, a Claude-authored change moved the Cloud Run deploy command into argument-list form but still built a single comma-joined --set-env-vars value from OPENAI_MODEL_NAME, OPENAI_API_KEY, and OPENAI_API_BASE. Nothing rejected commas in those values, and gcloud splits the argument on commas, so text after a comma became extra KEY=VALUE pairs. Whoever controls one of those settings could inject arbitrary environment variables into the deployed service. The fix writes the values to a mode-0600 temporary file and passes it with --env-vars-file.
deploy.py interpolated environment-derived values into the --set-env-vars argument without rejecting commas, and gcloud treats every comma in that argument as a separator between KEY=VALUE pairs. The values were also passed inline on the command line, which exposed the API key in the process list until the fix moved them into a temporary file.
Root cause
fix: add Windows support with minimal changes
Fix
fix: Security vulnerabilities in wrapper layer (#1744)
AI-assisted fix: GitHub Copilot + Claude Code · praisonai-triage-agent[bot]
Case facts
@@ -116,20 +116,46 @@ def run_commands(self): self.create_api_file() self.create_dockerfile() """Runs a sequence of shell commands for deployment, continues on error."""+ # Get the project ID first to make it Windows-compatible+ try:+ project_result = subprocess.run(+ ["gcloud", "config", "get-value", "project"],+ capture_output=True,+ text=True,+ check=True+ )+ project_id = project_result.stdout.strip()+ except subprocess.CalledProcessError:+ print("ERROR: Failed to get GCloud project ID. Please ensure gcloud is configured.")+ return+ + # Get environment variables with defaults+ openai_model = os.environ.get('OPENAI_MODEL_NAME', '')+ openai_key = os.environ.get('OPENAI_API_KEY', '')+ openai_base = os.environ.get('OPENAI_API_BASE', '')+ commands = [- "yes | gcloud auth configure-docker us-central1-docker.pkg.dev",- "gcloud artifacts repositories create praisonai-repository --repository-format=docker --location=us-central1",- "docker build --platform linux/amd64 -t gcr.io/$(gcloud config get-value project)/praisonai-app:latest .",- "docker tag gcr.io/$(gcloud config get-value project)/praisonai-app:latest us-central1-docker.pkg.dev/$(gcloud config get-value project)/praisonai-repository/praisonai-app:latest",- "docker push us-central1-docker.pkg.dev/$(gcloud config get-value project)/praisonai-repository/praisonai-app:latest",- "gcloud run deploy praisonai-service --image us-central1-docker.pkg.dev/$(gcloud config get-value project)/praisonai-repository/praisonai-app:latest --platform managed --region us-central1 --allow-unauthenticated --set-env-vars OPENAI_MODEL_NAME=${OPENAI_MODEL_NAME},OPENAI_API_KEY=${OPENAI_API_KEY},OPENAI_API_BASE=${OPENAI_API_BASE}"+ ["yes", "|", "gcloud", "auth", "configure-docker", "us-central1-docker.pkg.dev"],+ ["gcloud", "artifacts", "repositories", "create", "praisonai-repository", "--repository-format=docker", "--location=us-central1"],+ ["docker", "build", "--platform", "linux/amd64", "-t", f"gcr.io/{project_id}/praisonai-app:latest", "."],+ ["docker", "tag", f"gcr.io/{project_id}/praisonai-app:latest", f"us-central1-docker.pkg.dev/{project_id}/praisonai-repository/praisonai-app:latest"],+ ["docker", "push", f"us-central1-docker.pkg.dev/{project_id}/praisonai-repository/praisonai-app:latest"],+ ["gcloud", "run", "deploy", "praisonai-service", "--image", f"us-central1-docker.pkg.dev/{project_id}/praisonai-repository/praisonai-app:latest", + "--platform", "managed", "--region", "us-central1", "--allow-unauthenticated", + "--set-env-vars", f"OPENAI_MODEL_NAME={openai_model},OPENAI_API_KEY={openai_key},OPENAI_API_BASE={openai_base}"] ] - for cmd in commands:+ for i, cmd in enumerate(commands): try:- subprocess.run(cmd, shell=True, check=True)+ # First command needs special handling for pipe+ if i == 0:+ # Use shell=True for pipe command+ subprocess.run(" ".join(cmd), shell=True, check=True)+ else:+ # Use list format for better cross-platform compatibility+ subprocess.run(cmd, check=True) except subprocess.CalledProcessError as e:- print(f"ERROR: Command '{e.cmd}' failed with exit status {e.returncode}")+ print(f"ERROR: Command failed with exit status {e.returncode}") print(f"Continuing with the next command...") # Usage@@ -7,6 +7,7 @@ import logging import os import re+import platform from typing import Any, List, Optional, Callable, Iterable, Union from functools import wraps, partial @@ -199,7 +200,13 @@ def __init__(self, command_or_string=None, args=None, *, command=None, timeout=6 # Handle the single string format for stdio client if isinstance(command_or_string, str) and args is None: # Split the string into command and args using shell-like parsing- parts = shlex.split(command_or_string)+ # On Windows, handle command splitting differently+ if platform.system() == 'Windows':+ # Simple split for Windows commands+ parts = command_or_string.split()+ else:+ parts = shlex.split(command_or_string)+ if not parts: raise ValueError("Empty command string") @@ -12,6 +12,7 @@ import logging import os import time+import platform from typing import Dict, List, Optional, Union from ..approval import require_approval @@ -25,6 +26,7 @@ def __init__(self): def _check_dependencies(self): """Check if required packages are installed.""" try:+ global psutil import psutil except ImportError: raise ImportError(@@ -58,7 +60,14 @@ def execute_command( try: # Split command if not using shell if not shell:- command = shlex.split(command)+ # On Windows, handle command splitting differently+ if platform.system() == 'Windows':+ # Simple split for Windows commands+ # This handles most common cases, but complex quoted arguments+ # should use shell=True or be passed as a list+ command = command.split()+ else:+ command = shlex.split(command) # Set up process environment process_env = os.environ.copy()@@ -201,7 +210,8 @@ def get_system_info(self) -> Dict[str, Union[float, int, str, Dict]]: try: cpu_percent = psutil.cpu_percent(interval=1) memory = psutil.virtual_memory()- disk = psutil.disk_usage('/')+ # Use cross-platform root path+ disk = psutil.disk_usage(os.path.abspath(os.sep)) return { 'cpu': {@@ -223,7 +233,7 @@ def get_system_info(self) -> Dict[str, Union[float, int, str, Dict]]: 'percent': disk.percent }, 'boot_time': psutil.boot_time(),- 'platform': os.uname().sysname+ 'platform': platform.system() } except Exception as e: error_msg = f"Error getting system info: {str(e)}"@@ -246,7 +256,9 @@ def get_system_info(self) -> Dict[str, Union[float, int, str, Dict]]: # 1. Execute command print("1. Command Execution") print("------------------------------")- result = execute_command("ls -la")+ # Use cross-platform command+ cmd = "dir" if platform.system() == "Windows" else "ls -la"+ result = execute_command(cmd) print(f"Success: {result['success']}") print(f"Output:\n{result['stdout']}") if result['stderr']:@@ -140,52 +140,82 @@ def run_commands(self): openai_key = ep.api_key or 'Enter your API key' openai_base = ep.base_url - # Build commands with actual values- commands = [- ['gcloud', 'auth', 'configure-docker', 'us-central1-docker.pkg.dev'],- ['gcloud', 'artifacts', 'repositories', 'create', 'praisonai-repository', - '--repository-format=docker', '--location=us-central1'],- ['docker', 'build', '--platform', 'linux/amd64', '-t', - f'gcr.io/{project_id}/praisonai-app:latest', '.'],- ['docker', 'tag', f'gcr.io/{project_id}/praisonai-app:latest',- f'us-central1-docker.pkg.dev/{project_id}/praisonai-repository/praisonai-app:latest'],- ['docker', 'push', - f'us-central1-docker.pkg.dev/{project_id}/praisonai-repository/praisonai-app:latest'],- ['gcloud', 'run', 'deploy', 'praisonai-service', - '--image', f'us-central1-docker.pkg.dev/{project_id}/praisonai-repository/praisonai-app:latest',- '--platform', 'managed', '--region', 'us-central1', '--allow-unauthenticated',- '--set-env-vars', f'OPENAI_MODEL_NAME={openai_model},OPENAI_API_KEY={openai_key},OPENAI_API_BASE={openai_base}']- ]+ # Create temporary env vars file to avoid exposing secrets in argv+ import tempfile+ import yaml+ import os - # Run commands with appropriate handling for each platform- for i, cmd in enumerate(commands):- try:- if i == 0: # First command (gcloud auth configure-docker)- if platform.system() != 'Windows':- # On Unix, pipe 'yes' to auto-confirm- proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)- proc.communicate(input=b'Y\n')- if proc.returncode != 0:- raise subprocess.CalledProcessError(proc.returncode, cmd)+ env_vars_file = None+ try:+ # Create secure temp file for environment variables+ fd, env_vars_file = tempfile.mkstemp(suffix=".yaml", prefix="praisonai-deploy-")+ os.close(fd)+ os.chmod(env_vars_file, 0o600) # Secure file permissions+ + # Write env vars to file instead of passing in argv+ env_vars = {+ "OPENAI_MODEL_NAME": openai_model,+ "OPENAI_API_KEY": openai_key,+ "OPENAI_API_BASE": openai_base+ }+ + with open(env_vars_file, "w") as f:+ yaml.safe_dump(env_vars, f)+ + # Build commands with secure env vars file+ commands = [+ ['gcloud', 'auth', 'configure-docker', 'us-central1-docker.pkg.dev'],+ ['gcloud', 'artifacts', 'repositories', 'create', 'praisonai-repository', + '--repository-format=docker', '--location=us-central1'],+ ['docker', 'build', '--platform', 'linux/amd64', '-t', + f'gcr.io/{project_id}/praisonai-app:latest', '.'],+ ['docker', 'tag', f'gcr.io/{project_id}/praisonai-app:latest',+ f'us-central1-docker.pkg.dev/{project_id}/praisonai-repository/praisonai-app:latest'],+ ['docker', 'push', + f'us-central1-docker.pkg.dev/{project_id}/praisonai-repository/praisonai-app:latest'],+ ['gcloud', 'run', 'deploy', 'praisonai-service', + '--image', f'us-central1-docker.pkg.dev/{project_id}/praisonai-repository/praisonai-app:latest',+ '--platform', 'managed', '--region', 'us-central1', '--allow-unauthenticated',+ '--env-vars-file', env_vars_file]+ ]+ + # Run commands with appropriate handling for each platform+ for i, cmd in enumerate(commands):+ try:+ if i == 0: # First command (gcloud auth configure-docker)+ if platform.system() != 'Windows':+ # On Unix, pipe 'yes' to auto-confirm+ proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)+ proc.communicate(input=b'Y\n')+ if proc.returncode != 0:+ raise subprocess.CalledProcessError(proc.returncode, cmd)+ else:+ # On Windows, try with --quiet flag to avoid prompts+ cmd_with_quiet = cmd + ['--quiet']+ try:+ subprocess.run(cmd_with_quiet, check=True)+ except subprocess.CalledProcessError:+ # If --quiet fails, try without it+ print("Note: You may need to manually confirm the authentication prompt")+ subprocess.run(cmd, check=True) else:- # On Windows, try with --quiet flag to avoid promptsAI-assisted change 5a11b258af53cdc7e397fce2963e5c9dd1df12d750e9dda30903c1c5c3aec2f7 · Fix dd79f9fc078cbd9aff125d3e451257df93be59fce126bca70dd1ee2e1bd68213