# Dependency Confusion via Unpinned pip Package Installation

Language: Python
Severity: Critical
CWE: CWE-829

## Source
3

## Flow
3-4-5-6-7-8-10

## Sink
8, 10

## Vulnerable Code
```python
import subprocess
import os

def bootstrap_ml_inference_environment(model_type):
    required_libs = ['tensorflow', 'keras-preprocessing', 'scikit-learn', 'numpy']
    if model_type == 'vision':
        required_libs.extend(['opencv-python', 'pillow-simd'])
    for package in required_libs:
        subprocess.run(['pip', 'install', package, '--upgrade'], check=False)
    subprocess.run(['pip', 'install', 'company-ml-utils'], check=False)
    os.environ['MODEL_READY'] = '1'
    return True
```

## Explanation

The function installs ML packages without version pinning or integrity verification via subprocess.run(['pip', 'install', package, '--upgrade']). This enables dependency confusion attacks where a malicious package with the same name as an internal package (e.g., 'company-ml-utils') can be published to a public repository with a higher version number and automatically installed instead of the intended private package. Additionally, the absence of version constraints on all dependencies means any compromised or malicious version available on PyPI could be installed at runtime.

## Remediation

The fix pins all dependencies to exact versions preventing arbitrary version upgrades, validates model_type against an allowlist to prevent misuse, uses a private index URL for internal packages to prevent dependency confusion, adds --no-deps to prevent transitive dependency attacks, and sets check=True with shell=False for safer subprocess execution.

## Secure Code
```python
import subprocess
import os

PINNED_PACKAGES = {
    'tensorflow': 'tensorflow==2.15.0',
    'keras-preprocessing': 'keras-preprocessing==1.1.2',
    'scikit-learn': 'scikit-learn==1.3.2',
    'numpy': 'numpy==1.26.2',
    'opencv-python': 'opencv-python==4.8.1.78',
    'pillow-simd': 'pillow-simd==9.5.0.post1'
}

PRIVATE_INDEX_URL = os.environ.get('PRIVATE_PYPI_URL', 'https://private.pypi.company.com/simple/')

ALLOWED_MODEL_TYPES = {'vision', 'nlp', 'tabular', 'default'}

def bootstrap_ml_inference_environment(model_type):
    if model_type not in ALLOWED_MODEL_TYPES:
        raise ValueError(f"Invalid model_type: {model_type!r}. Must be one of {ALLOWED_MODEL_TYPES}")

    required_libs = ['tensorflow', 'keras-preprocessing', 'scikit-learn', 'numpy']
    if model_type == 'vision':
        required_libs.extend(['opencv-python', 'pillow-simd'])

    for package in required_libs:
        pinned = PINNED_PACKAGES.get(package)
        if pinned is None:
            raise ValueError(f"Unknown package requested: {package!r}")
        subprocess.run(
            ['pip', 'install', '--no-deps', pinned],
            check=True,
            shell=False
        )

    subprocess.run(
        ['pip', 'install', '--no-deps', '--index-url', PRIVATE_INDEX_URL, 'company-ml-utils==1.0.0'],
        check=True,
        shell=False
    )

    os.environ['MODEL_READY'] = '1'
    return True
```
