{"title":"PATH Hijacking via Unsafe subprocess.Popen Search Order","language":"Python","severity":"High","cwe":"CWE-426","source_lines":[3],"flow_lines":[3,5,6,8],"sink_lines":[8],"vulnerable_code":"import subprocess\nimport os\n\ndef deploy_model_to_edge(device_id, model_name, config_path):\n    edge_env = os.environ.copy()\n    edge_env['DEVICE_TARGET'] = device_id\n    edge_env['PATH'] = f\"/tmp/edge_tools:{edge_env.get('PATH', '')}\"\n    deployment_cmd = ['model_optimizer', '--input', model_name, '--config', config_path, '--device', device_id]\n    proc = subprocess.Popen(deployment_cmd, env=edge_env, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n    output, errors = proc.communicate(timeout=120)\n    if proc.returncode == 0:\n        return {'status': 'deployed', 'device': device_id, 'output': output.decode()}\n    else:\n        return {'status': 'failed', 'error': errors.decode()}","explanation":"The code prepends '/tmp/edge_tools' to the PATH environment variable before executing 'model_optimizer' via subprocess.Popen. An attacker with write access to /tmp/edge_tools can place a malicious 'model_optimizer' executable there, which will be executed instead of the legitimate binary due to PATH search order hijacking.","remediation":"The fix removes the unsafe PATH modification that prepended the world-writable /tmp/edge_tools directory. Instead, the model_optimizer binary is resolved using an explicit whitelist of trusted absolute paths, with symlink resolution and directory verification to prevent path traversal attacks. This eliminates the PATH hijacking vector entirely.","secure_code":"import subprocess\nimport os\nimport shutil\n\nALLOWED_OPTIMIZER_PATHS = [\n    '/usr/local/bin/model_optimizer',\n    '/opt/edge_tools/bin/model_optimizer',\n    '/usr/bin/model_optimizer'\n]\n\ndef _resolve_optimizer_path():\n    \"\"\"Resolve the model_optimizer binary from a whitelist of trusted paths.\"\"\"\n    for path in ALLOWED_OPTIMIZER_PATHS:\n        if os.path.isfile(path) and os.access(path, os.X_OK):\n            # Resolve symlinks and verify the resolved path is still in a trusted directory\n            real_path = os.path.realpath(path)\n            trusted_prefixes = ('/usr/local/bin/', '/opt/edge_tools/bin/', '/usr/bin/')\n            if real_path.startswith(trusted_prefixes):\n                return real_path\n    return None\n\ndef deploy_model_to_edge(device_id, model_name, config_path):\n    # Validate inputs to prevent command injection\n    if not all(isinstance(arg, str) for arg in [device_id, model_name, config_path]):\n        raise ValueError(\"All arguments must be strings\")\n    \n    # Resolve the optimizer binary from trusted, absolute paths only\n    optimizer_path = _resolve_optimizer_path()\n    if optimizer_path is None:\n        return {'status': 'failed', 'error': 'model_optimizer binary not found in trusted locations'}\n    \n    # Use a clean environment without modifying PATH unsafely\n    edge_env = os.environ.copy()\n    edge_env['DEVICE_TARGET'] = device_id\n    # Do NOT prepend writable directories like /tmp to PATH\n    \n    deployment_cmd = [optimizer_path, '--input', model_name, '--config', config_path, '--device', device_id]\n    proc = subprocess.Popen(deployment_cmd, env=edge_env, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n    output, errors = proc.communicate(timeout=120)\n    if proc.returncode == 0:\n        return {'status': 'deployed', 'device': device_id, 'output': output.decode()}\n    else:\n        return {'status': 'failed', 'error': errors.decode()}"}