Almost no modern software is written from scratch. You write a thin layer of your own code on top of hundreds of packages other people wrote — pulled from a registry, pinned by a lockfile, and shipped as part of your product. That dependency graph is enormous leverage and a real risk surface at the same time, and it's one an AI agent now reaches into every time it runs install.
A package is a reusable chunk of code someone published so others don't have to rewrite it — a date library, an HTTP client, a UI framework. A registry is the public warehouse those packages live in. A package manager (npm, pip, cargo) is the tool that downloads the ones you ask for, plus everything they depend on, and so on down the tree.
That last part is the whole story. You add one dependency; it pulls in five; each of those pulls in more. The result is a dependency graph that can run to hundreds or thousands of packages from people you've never heard of — and all of it ships inside your product and runs with your product's permissions. You wrote maybe 5% of the code your users run. The other 95% is the graph.
Two files describe your dependencies. The manifest (package.json, pyproject.toml, Cargo.toml) lists what you asked for, usually as version ranges. The lockfile (package-lock.json, poetry.lock, Cargo.lock) records the exact version of every package — direct and transitive — that was actually installed. Commit both. The manifest is intent; the lockfile is the reproducible reality.
Each language has its dominant registry and manager. They differ in size and culture, but the model is the same everywhere.
| Ecosystem | Registry · manager | Notes |
|---|---|---|
| JavaScript / TS | npm · npm / pnpm / bun | The largest registry on earth (millions of packages). Huge leverage, huge surface — tiny packages and deep trees are the norm. |
| Python | PyPI · pip / uv / poetry | The default for data, ML, and scripting. uv is the fast modern installer; lockfiles via poetry/uv. |
| Rust | crates.io · cargo | Best-in-class tooling — cargo handles build, test, and deps as one. Strong culture of small, well-audited crates. |
| Java / JVM | Maven Central · Maven / Gradle | Enterprise mainstay. Heavier, but mature security and provenance tooling. |
The pattern repeats for every language (Go modules, RubyGems, NuGet, Packagist). The skill isn't memorising commands — it's understanding that every ecosystem gives you the same deal: enormous reuse in exchange for trusting a lot of strangers' code, mediated by versioning and lockfiles.
Semantic versioning (SemVer) is the convention that makes the whole system tractable. A version is MAJOR.MINOR.PATCH — bump patch for a bug fix, minor for a backwards-compatible feature, major for a breaking change. It's a promise from the author to you: "upgrading within the same major version won't break you." Your manifest leans on that promise by allowing ranges (e.g. "any 4.x").
Promises get broken, which is why the lockfile exists. It pins the exact resolved versions so that install produces an identical dependency tree on your machine, your colleague's, CI, and production — today and in six months. Without a committed lockfile, "it worked yesterday" and "it works on my machine" become genuine, unanswerable mysteries. With one, an install is reproducible, and reproducibility is the foundation everything else (debugging, audit, rollback) stands on.
The healthy rhythm: commit the lockfile so installs are reproducible, then update dependencies on purpose — on a branch, through CI, reviewed in a PR — rather than letting them drift. Tools like Dependabot/Renovate open those update PRs automatically, so upgrades become small, tested, reviewable changes instead of a scary big-bang. Same discipline as everything else in this branch: change deliberately, gate it, keep it reversible.
The flip side of all that reuse: every package in your graph is code running with your product's trust. If one is malicious or compromised, so is your product. This is the software supply chain, and it's a live attack surface.
A malicious package named to look like a popular one (reqests for requests). Install the wrong name and you've run someone's payload.
A legitimate, widely-used package gets a malicious release after its maintainer's account is hijacked. Your auto-update pulls it in.
The dangerous package is rarely the one you chose — it's five levels down, a dependency of a dependency you never evaluated.
A package that's no longer maintained accumulates known vulnerabilities with no fixes coming. Stale is its own risk.
The defences are practical and mostly free: commit lockfiles (so a surprise version can't sneak in); run automated vulnerability scanning (npm audit, GitHub Dependabot alerts, pip-audit); prefer fewer, well-maintained dependencies over many obscure ones; review what an update actually changes before merging it; and for regulated work, generate an SBOM (software bill of materials) so you can answer "what's actually in our product?" when — not if — the next big vulnerability lands.
A coding agent building a feature does what a developer does: it reaches for packages and runs install. That's a real expansion of the supply-chain surface — an agent can pull in a dependency no human explicitly chose, and it can be fooled by a plausible-but-wrong package name just as a human can (arguably more so, since it's optimising for "something that works" at speed).
The mitigations are the ones this whole branch keeps returning to. The agent's work lands as a pull request, so a human sees exactly which dependencies it added before anything ships. CI runs the audit on every change, flagging known-vulnerable packages automatically. The lockfile diff in the PR makes new dependencies visible and reviewable. And for untrusted execution, the agent runs in a sandbox with restricted network and filesystem access, so a malicious install can't reach your secrets or your network. Agent autonomy over dependencies is safe for exactly the same reason agent autonomy over code is: the gate, not the trust.
The single highest-value habit when an agent (or anyone) adds a dependency: actually look at the lockfile diff in the PR. It shows every package — direct and transitive — that the change brings in. A feature that should need one small library but adds forty packages is a signal worth pausing on, whether a human or an agent wrote it.
On bandwidth that's expensive or intermittent, reproducible installs matter twice over: a committed lockfile plus a CI cache means builds don't re-download the world on every run, and a clean checkout produces an identical, known-good dependency tree even months later. For regulated SA work, the lockfile and an SBOM are also the evidence base for "what third-party code is in the system touching this data?" — the question POPIA and security reviews eventually ask. Pinned, scanned dependencies turn that from a scramble into a lookup.