Modern software development has degraded engineers into package composers. Faced with a trivial problem, the default reflex across the industry is no longer to open an editor and write thirty lines of standard-library code, but to query a public registry and pull a stranger’s module into the build graph. For over a decade, engineering culture celebrated this reflex as productivity. Why write an HTTP wrapper, a file-system walker, a console colorizer, or a date formatter when someone else already published one? That convenience rested on a fatal accounting error. Teams treated the upfront cost of writing code as high while pretending the ongoing carrying cost of a third-party dependency was zero. Every single entry in a package manifest is an uncollateralized loan taken out against your system’s security, stability, and long-term survival.

Look past the top-level manifest into the transitive graph. In their landmark USENIX Security study of the npm ecosystem, Markus Zimmermann and his colleagues analyzed more than five million package versions and found that while the average package declares just 2.8 direct dependencies, installing it pulls in an average of 79 transitive packages and forces implicit trust in 39 separate maintainers. Worse, the graph is so heavily centralized around tiny utility libraries that compromising just 20 maintainer accounts is enough to inject malicious code into more than half of all packages in the entire registry. Walk into almost any production TypeScript or Python repository today and you will find 800 to 1,500 resolved packages sitting in the lockfile. Industry telemetry consistently shows that between 80% and 95% of open-source vulnerabilities hide inside those transitive dependencies rather than the top-level libraries a team consciously chose. Audit fatigue sets in immediately. Nobody reads the source diffs across a thousand transitive packages on a routine version bump, which means nobody actually knows what code is executing inside their own production environment.

You do not have to look at obscure hobbyist projects to see how absurd this culture has become. Early in February 2026, I audited a batch of mainstream React Native ecosystem releases to see what was actually riding along inside the dependency trees of libraries used by thousands of commercial mobile apps. Take react-native-screens, an essential library that binds native iOS and Android navigation containers and saves teams months of platform-specific plumbing. Inspect its transitive dependencies and you immediately hit react-freeze, a 30-line React Suspense wrapper last published two years earlier, alongside warn-once, a four-year-old package whose entire implementation is a single JavaScript Set that remembers whether a console warning has already been logged in development mode. A team capable of engineering cross-platform native screen containers somehow decided it could not inline a thirty-line wrapper or instantiate a native Set without pulling two external packages into every downstream consumer’s lockfile.

In that same audit, I looked at detox, the end-to-end gray-box mobile testing framework built by Wix. Wix is a serious engineering organization. Yet detox pulls in find-up simply to walk up parent directories looking for a configuration file. Installing find-up drags in locate-path, which depends on p-locate, which depends on p-limit, which pulls in yocto-queue. To answer a sequential, four-step question that Node.js solves in fifteen lines of synchronous fs.existsSync and path.dirname calls, the dependency tree imports generic Promise concurrency limiters, asynchronous iterable locators, and a custom linked-list queue implementation. Across the wider Node ecosystem, packages routinely pull in is-stream just to check whether a value is a non-null object with a .pipe function. Ticket-closing developers shrug at this bloat because npm install finishes in three seconds, and they never have to sit across the table from independent security auditors asking why an enterprise codebase trusts forty strangers to check a boolean condition or traverse four folders.

Attackers understand the economics of transitive trust far better than the developers defending it. Because package managers like npm execute lifecycle hooks (preinstall, install, and postinstall) with the full operating-system privileges of the developer or CI runner by default, every dependency is an implicit remote code execution vector waiting for a single phished maintainer credential. We have had ten years of escalating warnings. In 2016, an unpublished eleven-line string padding function (left-pad) broke global builds overnight. In 2018, a burned-out maintainer handed event-stream (CVE-2018-16487) to a polite stranger who injected a targeted Bitcoin wallet stealer via flatmap-stream. In December 2021, Log4j2 (CVE-2021-44228) proved that an unauthenticated remote code execution flaw buried five levels deep in a Maven dependency tree could bring the global enterprise software industry to its knees. What did the software industry learn from Log4j2? Nothing.

By late 2025 and 2026, opportunistic account takeovers matured into automated, self-propagating supply-chain worms and state-sponsored surgical strikes. In September 2025, a single phished maintainer account compromised chalk, debug, and ansi-styles (packages totaling more than two billion weekly downloads), landing malicious code inside 10% of all cloud environments within two hours, followed days later by the self-replicating Shai-Hulud worm, which used postinstall hooks to run trufflehog across victim machines, harvest ~/.npmrc publishing tokens, and automatically backdoor every package owned by infected maintainers. On March 31, 2026, threat actors compromised the workstation of axios lead maintainer Jason Saayman through a targeted social-engineering campaign and a remote access trojan. Rather than touching axios’s own source code, the attackers published [email protected] and [email protected] (over 100 million weekly downloads) with a single added line in package.json pointing to [email protected]. On install, its postinstall script executed a four-kilobyte XOR-obfuscated dropper (setup.js) that fingerprinted the host, pulled down a second-stage Mach-O, PowerShell, or Linux payload, spawned it detached from the Node process tree, deleted setup.js, and rewrote its own package.json into a clean stub so post-compromise inspection of node_modules revealed nothing. By August 2026, the ChainDrop worm hit [email protected] (154 million weekly downloads) and weaponized stolen CI tokens to trojanize 2,234 package artifacts across 444 packages in real time.

The exact same pathology infects modern CI/CD pipelines and the Python ecosystem. Instead of writing deterministic build scripts, teams assemble GitHub Actions workflows out of dozens of unvetted third-party Marketplace actions referenced by mutable Git tags like @v3 or @main. A Git tag is not an immutable cryptographic digest; an attacker who steals a maintainer token can force-push every historical tag to a malicious commit in seconds. In March 2025, attackers did exactly that to tj-actions/changed-files (CVE-2025-30066), repointing every version tag from v1 through v45 to a script that dumped Runner.Worker process memory and printed CI secrets straight into public build logs across more than 23,000 repositories.

One year later, on March 19, 2026, the TeamPCP threat group repeated that playbook at a massive scale by force-pushing 76 of 77 tags in aquasecurity/trivy-action and poisoning trivy v0.69.4 to scrape runner memory and credentials. Because the flagship Python LLM router litellm (over 95 million monthly downloads) ran unpinned Trivy scans inside its CI pipeline, TeamPCP stole its PyPI publishing token and published backdoored versions 1.82.7 and 1.82.8 straight to PyPI on March 24. In litellm==1.82.8, the attackers didn’t even need a victim application to import the library. They dropped a 34-kilobyte file named litellm_init.pth at the root of the wheel package, abusing a documented legacy behavior in CPython’s built-in site.py module where any line in a .pth file starting with import is passed directly to exec() during interpreter startup. Merely running pytest, invoking pip list, or opening the folder in an editor with a Python language server active triggered the payload before a single line of application code ran. The malware harvested SSH keys, cloud credentials, .env files, and Kubernetes configurations, encrypted them with AES-256 and RSA-4096 into tpcp.tar.gz, installed a persistent sysmon.py user systemd service, and spawned privileged pods across every node in reachable Kubernetes clusters. Only a coding bug inside the malware itself exposed the breach within three hours: the authors forgot a re-entrancy guard when spawning child Python processes, accidentally triggering a recursive fork bomb on test machines. Had the attackers included a simple three-line re-entrancy check, that backdoor would have silently drained production AI infrastructure for weeks.

Malware is only half the story; heavy dependency chains also turn everyday software architecture into a house of cards. In April 2026, Flutter developers woke up to discover that GetX (get), the single most-liked package on pub.dev, had abruptly vanished from GitHub. For years, thousands of teams ignored Flutter’s native widget lifecycle and built-in primitives in favor of GetX because it promised an all-in-one shortcut for state management, route navigation, dependency injection, and networking. When a foundational repository disappears overnight—or when a maintainer abandons a library, changes its license, or gets phished—every team that coupled its core architecture to a third-party convenience layer discovers who actually owns their product. I realized the same thing years ago looking at Nest.js backends buried under dozens of thin @nestjs/* wrapper packages that could be rewritten cleanly in an afternoon, or SwiftUI codebases importing third-party service locators when native protocol programming and @Environment injection already solve dependency management without a single external library.

Why do so many engineers keep building fragile, dependency-laden systems and over-engineered CI/CD pipelines despite a decade of catastrophic breaches? Follow the institutional incentives. Across nearly every client codebase I have been brought in to audit or rescue, the mess was not an accident. It was a business model. By assembling an opaque labyrinth of niche frameworks, unvetted transitive packages, custom build patches, and twenty-step GitHub Actions workflows where a thirty-line Makefile and a POSIX Bash script would suffice, a small group of self-styled architects manufactures artificial dependency on themselves. They create a bus factor of one, holding the organization hostage to their personal opinions and undocumented trade secrets. It is employment through complication, a quiet scam dressed up as modern engineering sophistication. Real engineering discipline runs in the exact opposite direction. When I build a Next.js or Flutter system for a client, I stick strictly to official framework defaults, keep the dependency list ruthlessly minimal, and wire deployments through dead-simple scripts that run identically on a local workstation or a self-hosted Linux runner. The bus factor stays at zero. Any competent senior engineer can clone the repository, drop secrets into .env.local, and become productive in under an hour.

In 2026, the last remaining excuse for micro-dependency sprawl has evaporated. For fifteen years, developers justified npm install with a simple economic trade-off: typing out custom utilities and unit tests by hand took hours, whereas importing a community package took seconds. AI coding agents governed by strict architectural rules have permanently inverted that math. Code is now a cheap commodity, while third-party trust has never been more expensive. When I needed a fast RSS and Atom feed parser in Go, or a complete backend and CLI for network speed testing, I built both with zero third-party dependencies using only Go’s standard library. When Wix’s detox needs to walk four parent directories, you do not need a five-package dependency chain; you instruct your coding agent to write a fifteen-line fs.readdirSync or fs.existsSync helper with exhaustive unit tests, and you commit it directly into your repository. Even for heavier problems, the industry is beginning to wake up: when Obsidian’s team released Pele in October 2026, a zero-dependency Mermaid diagram renderer running 100 to 500 times faster and six times smaller than mermaid.js, they proved that stripping away the dependency graph makes software dramatically faster, smaller, and safer at the same time.

The economic value of random convenience dependencies is collapsing toward zero, and organizations should start treating every external package in their lockfile as an active liability. A third-party utility only ever had economic value equal to the engineering hours it saved minus the ongoing cost of carrying it. When a general-purpose library has to serve ten thousand different consumers, it inevitably accumulates configuration flags, legacy polyfills, defensive abstractions, and sub-dependencies for edge cases your application will never encounter. Pulling that library into your project forces you to inherit 100% of its attack surface and maintenance churn just to use 5% of its code. With an AI coding agent and well-structured repository rules, generating a bespoke implementation against your language’s standard library takes minutes, targets your exact domain constraints, and ships with deterministic unit tests that live inside your own tree. Once committed, that code stays inert. It does not wake up at 3 AM with a breaking major-version bump, it does not unexpectedly change its open-source license to a commercial tier, and it does not depend on whether an unpaid maintainer in another time zone clicked a phishing link.

Recognizing this shift requires a hard cultural reset inside engineering teams. Adding a package to package.json, pyproject.toml, or pubspec.yaml should never be the path of least resistance; it should require the same architectural justification as opening a network port or granting production database access. If a developer cannot implement a basic algorithm, parse a standard data format, or traverse a directory tree without reaching for an external registry—and refuses to adapt even now that AI has reduced the implementation cost to near zero—they do not belong near a production codebase. Reserve third-party libraries for genuine, high-complexity domains where building in-house is irrational, such as cryptographic primitives, database drivers, or native OS bindings, and when you do bring one in, vet its maintainer history, audit every single node in its transitive tree, pin its cryptographic hashes, and strip external action sprawl out of your build pipelines in favor of deterministic local scripts.

Every dependency you refuse to install is a maintainer who cannot be phished on your behalf, a .pth hook or postinstall worm that cannot execute on your runners, and a piece of your own architecture that nobody can take away from you. Software engineering is supposed to be the discipline of building durable systems you actually understand and control, rather than gluing together opaque boxes written by strangers and praying the registry holds together until the next quarter. Strip out the middlemen, return to the standard library, and reclaim full accountability for every instruction executing on your machines. Trust nothing and nobody, verify everything, and own your code.