Skip to content
Back to skills

Package Manager Supply Chain And Workspaces

BSecurity

Use when hardening a package ecosystem against supply-chain attacks or designing build and monorepo features. Covers the 2025–2026 npm worm era, the attack taxonomy (typosquatting, dependency confusion, account takeover, install-time malware), registry-side controls worth building, client-side controls including install cooldowns, lifecycle scripts — the problem, why they exist and how ecosystems escaped them, running them safely — binary distribution design, workspaces, non-registry dependen...

  • 2 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added September 19, 2026
ai-agentspythonrustgonodeexpresskubernetesawsgitapisecurity

Works with

  • cli
  • api

Security analysis

B84/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro shows the line behind each finding and how to fix it

Scanned September 19, 2026

npx -y skills add the-vibey-project/vibey --skill package-manager-supply-chain-and-workspaces --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Package Manager Supply Chain And Workspaces?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Package Manager Supply Chain And Workspaces
[![Security: B — Skills Directory](https://www.skillsdirectory.com/api/skills/the-vibey-project-package-manager-supply-chain-and-workspaces/badge)](https://www.skillsdirectory.com/skills/the-vibey-project-package-manager-supply-chain-and-workspaces)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: package-manager-supply-chain-and-workspaces
description: "Use when hardening a package ecosystem against supply-chain attacks or designing build and monorepo features. Covers the 2025–2026 npm worm era, the attack taxonomy (typosquatting, dependency confusion, account takeover, install-time malware), registry-side controls worth building, client-side controls including install cooldowns, lifecycle scripts — the problem, why they exist and how ecosystems escaped them, running them safely — binary distribution design, workspaces, non-registry dependencies (git, path, URL), and overrides."
---

# Package Manager Development: Supply-Chain Security, Lifecycle Scripts, and Workspaces

> **Part 3 of 5** of the *Package Manager Development* reference (plugin `package-manager-development`), covering §8–§10. Sibling skills: `package-manager-versioning-and-resolution` (§0–§4), `package-manager-registries-and-installation` (§5–§7), `package-manager-ux-ecosystems-and-governance` (§11–§14), `package-manager-reference` (§15–§20). Section numbers are shared across the set; a reference written as §N → `skill` points into that sibling skill.
>
> **Currency:** Verified August 2026. See §17 → `package-manager-reference` for the currency snapshot and what goes stale first.

> **How to read this.** Reference, not tutorial. Sections are independent. Three markers:
> - **[DURABLE]** — computer science, or a lesson every ecosystem has independently learned.
>   Does not expire.
> - **[ECOSYSTEM]** — specific to npm, PyPI, crates.io, Go, etc. Verify against that
>   ecosystem's current docs.
> - **[CONTESTED]** — the ecosystems have made genuinely different, defensible choices, or
>   the practitioners disagree. Both cases given.
>
> **⚠️ GOTCHA** boxes mark the design mistakes that produce unreproducible builds, wedged
> caches, exponential resolutions, or supply-chain compromise.
>
> **The framing that organizes everything below: a package manager is a distributed
> systems problem wearing a CLI.** It is a cache-coherency problem, a constraint-solving
> problem, a trust-and-identity problem, and a compatibility-contract problem, all of
> which happen to be invoked by typing `install`. Every serious difficulty in this
> document is one of those four, and treating it as "just downloading files" is the root
> of most bad designs.

---

## §8. Supply-Chain Security

### 8.1 The 2025–2026 npm worm era — what actually happened, because it changed the design space

**[ECOSYSTEM, but the lessons are universal.]** This is the most important recent case
study in package-manager security, and its details matter.

**Timeline:**
- **September 2025 — Shai-Hulud.** A **self-replicating worm** (named for the workflow file
  `shai-hulud-workflow.yml` it dropped, a *Dune* reference) entered via maintainer
  phishing. It injected malicious **postinstall** scripts, stole credentials, then — the
  novel part — **used the stolen npm token to authenticate as the compromised developer,
  enumerate their other packages, inject itself, and publish new versions.** Exponential
  spread with no attacker involvement. It also created public GitHub repos named
  "Shai-Hulud" containing the victim's exfiltrated secrets. GitHub removed 500+ compromised
  packages and blocked uploads matching the malware's IoCs.
- **November 2025 — Shai-Hulud 2.0.** Moved from **postinstall to preinstall**, widening
  impact to run before installation even completed.
- **2026 — continued waves**: "The Third Coming" (April), "Mini Shai-Hulud" (April–May,
  attributed to TeamPCP), and **August 2026's "ChainDrop"** — a Mini Shai-Hulud variant
  that compromised **`keyv`, `flat-cache`, `cache-manager` and 400+ packages across
  multiple unrelated publishers, ~2 billion monthly installs, within hours.** Microsoft's
  analysis describes a heavily obfuscated **Bun-based** payload executing via npm
  `preinstall`, harvesting npm, GitHub, AWS, Kubernetes, and HashiCorp Vault credentials.
- **The tradecraft shift that matters most:** earlier waves published using stolen tokens.
  **The 2026 waves increasingly ride the victim project's own release workflow** — pushing
  to `main` and cutting a release through legitimate CI. JFrog documented one sample that,
  inside a *specific* GitHub Actions run, requested an Actions OIDC token with audience
  `npm:registry.npmjs.org` — i.e. **abusing trusted publishing itself.**
- Parallel campaigns hit PyPI: **LiteLLM 1.82.7/1.82.8 (24 March 2026, ~95M monthly
  downloads) and telnyx 4.87.1/4.87.2**, part of a chain that began with a compromise of
  the **Trivy GitHub Action**. The LiteLLM payload used a **`.pth` file**, which
  auto-executes on *every interpreter start* — no import required.

**Scale context**: ReversingLabs measured a **73% year-over-year increase in malicious
open-source packages**, with npm accounting for roughly 90% of open-source malware;
Sonatype tracked 512,847 malicious packages in a single year.

**[DURABLE] The five design lessons:**
1. **Lifecycle scripts are the primary payload delivery mechanism.** See §9.
2. **A publishing credential is a lateral-movement primitive**, not just a secret. Any
   design where one credential can publish many packages is a worm substrate.
3. **Signing and provenance are necessary but not sufficient.** A worm that publishes
   through the victim's real CI earns a *legitimate* attestation. The VentureBeat framing
   is exact: the worm "didn't fake its security check — it earned a legitimate one."
4. **Detection speed is now a design parameter.** Socket reported average detection at
   ~5 minutes 18 seconds after publication during the August wave — and hundreds of
   packages still propagated.
5. **Containment beats prevention.** The controls that actually limited damage were
   short-lived credentials, script execution defaults, cooldowns, and blast-radius limits —
   not detection.

**npm's response** (worth knowing as the reference remediation): mandatory 2FA for
publishing, **revocation of legacy never-expiring tokens**, granular access tokens with
short expiry, and trusted publishing so build systems push without stored credentials.

### 8.2 The attack taxonomy

| Attack | Mechanism | Defense |
|---|---|---|
| **Typosquatting** | `reqeusts`, `python-dateutil` vs `dateutil` | Name-similarity checks at publish, install-time warnings |
| **Dependency confusion** | Public package shadows a private one of the same name | **Scoped/namespaced private packages; never let a public index satisfy an internal name.** Configure per-registry scope pinning |
| **Maintainer account takeover** | Phishing, credential stuffing | Mandatory 2FA/WebAuthn, trusted publishing |
| **Token theft** | Exfiltrated from CI, dotfiles, a compromised machine | Short-lived OIDC credentials; no long-lived tokens |
| **Self-replicating worm** | Stolen token → publish to all your packages | §8.1; per-package publish scoping |
| **Malicious maintainer / hostile handoff** | Legitimate owner turns, or hands the package to an attacker | Ownership-change alerts, cooldowns, code review of updates |
| **Protestware / sabotage** | Author deliberately breaks or wipes | Lockfiles + cooldowns + vendoring for critical paths |
| **Release poisoning** | Add a malicious file to an old, trusted release | §5.3 → `package-manager-registries-and-installation` — PyPI's 14-day rule |
| **Repojacking / name reuse** | Take over an abandoned name or deleted repo | **Never allow name reuse**; verify repo ownership continuously |
| **Compromised build tool** | The CI action itself is backdoored (Trivy, KICS in 2026) | Pin actions **by commit SHA, never by mutable tag** |
| **`.pth` / import-time execution** | Python-specific auto-execution | Restrict what installs may place on `sys.path` |
| **Compromised mirror / MITM** | Substituted bytes | TLS + lockfile hashes + transparency logs |

> **⚠️ GOTCHA — mutable references are a recurring root cause.** The March 2026 LiteLLM and
> Telnyx compromises traced to a **mutable reference in their use of the Trivy GitHub
> Action**. `uses: some/action@v1` is a moving target controlled by someone else. Pin to a
> full commit SHA. This single practice would have prevented multiple 2026 incidents.

### 8.3 Registry-side controls worth building

- **Mandatory 2FA / WebAuthn for publishing**, especially for high-impact packages.
- **Trusted publishing** and deprecation of long-lived tokens (§7.1 → `package-manager-registries-and-installation`).
- **Provenance generation by default**, not opt-in (§7.2 → `package-manager-registries-and-installation`).
- **Quarantine**, not delete — freeze a suspected package pending investigation while
  preserving existing installs.
- **Name-similarity scoring at publish time** and an appeals process.
- **Publish-event alerting to maintainers**, so an unexpected release is noticed in minutes.
- **A public, machine-readable advisory feed** (OSV format is the interoperable standard).
- **Malware scanning at ingest** — imperfect, but the August 2026 detection times show it
  meaningfully compresses exposure windows.
- **Upload timestamps in the API** so clients can implement cooldowns.

### 8.4 Client-side controls — and the one with the best evidence

**Cooldowns / minimum release age are the highest-value new control.** The idea: refuse to
install a version published less than N days ago, so the ecosystem's detection machinery
gets a chance first.

- **pip 26.1 (April 2026) added `--uploaded-prior-to`**; **uv has `--exclude-newer`**;
  **pnpm has `minimum-release-age`**; Dependabot has a cooldown setting. All rely on
  registry-reported upload timestamps (PEP 700 in Python).
- **The reported evidence**: research cited alongside the pip 26.1 release found that a
  **7-day cooldown would have prevented 8 out of 10 analyzed supply-chain attacks from
  reaching end users**. That is an unusually strong result for a control this cheap.
- **The honest limitations**, which the pip team and others state plainly: it does not stop
  a sophisticated attack that evades detection for longer, does nothing about
  vulnerabilities in packages you already depend on, and **delays security patches** — so
  you need an expedite path for genuine fixes.

The rest of the client-side baseline:
- **`--frozen-lockfile` / `ci` mode everywhere in CI.** Never resolve in CI.
- **Disable lifecycle scripts by default** (§9), allowlisting the few that need them.
- **Pin CI actions by SHA.**
- **Generate an SBOM per build** (SPDX or CycloneDX; `npm sbom`, `syft`) — this is what
  turns "are we affected?" from a week into minutes, and is increasingly a legal
  requirement (§14.3 → `package-manager-ux-ecosystems-and-governance`).
- **Verify provenance where available** — prefer packages with attestations, and pin
  *publisher identities* where your tooling supports it.
- **Vendor or mirror** the dependencies of anything you cannot afford to have change.

---

## §9. Lifecycle Scripts and Building

### 9.1 The problem, stated plainly

**[DURABLE] Arbitrary code execution at install time is the original sin of package
management.** `npm install` running `preinstall`/`postinstall`, `pip install` executing
`setup.py`, `gem install` running `extconf.rb` — in every case, *fetching a dependency
executes attacker-controlled code with the developer's full privileges.*

Every major worm in §8.1 used this. Shai-Hulud's move from `postinstall` to `preinstall`
was specifically to execute *before* installation completed, widening the blast radius.

### 9.2 Why it exists, and how ecosystems escaped it

It exists because native code needs compiling and platforms differ. The escapes, in order
of how well they worked:

1. **Prebuilt binary artifacts.** Python's **wheels** are the great success story here: a
   wheel is a zip that is *installed by copying*, with no code execution. The
   `manylinux`/`musllinux`/`macosx` platform tag scheme made prebuilt binaries portable.
   **This single change removed install-time execution from the overwhelming majority of
   Python installs** — and it's why sdists (which do run `setup.py`) are now the risky path.
2. **Declarative build metadata.** `pyproject.toml`'s `[build-system]` replaced executable
   `setup.py` configuration with data. Cargo's `Cargo.toml` was declarative from birth
   (with `build.rs` as a deliberate, visible exception).
3. **Separate build from install.** Go does not execute dependency code at fetch time at
   all — it compiles it as part of *your* build, which is a different and much better trust
   boundary.
4. **Default-off with an allowlist.** pnpm's `onlyBuiltDependencies` is the pragmatic
   modern answer: scripts don't run unless you name the package. npm has `--ignore-scripts`.
   Bun and Yarn have equivalents.

**[DURABLE] If you are designing a package manager today: do not run install-time scripts
by default.** Provide an explicit, per-package allowlist that lives in the manifest and is
reviewable in a diff. The compatibility cost is real (native modules — node-gyp builds,
`sharp`'s prebuilt binaries, Prisma's engine downloads — all use postinstall) and it is
worth paying.

### 9.3 If you must run them

- **Sandbox**: no network, restricted filesystem, no access to credentials or environment
  secrets. Nix and Guix's build sandboxes are the reference implementations.
- **Log everything** the script does, visibly.
- **Never run scripts for transitive dependencies the user didn't name**, at minimum
  without a prompt.
- **Deterministic environment**: fixed `PATH`, no ambient config, no `$HOME` dotfiles.

### 9.4 Binary distribution design

If you ship prebuilt artifacts, you need a **platform tag scheme** answering: OS, libc
(glibc version! musl!), CPU architecture, and language/ABI version. Python's
`manylinux_2_28_x86_64` encodes a glibc floor; that design is worth copying because it
expresses *forward* compatibility rather than distro identity.

Also decide: what happens when there's no matching binary? Fall back to source (and thus to
executing a build), or fail? Both answers are defensible; **silently falling back to source
build is how "it worked on my machine and took 40 minutes in CI" happens.**

---

## §10. Workspaces, Monorepos, and Overrides

### 10.1 Workspaces

Multiple packages in one repository, resolved together, with local packages satisfying each
other's dependencies instead of the registry.

Requirements that make a workspace implementation good:
- **A single lockfile for the whole workspace.** Per-package lockfiles defeat the purpose.
- **One shared resolution**, so two members can't end up on incompatible versions of a
  shared dependency by accident.
- **An explicit local-link protocol.** pnpm's `workspace:*` **always** resolves to the local
  package — making it impossible to accidentally test against the published version when you
  meant local. That is the correct default and worth copying.
- **Shared version constraints** — pnpm's *catalogs*, Cargo's `[workspace.dependencies]`,
  Maven's `dependencyManagement`. Without this, upgrading a dependency across 50 packages is
  50 edits.
- **Filtering and topological task execution** — `--filter`, run in dependency order.

### 10.2 Non-registry dependencies

Path, git (with a **pinned commit**, never a branch), URL, and vendored dependencies. Each
needs a lockfile representation that preserves reproducibility — a git dependency locked to
a branch name is not locked.

### 10.3 Overrides

`overrides` (npm, pnpm), `resolutions` (Yarn), `[patch]` (Cargo), `replace` (Go).
**[DURABLE] You need this escape hatch** — a transitive dependency has a CVE and the
intermediate package hasn't updated. Design it explicitly rather than letting people
hand-edit lockfiles.

But make it **loud**: overrides silently violate a dependency's declared constraints, which
means you own the compatibility risk. Print them on install. Make them expire or require
periodic re-confirmation if you can.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…