Skip to content
Back to skills

Package Manager Registries And Installation

ASecurity

Use when building or operating a package registry or the install side of a client. Covers the registry API surface, immutability as the single most important property, mutable-release poisoning, namespacing and name allocation, the content-addressed store, the layout problem's four answers (flat, nested, isolated, PnP), multiple versions coexisting, runtime resolution as part of the design, and publishing and authentication — the evolution from API tokens to trusted publishing with OIDC, prov...

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

Works with

  • cli
  • api

Security analysis

A100/100

Scanned September 19, 2026

npx -y skills add the-vibey-project/vibey --skill package-manager-registries-and-installation --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Package Manager Registries And Installation?

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

Security grade badge for Package Manager Registries And Installation
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/the-vibey-project-package-manager-registries-and-installation/badge)](https://www.skillsdirectory.com/skills/the-vibey-project-package-manager-registries-and-installation)

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-registries-and-installation
description: "Use when building or operating a package registry or the install side of a client. Covers the registry API surface, immutability as the single most important property, mutable-release poisoning, namespacing and name allocation, the content-addressed store, the layout problem's four answers (flat, nested, isolated, PnP), multiple versions coexisting, runtime resolution as part of the design, and publishing and authentication — the evolution from API tokens to trusted publishing with OIDC, provenance and attestations, Sigstore, and SLSA."
---

# Package Manager Development: Registry Design, Cache, Layout, and Linking, Publishing and Authentication

> **Part 2 of 5** of the *Package Manager Development* reference (plugin `package-manager-development`), covering §5–§7. Sibling skills: `package-manager-versioning-and-resolution` (§0–§4), `package-manager-supply-chain-and-workspaces` (§8–§10), `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.

---

## §5. Registry Design

### 5.1 The API surface

```
GET  /packages/{name}                  → all versions + metadata (the resolver's hot path)
GET  /packages/{name}/{version}        → one version's metadata
GET  /packages/{name}/{version}/dl     → the artifact (usually a CDN redirect)
POST /packages                         → publish (authenticated)
DELETE / POST /yank                    → yank/unyank (NOT delete — see 5.2)
GET  /search, /index, /changes         → discovery and incremental sync
```
**Design notes that matter at scale:**
- **Separate metadata from artifacts.** Metadata is small, hot, highly cacheable, and
  resolver-critical. Artifacts are large, cold, and belong on a CDN. Conflating them makes
  resolution slow (§3.6 → `package-manager-versioning-and-resolution`).
- **Support conditional requests and incremental sync** (`ETag`, `If-None-Match`, a change
  feed). Mirrors and CI caches depend on it.
- **Paginate and bound everything.** A package with 10,000 versions must not return a
  10 MB JSON blob on every resolution step.
- **Publish upload timestamps** (Python's PEP 700 does) — this is what makes cooldowns
  possible (§8.4 → `package-manager-supply-chain-and-workspaces`).

### 5.2 Immutability — the single most important registry policy

**[DURABLE] A published (name, version) must never change its bytes.** Consequences of
getting this wrong are severe and every ecosystem has learned it, usually the hard way:
- Caches everywhere become incoherent.
- Lockfile integrity hashes break for everyone.
- The `left-pad` incident (2016) — an unpublished package broke thousands of builds —
  established that **unpublishing must be tightly restricted**.

**The correct primitive is a *yank* (or *quarantine*), not a delete:**
- Yanked versions remain downloadable so existing lockfiles keep working.
- They are excluded from *new* resolutions.
- crates.io, PyPI (quarantine), and npm's deprecate/unpublish windows all converge on this.

**⚠️ And immutability has a security cost you must design for:** Go's `proxy.golang.org`
and `sum.golang.org` provide extremely strong reproducibility — but that same immutability
means **a malicious module version, once published and cached, persists and continues to be
served even after the source repository is removed.** Reproducibility and takedown are in
direct tension. Have a documented answer for it.

### 5.3 Mutable-release poisoning — a live design problem

Even with immutable versions, most registries historically allowed **adding new files to an
existing release** (e.g. a new wheel for a new Python version, months later). That's a
poisoning vector: a stolen token lets an attacker add a malicious artifact to a
long-trusted release.

**PyPI closed this in July 2026: new files are rejected on releases older than 14 days.**
The reasoning is instructive — the change was proposed during PEP 740 discussions in
January 2024, stalled, and was revived by the **March 2026 LiteLLM and Telnyx
compromises**. Before shipping, PyPI measured the impact: of the top 15,000 projects, only
**56** had uploaded a Python 3.14-compatible wheel more than 14 days after the release
first appeared. Seth Larson's stated rationale is worth internalizing: it prevents releases
from entering "an indeterminate and confusing state of both compromised and not
compromised, where only a subset of files could be poisoned."

**[DURABLE] That's the template for registry policy changes: identify the vector, measure
the legitimate usage you'd break, publish the numbers, then change the default.**

### 5.4 Namespacing and name allocation

| Model | Example | Trade-off |
|---|---|---|
| Flat, first-come | npm (unscoped), PyPI, crates.io | Simple; **land-grab, typosquatting, permanent name exhaustion** |
| Scoped/namespaced | `@org/pkg` (npm), NuGet prefixes | Ties identity to an owner; reduces squatting |
| Domain-derived | **Go** (`github.com/user/mod`), Maven (reverse-DNS groupId) | Namespace is *inherited from an existing trust root*. Elegant; couples you to the host |
| Content-addressed | Nix, Guix | Names are labels, identity is a hash |

**[DURABLE] Go's and Maven's approach — deriving the namespace from a domain or repo you
already control — eliminates an entire class of problems** (squatting, name disputes,
ownership transfer ambiguity) at the cost of coupling package identity to a hosting
provider. If you're designing new, seriously consider it.

Whatever you choose, you need policy for: name similarity (§8.3 → `package-manager-supply-chain-and-workspaces`), abandoned packages,
ownership transfer, trademark disputes, and reuse of deleted names (**never reuse a name —
that's the "repojacking" attack**).

---

## §6. Cache, Layout, and Linking

### 6.1 The content-addressed store

**[DURABLE] The right shape for a package cache is a global, content-addressed store.**
```
~/.cache/pm/
  index/         metadata, keyed by (name, version), immutable → cacheable forever
  files/         file blobs keyed by content hash (sha256)
  packages/      package trees, assembled from file blobs
  tmp/           staging — assemble here, then atomically rename into place
```
Properties you get for free: deduplication across projects, integrity verification by
construction, safe concurrent access (a hash-named file either exists and is correct, or
doesn't exist), and trivial garbage collection.

**Write atomically**: download to `tmp/`, verify the hash, then `rename()` into place.
A partially-written cache entry that looks complete is the classic "wedged cache" bug that
users fix by deleting the whole cache and cursing your name.

**pnpm's contribution** was demonstrating how much this matters: a single content-addressed
store hard-linked into each project's `node_modules` means one copy of a given package
version on the whole machine, saving developers with many projects tens of gigabytes.

### 6.2 The layout problem — four answers

This is the JavaScript ecosystem's defining argument, and it generalizes.

| Layout | Mechanism | Ecosystem | Trade-off |
|---|---|---|---|
| **Nested** | Each package gets its own `node_modules` | npm v2 | Correct; enormous duplication; Windows path-length failures |
| **Flat / hoisted** | Everything flattened to the top, conflicts nested | npm v3+, Yarn Classic, Bun (default) | Compact, compatible; **allows phantom dependencies** (§6.3) |
| **Isolated / symlinked** | Content-addressed store + hardlinks + a symlink tree mirroring the true graph | **pnpm**, **Bun `--linker isolated`** | Strict — packages can only see what they declared; occasional postinstall scripts assume a hoisted shape and break |
| **No node_modules** | A `.pnp.cjs` map from import to zip archive | **Yarn Berry PnP** | Fastest, strictest, "zero installs"; requires runtime cooperation and breaks tools that stat the filesystem |

> **⚠️ GOTCHA — phantom dependencies are a correctness bug, not a style issue.** Under a
> hoisted layout, `require('lodash')` works even if you never declared lodash, because
> something else hoisted it to the top. Your code then breaks when that transitive
> dependency changes — a failure with no visible cause in your own manifest. Strict layouts
> exist entirely to make this impossible, and **that, not disk space, is their real
> argument.**

**[DURABLE] The generalizable lesson:** you're choosing between *the real dependency graph*
and *a flattened approximation that the runtime finds easier*. Flattening is faster and
more compatible; it is also lying to the program about what it can see. Ecosystems whose
runtime resolution is explicit (Python's `sys.path` per-venv, Cargo's compiler-passed
`--extern`, Go's import paths) never had this problem, because the layout was never the
lookup mechanism.

### 6.3 Multiple versions coexisting

If your runtime can load two versions of the same package simultaneously (npm, Cargo), the
resolver can escape most conflicts (§3.5 → `package-manager-versioning-and-resolution`) — at the cost of duplicated code, larger
artifacts, and **singleton bugs**. If it can't (Python, Java on one classpath), a conflict
is fatal and must be reported, which makes resolution failures far more common and your
error messages far more important.

Cargo's rule is a good middle: **semver-compatible versions are unified into one; semver-
incompatible versions coexist.** This maximizes deduplication while keeping the escape
hatch, and the "expected `foo::Type`, found `foo::Type`" error is the price.

### 6.4 Runtime resolution is part of your design

The package manager's job doesn't end at install; the runtime has to find the code.
- **Node**: directory walk up from the importer, looking for `node_modules` — the reason
  layout *is* resolution.
- **Python**: `sys.path`, one environment per virtualenv — flat, one version of anything.
- **Cargo/Rust**: the compiler is passed explicit `--extern` paths — no filesystem search
  at all, which is why Rust can have two versions of a crate without ambiguity.
- **Go**: import path includes the major version (`/v2`) — **semantic import versioning**,
  so two majors are literally different packages.
- **JVM**: classpath order; first match wins. The source of endless "jar hell."

**[DURABLE] If you're designing a new ecosystem, make runtime resolution explicit and
independent of directory layout.** It removes phantom dependencies, path-length limits,
and hoisting entirely.

---

## §7. Publishing and Authentication

### 7.1 The evolution, and where it landed

```
Gen 1  username + password                  → phishable, reused, no scoping
Gen 2  long-lived API token                 → phishable, exfiltratable, "harvest now use later"
Gen 3  token + 2FA on the account           → helps login, does NOT help a stolen CI token
Gen 4  TRUSTED PUBLISHING (OIDC)            → no long-lived credential exists at all  ★
       + provenance attestations            → and the artifact proves where it came from
```

**[DURABLE] Trusted publishing is the current best practice and the direction every major
registry has moved.** The mechanism:
1. The registry pre-registers a trust relationship: "GitHub Actions workflow
   `.github/workflows/release.yml` on repo `org/repo`, environment `release`, may publish
   package X."
2. At publish time, CI requests a short-lived **OIDC token** from its provider.
3. The registry validates the token's claims (repo, workflow, ref/environment) against the
   registration and issues a **short-lived, minimally-scoped** upload credential.
4. No long-lived secret is ever stored anywhere.

PyPI shipped this in April 2023; npm added it in response to Shai-Hulud; crates.io has it
GA. **PEP 807** proposes standardizing the mechanism so *any* Python index can implement
it, encapsulating PyPI's existing implementation.

**Its explicit security properties**, as PEP 807 states them: credentials are short-lived
and minimally scoped, limiting blast radius; automatic expiry means **attackers cannot
mount "harvest now, use later" campaigns**; and the upload is conceptually linked to the
CI identity authorized to perform it.

> **⚠️ GOTCHA — trusted publishing is only as good as its trust configuration.** Trust by
> *full repository*, not by org or wildcard. Trust by *branch or environment*, not by any
> ref. A trusted-publishing config that accepts any workflow on any branch is worse than a
> token, because it looks secure. The 2026 attacks specifically exploited this class of
> misconfiguration.

### 7.2 Provenance and attestations

**The vocabulary, precisely — people conflate these constantly:**
- **Provenance** = a *factual claim* about how an artifact was built (source repo, commit,
  builder, parameters). **On its own it has no integrity properties whatsoever.**
- **Attestation** = provenance wrapped in a *signed* envelope, so the claim has a
  cryptographically verifiable author.
- The dominant envelope is **DSSE** (Dead Simple Signing Envelope); the dominant payload
  format is an **in-toto statement** with a **SLSA provenance** predicate. "Attestation"
  in 2026 almost always means DSSE + in-toto.
- **SLSA** describes build integrity *levels*; **Sigstore** is the signing mechanism that
  makes the claims trustworthy rather than self-asserted text.

**Sigstore's keyless model** is why this became practical: instead of maintainers managing
long-lived signing keys (which historically nobody did well — see the PGP-on-PyPI
experience), you generate an **ephemeral keypair**, exchange a CI OIDC token with the
**Fulcio** CA for a short-lived X.509 certificate binding that key to the CI identity, sign,
publish the signature to the **Rekor** transparency log, and **throw the key away**. No key
management, and anyone can verify both the signature and the identity that made it.

**Registry state as of 2026:**
- **npm** — Sigstore-backed SLSA provenance, GA since 2023, generated by the npm CLI itself.
- **PyPI** — **PEP 740** digital attestations, Final in 2024, generated automatically by
  `pypa/gh-action-pypi-publish` v1.11.0+ when using trusted publishing. **Over 132,000
  PyPI packages carried attestations as of March 2026.**
- **crates.io** — trusted publishing is GA, but **Sigstore-based signing/provenance
  (RFC 3403) remains at the proposal stage**; integrity comes from elsewhere.
- **Go** — arguably the strongest *integrity* story of any ecosystem via `sum.golang.org`,
  but no build provenance; achieving higher SLSA levels goes through the build platform,
  not the registry.
- **Maven Central** — PGP signatures traditionally; Sigstore adoption is emerging and Java
  provenance is the least mature of the major ecosystems.
- **GitHub Artifact Attestations** provide a language-agnostic path (SLSA Build L2 by
  default, L3 with reusable workflows), which is how ecosystems without registry support
  get provenance at all.

**[DURABLE, and the sentence to remember] Provenance does not prove a package is safe. It
proves it came from the build pipeline its maintainers claim.** That is precisely enough to
detect the dominant 2025–26 attack pattern — a malicious version published from a stolen
token outside the project's normal CI — and precisely not enough to detect a maintainer who
was themselves compromised (§8.1 → `package-manager-supply-chain-and-workspaces`).

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…