---
title: 'PyPitfall: Mapping Vulnerable Dependencies in PyPI'
url: https://www.emergentmind.com/topics/pypitfall
type: topic
---

# PyPitfall: Mapping Vulnerable Dependencies in PyPI

Searching arXiv for the PyPitfall paper and closely related PyPI supply-chain security work.
arXiv search query: `PyPitfall Python dependency chaos software supply chain vulnerabilities in Python`
PyPitfall is a large-scale empirical analysis framework for the Python Package Index (PyPI) that quantifies how known vulnerabilities propagate through direct and transitive dependencies. It studies dependency metadata across the PyPI ecosystem, maps version constraints to a curated set of CVE-identified vulnerable releases, and classifies exposure as either “Guaranteed,” when every permitted version is vulnerable, or “Potential,” when at least one permitted version is vulnerable [2507.18075]. The framework is motivated by the fact that Python software development heavily relies on third-party packages, while direct and transitive dependencies create a software supply chain in which vulnerabilities can propagate downstream. In the reported analysis, 378,573 PyPI packages were resolved, yielding 4,655 packages that explicitly require at least one known-vulnerable version and 141,044 packages that permit vulnerable versions within specified ranges [2507.18075].

## 1. Definition, scope, and analytical objectives

PyPitfall is defined as a quantitative analysis of vulnerable dependencies across the PyPI ecosystem [2507.18075]. Its stated objectives are to crawl and resolve the dependency metadata of all publicly available PyPI packages, map these dependencies against a curated set of known vulnerabilities in Python libraries, classify exposures as “Guaranteed” or “Potential,” and produce ecosystem-wide statistics, case studies, and recommendations for improving Python software supply-chain security [2507.18075].

The study is confined to dependency-mediated exposure. It does not treat all package risk as equivalent. A dependency may be vulnerable because its allowed version set overlaps a known vulnerable range, even when the parent package itself is not directly vulnerable in isolation. This distinction is central to the framework’s emphasis on transitive dependencies, which may occur at arbitrary depth and are resolved by pip’s recursive solver [2507.18075].

A common misunderstanding is to equate all supply-chain risk in PyPI with the same phenomenon. PyPitfall concerns known vulnerabilities in dependency version ranges, whereas other work on the PyPI ecosystem studies intentionally malicious packages and their lifecycle, including install-time, import-time, and run-time attack vectors [2309.11021]. These are adjacent but distinct problem classes.

## 2. Dependency model and exposure semantics

In PyPI’s packaging model, a package $P$ publishes requirement specifiers on other packages. A direct dependency of $P$ is any package $D$ explicitly listed in $P$’s metadata under PEP 508, whereas a transitive dependency of $P$ is any package $T$ reachable through a dependency chain
$$
P \to D_1 \to D_2 \to \cdots \to T.
$$
PyPitfall models the resolved ecosystem as a directed graph
$$
G=(V,E),
$$
where $V$ is the set of unique packages encountered and $(u,v)\in E$ indicates that $u$ declares $v$ as a dependency [2507.18075].

Version admissibility is defined using PEP 440 specifiers. Exact matches such as `==1.2.3`, inequalities and ranges such as `>=1.0,<2.0`, and wildcards such as `~=1.4` define allowed version sets $S\subseteq\mathbb{V}$ for each dependency [2507.18075]. For each vulnerable package $D$, PyPitfall records a vulnerability set $V_D\subseteq\mathbb{V}(D)$ described by unions of version ranges, for example `<1.3.0` or `(\geq 2.0.0 \land <2.0.6)\lor(<1.26.17)` [2507.18075].

Exposure is then computed by intersecting a parent package’s allowed version set with the vulnerable set:
$$
I=S\cap V_D.
$$
The classification rule is exact. There is **Guaranteed Exposure** if $S\subseteq V_D$, meaning no non-vulnerable version is permitted. There is **Potential Exposure** if $S\cap V_D\neq\emptyset$ but $S\not\subseteq V_D$, meaning at least one vulnerable version is allowed but not exclusively so [2507.18075].

This distinction is analytically important. Guaranteed exposure corresponds to a resolver outcome space in which every admissible choice is vulnerable. Potential exposure corresponds to a mixed admissible set containing both vulnerable and non-vulnerable releases. A plausible implication is that the latter category is more sensitive to resolver behavior, environment, and release-time package availability, whereas the former reflects a stronger metadata-level commitment to vulnerable versions.

## 3. Data collection and computational methodology

The study retrieved 616,266 PyPI package names out of 627,810 unique names claimed and successfully resolved dependency metadata for 378,573 packages, corresponding to 60.3% of the retrieved set [2507.18075]. Collection was performed through continuous dry-run resolution via Johnnydep over approximately 17 days. Metadata was obtained from the PyPI Simple Index under PEP 503 and pip’s backtracking resolver, accessed through Johnnydep [2507.18075].

The vulnerability corpus consisted of 67 CVE entries affecting third-party Python packages, collected manually from NVD and MITRE, with the built-in standard library excluded [2507.18075]. Comparison between dependency constraints and vulnerable version ranges was performed by iteratively parsing JSON-encoded dependency trees with an iterative DFS and using Python’s `packaging` library to interpret and compare version specifiers [2507.18075].

PyPitfall’s scalable pipeline rests on three implementation choices: parallelized dry-run dependency resolution through Johnnydep, memory-efficient iterative JSON parsing through `ijson`, and PEP 440–compliant version specifier resolution [2507.18075]. These choices support an ecosystem-wide mapping from dependency reachability to vulnerability exposure.

The study also defines reachability and a package-level exposure measure. For each node $p$, the transitive dependency set is derived from the reachability relation $R(p)$. The vulnerability-aware subset is
$$
R_v(p)=\{\,d\in R(p)\mid d\in \text{VulnerablePackages}\,\},
$$
and the end-to-end map of exposure is summarized through
$$
\mathrm{RiskScore}(p)=\frac{|R_v(p)\cap \mathrm{Guaranteed}|}{|R(p)|}
\quad \text{or} \quad
\frac{|R_v(p)\cap \mathrm{Potential}|}{|R(p)|}.
$$
This quantifies the fraction of a package’s total dependency footprint that is vulnerable [2507.18075].

## 4. Ecosystem-scale measurements

The resolved subgraph contains 57,767 nodes. Across the analyzed packages, the number of dependency edges (non-unique transitive nodes) is 47,974,375, the average direct dependencies per top-level package is 2.6, the average number of non-unique transitive dependencies is 129.6, and the average depth of dependency chains is 2.3 levels, with a maximum observed depth of 23 [2507.18075]. The analysis also detected 1,075,559 unique circular dependency occurrences, with mean circular depth 10.3 [2507.18075].

The exposure counts are the central empirical result.

| Metric | Value | Note |
|---|---:|---|
| Resolved packages | 378,573 | 60.3% of retrieved packages |
| Guaranteed exposures | 4,655 | Approximately 1.23% |
| Potential exposures | 141,044 | Approximately 37.27% |

These numbers indicate that Guaranteed exposures are relatively rare in percentage terms, while Potential exposures are widespread [2507.18075]. This suggests that much of the ecosystem’s supply-chain risk is encoded not as hard pins to vulnerable releases, but as permissive version ranges that still admit vulnerable versions.

PyPitfall also reports graph metrics specific to vulnerability spread. The average depth of guaranteed exposures is 4.1, whereas the average depth of potential exposures is 6.2 [2507.18075]. The effective vulnerability propagation diameter, defined as the longest vulnerability path observed, is 17 for guaranteed exposures, and no guaranteed exposure is observed beyond depth 17 [2507.18075]. Packages such as `setuptools` and `urllib3` have breadth factors exceeding 300,000 occurrences across all dependency trees [2507.18075]. These breadth values identify a small number of highly reused packages as dominant amplifiers of ecosystem-wide exposure.

## 5. Transitive propagation and the urllib3 case study

The study emphasizes that vulnerability propagation is not confined to immediate dependencies. Aggregating over top-level packages, a single widely used vulnerable package such as `urllib3` can appear in over 407,000 distinct dependency paths and can reach depths up to 22 [2507.18075]. This is the clearest demonstration of the transitive nature of the problem: a package may inherit exposure through deep dependency chains without directly naming the vulnerable package.

The `urllib3` case study is quantitatively specific. PyPitfall records 407,333 `urllib3` occurrences in dependencies and associates `urllib3` with the curated CVEs CVE-2020-7212, CVE-2021-28363, CVE-2023-43804, CVE-2023-45803, and CVE-2024-37891 [2507.18075]. The study identifies 1,906 top-level packages with guaranteed exposures to `urllib3`, representing 41.4% of all guaranteed exposures, and 100,213 top-level packages with potential exposures, representing 71.1% of all potential exposures [2507.18075].

The accompanying visualization is described as showing multiple popular packages, including `requests` and `boto3`, propagating `urllib3` vulnerabilities deep into the ecosystem [2507.18075]. This does not imply that these upstream packages are malicious or intrinsically defective; rather, it illustrates how central libraries can function as transmission hubs for downstream vulnerability exposure when version constraints remain permissive or outdated.

A common misconception is that transitive risk is a secondary issue compared with direct dependency hygiene. The reported depth, breadth, and path-count statistics indicate the opposite: transitive propagation is structurally central to ecosystem-wide exposure [2507.18075].

## 6. Mitigations, infrastructure implications, and relation to adjacent Python ecosystem risks

The recommendations are directed at maintainers, users, repository infrastructure, and dependency resolution tooling. For package maintainers and users, the study recommends stricter version pinning to non-vulnerable versions via exact (`==`) or narrow range specifiers so that $S\cap V=\emptyset$, automated security scans in CI/CD using tools such as `pip-audit` or `in-toto`, and regular dependency reviews against updated vulnerability databases [2507.18075].

For PyPI infrastructure and resolver behavior, the recommendations are to extend the PyPI Simple Index to expose vulnerability advisories, enhance pip’s resolver to emit warnings or errors when a final candidate set contains guaranteed or potential exposures, and support advisory-driven resolution strategies that automatically avoid vulnerable version ranges unless explicitly overridden [2507.18075]. These proposals target the metadata and resolution layers rather than only post hoc auditing.

Within the broader Python ecosystem, PyPitfall complements rather than replaces other security analyses. Work on malicious code in PyPI focuses on disguised malicious packages, multi-behavior malware, source-install attack success, and persistence on mirror servers [2309.11021]. PyPitfall instead addresses a different failure mode: ordinary package metadata that permits or requires known-vulnerable versions [2507.18075]. Both are supply-chain problems, but one is primarily about malicious artifact publication and distribution, while the other is about vulnerability propagation through dependency constraints.

A further distinction can be drawn from Python testing research. Flaky tests in PyPI projects are defined by nondeterministic PASS/FAIL or ERROR outcomes under unchanged code and are dominated by order dependency and infrastructure causes [2101.09077]. That phenomenon concerns test reliability, not dependency vulnerability propagation. The comparison is useful because it shows that “pitfalls” in the Python ecosystem span orthogonal layers: testing nondeterminism, malicious package publication, and dependency-mediated exposure are separate empirical domains with different methodologies and mitigations.

PyPitfall’s principal significance lies in making dependency chaos measurable. By constructing $G=(V,E)$, computing reachability, and classifying exposures through the relations
$$
\text{Guaranteed if } S\subseteq V,\qquad
\text{Potential if } S\cap V\neq\emptyset,
$$
it provides an ecosystem-level account of how vulnerable version allowances are embedded in Python package metadata and how those allowances propagate through transitive dependency structures [2507.18075].

Source: https://www.emergentmind.com/topics/pypitfall