A library published to both npm and PyPI looks like one dependency wearing two formats. Once installed it is two artifacts, with separate release histories, separate metadata declaring which machines and interpreters each can run on, and separate dependency trees resolved by separate clients. The best current map of how such projects are built is a preprint, “Cross-Ecosystem Packages As Multilingual: Prevalence, Architecture, and Health” (arXiv:2609.14983), and its most useful lesson for anyone auditing dependencies is negative: knowing that two registry packages share a repository, or even a source file, establishes nothing by itself about whether either copy is current, compatible, or safe. The paper’s own framing pushes you toward that caution. Its abstract opens with packages “published to multiple package ecosystems,” naming npm for JavaScript and PyPI for Python as its example, and notes that “Little is known about cross-ecosystem packages, especially regarding how they are structured.”
Shared code is a useful clue, not an artifact guarantee
The paper defines its subject by service, not by shared code. Section 3.2 reads: “we define a cross-ecosystem package as a software project that is published to two or more package registries and provides similar or the same service.” Two packages can satisfy that definition while sharing no source at all. What the authors actually detect is repository linkage: they mine each registry’s package metadata for a GitHub URL (the declared repository or homepage), normalize it to a canonical github.com/owner/repo form, and treat packages pointing at the same repository as candidates. Across six registries (crates.io, Maven Central, npm, Packagist, PyPI and RubyGems) that produced 6,080,775 packages linked to 2,489,326 unique normalized GitHub repository URLs. The gap between those two numbers is itself on theme: many packages across different ecosystems point to one shared repository, and a further subset declares no usable GitHub URL at all, which the authors exclude from cross-ecosystem detection.
For the health analysis the authors collected six GitHub counters per repository: stars, forks and contributors as community metrics, and commits, pull requests and issues as activity metrics. Their description of these numbers is the sentence that should govern every use of the paper: “All six metrics represent lifetime totals at the time of data collection; they are not rate-based and do not reflect the pace of development during any particular time window.”
That sentence rules out the question a shared repository invites: which registry copy falls behind. A cumulative star count cannot tell you whether this month’s security fix reached npm, PyPI, both, or neither, and neither can a cumulative commit total. The paper’s stated analysis is the “relationship between architectural patterns and project health,” measured through those counters; comparing npm releases with PyPI releases is not among its questions. What the study supplies is a count of how common these projects are, a classification of how they are organized, and correlations between organization and accumulated GitHub activity.
One more boundary: this is a preprint. arXiv’s description of its process states that “Material is not peer-reviewed by arXiv” and that submission contents are “wholly the responsibility of the submitter” and presented “as is.” The correlations are author-reported and, as far as this article’s sources show, unreplicated. Treat them as a prior, not a finding.
What the study measures and how it classifies projects
The quantitative spine is a prevalence count. Of the 2,489,326 unique repositories, 38,970 packages are identified as cross-ecosystem, or 1.57%. The authors set that against prior work (Constantinou et al., 2018), which reported 15,389 cross-ecosystem packages, 0.99% of 1,556,300 packages. The share has grown, though the denominators differ in kind, repositories here against packages there, so read the trend as directional rather than exact. npm is the most commonly involved ecosystem, with 25,489 entries, followed by Maven (17,004) and PyPI (8,908).
On structure, the abstract reports five distinct architectural patterns in how these projects arrange their source code. The prevalence tables also cut the same population along a simpler line, repository layout: 21,868 packages (56.1%) use a mono-repo and 17,102 (43.9%) use a multi-repo, with the paper’s validity filter (non-forked, non-archived, non-404) leaving 18,564 and 13,930 valid repositories respectively.
A layout label answers one question well: where does each ecosystem’s source live? A mono-repo pair at least shares an upstream you can read; a multi-repo pair may share nothing but a name and a maintainer. Neither label says anything about the artifacts the registries hold. The health analysis correlates these organizational choices with the GitHub counters, and leans on prior work to interpret them, citing Borges and Valente (2018) for the finding that three out of four developers consider the number of stars before using or contributing to a GitHub project. Stars are a real adoption signal, and they move as slowly as any cumulative counter: a project can hold a large star count and still have published nothing to one registry in a year.
Inspect the JavaScript and Python artifacts separately
Each artifact carries its own compatibility declarations, drawn from different specification worlds.
On the JavaScript side, package.json lets a package declare os (to allow or block platforms, for example ["darwin", "linux"] or ["!win32"]), cpu (for architectures, for example ["x64", "ia32"], with the same blocking syntax), and libc for code that requires a particular C library, a field that only applies when os includes Linux. The host values npm checks against come from process.platform and process.arch. A separate devEngines field constrains the tooling used to develop the package, and the documentation draws a useful distinction with the older engines field: engines is “advisory only and will only produce warnings when your package is installed as a dependency” unless the user sets engine-strict. A declared constraint is not always an enforced one.
The Python artifact answers to different authorities. Core metadata’s Requires-Python “specifies the Python version(s) that the distribution is compatible with,” and, per the specification, “Installation tools may look at this when picking which version of a project to install.” The spec’s own example is a package using f-strings that sets Requires-Python: >=3.6 to block older interpreters. That deserves a second read: the resolver can select a different version of the same project depending on your interpreter. Wheels add platform compatibility tags, which “allow build tools to mark distributions as being compatible with specific platforms, and allows installers to understand which distributions are compatible with the system they are running on,” with interpreter, ABI and platform compatibility expressed separately in the tag format {python tag}-{abi tag}-{platform tag}.
None of this crosses over. The npm manifest knows nothing about Requires-Python; wheel tags know nothing about your cpu field. Shared source does not merge two sets of platform constraints into one, and a version label that matches across registries does not make the installed artifacts equivalent.
Dependency resolution is equally separate. npm’s package-lock.json “describes the exact tree that was generated, such that subsequent installs are able to generate identical trees, regardless of intermediate dependency updates,” and the documentation states the file is intended to be committed into source repositories. Each entry also records an integrity value, described as “a sha512 or sha1 Standard Subresource Integrity string for the artifact that was unpacked in this location,” along with per-package os and cpu arrays, which the lockfile includes whenever the package declares those restrictions. (If you rely on the shrinkwrap variant instead, note that npm v12 stopped honoring it in several places, per Groundy’s v12 upgrade audit.) A lockfile is a strong per-ecosystem snapshot, and only that. Groundy’s coverage of the CHRONO-RESOLUTION dataset measured a lockfile regenerated six weeks apart from an unchanged manifest resolving to different transitive dependency versions. Run two ecosystems, each with its own snapshot mechanism, and you have two independent drift clocks for a project you assumed was synchronized.
A decision table of checks and what each can establish
| What you check | What it establishes | What it cannot establish |
|---|---|---|
| Project architecture (mono-repo vs multi-repo layout; the paper’s five source-code patterns) | Where each ecosystem’s source lives, and how much the copies plausibly share | Compatibility, freshness or security of either artifact; the patterns classify repository organization, and the study’s health questions stop at GitHub counters |
| Each registry’s release history (npm versions, PyPI project files) | What that registry received, and when | Whether the other registry’s copy matches, or why a gap exists; the paper’s questions cover prevalence, structure and health correlations, not cross-registry release comparison |
Runtime and platform declarations (os, cpu, libc, engines; Requires-Python, wheel tags) | Whether the artifact can install and run on a specific machine or interpreter | Anything about the other ecosystem’s artifact; each side declares its constraints independently, and npm’s engines is advisory without engine-strict |
| The resolved dependency tree (npm lockfile with integrity hashes; your Python environment’s resolution) | A reproducible snapshot for that ecosystem at a point in time | Agreement with the other ecosystem; two snapshots can encode different states of what you call one library |
| GitHub stars, forks, contributors, commits, pull requests, issues | Accumulated community reach and activity, per the study’s six counters | Development pace or publication cadence in any window; the authors state these metrics are not rate-based |
The throughline is that no single row settles an upgrade. Architecture tells you where to look; release histories tell you what each registry holds; platform declarations and resolved trees tell you what will actually install. A healthy-looking repository is compatible with every failure mode in the third column.
The remaining unknowns before an upgrade
The quantitative claims rest on a single preprint, moderated for topicality but not peer reviewed, and unreplicated as far as this article’s sources show. The study compares architectural patterns against GitHub counters; its stated questions do not extend to comparing npm releases with PyPI releases, and its health metrics are not security outcomes. “Healthier” on those counters is not the same claim as “safer to install,” and the paper does not make the second claim.
Pins have the same boundary. Groundy’s coverage of the axios npm compromise showed that a locked version guarantees stable bytes, not safe bytes: the attacker’s two poisoned versions stayed installable for roughly three hours, and the responding vendor’s blocks applied only inside its own environment. Locking both artifacts manages drift between them. It does not manage what either maintainer, or an attacker, published.
Practical verdict
Treat the two installed artifacts as separate audit targets. For each one, confirm that the release you want exists on that registry, read its runtime and platform declarations against the machines you actually deploy to, and record its dependency tree with that ecosystem’s own locking mechanism. Use the project’s architecture as a map of where to look, not as a verdict: a mono-repo layout invites checking that every registry artifact is actually built and published from the shared source you can see, while a multi-repo pair invites checking whether fixes propagate between the repositories at all. Shared code is a reason to investigate. It is not a compatibility, freshness, or security guarantee, and nothing in this study claims that it is.

Join the discussion
Share a useful perspective or ask a question about this article.