Skip to content

chore(deps): migrate from npm to pnpm for supply-chain security #84

Description

@natashaannn

User story

As a contributor, I want the project to use pnpm instead of npm, so that package installation is protected against supply-chain attacks that exploit npm's resolution and download model.

Background

npm's package resolution model has known supply-chain attack vectors. The mini sha hulud attack (June 2026) is a recent example targeting npm's package publishing and resolution flow, demonstrating how malicious packages can be injected and resolved transparently under npm.

Key risks with npm:

  • Hoisted node_modules layout enables phantom-dependency exploitation
  • No content-addressed store — a tampered package isn't detected at install time
  • npm install does not enforce lock-file integrity by default

pnpm addresses these directly:

  • Content-addressed store: each package version is stored once with an integrity hash; a tampered package fails on hash mismatch
  • Strict node_modules layout: no phantom dependencies by default
  • pnpm-lock.yaml includes per-package integrity hashes and is deterministic

Related: npm supply-chain attack surface documented at https://pnpm.io/motivation

Acceptance criteria

Happy path

Given the repo is cloned fresh
When pnpm install is run
Then all dependencies install successfully using pnpm-lock.yaml and pnpm audit reports no critical/high vulnerabilities

Given any npm run <script> command from package.json
When run as pnpm run <script> (or pnpm <script>)
Then it produces identical output to the previous npm invocation

Given the CI workflow runs
When pnpm is installed and pnpm install --frozen-lockfile is executed
Then the build and test suite pass without modification

Error path / edge case

Given pnpm-lock.yaml is present but package-lock.json is accidentally re-generated
When CI runs
Then CI fails loudly (frozen lockfile check catches the mismatch)

Out of scope

  • Upgrading any dependency versions as part of this migration
  • Changing workspace configuration (no monorepo restructure)
  • Migrating .npmrc auth tokens (out of scope; handled separately if needed)

Technical context

  • Current lock file: package-lock.json (npm v10)
  • package.json has no packageManager field yet
  • CI: check .github/workflows/ for any npm ci or npm install calls that need updating
  • No private registry or scoped packages that would require special .npmrc config

Implementation details

  1. Install pnpm globally (npm install -g pnpm or via corepack enable && corepack prepare pnpm@latest --activate)
  2. Delete package-lock.json
  3. Run pnpm import to generate pnpm-lock.yaml from the existing lock file, then verify with pnpm install
  4. Add "packageManager": "[email protected]" to package.json (use the installed version)
  5. Run the full test suite: pnpm test, pnpm run test:react, pnpm run lint, pnpm run build
  6. Update .github/workflows/ — replace npm ci with pnpm install --frozen-lockfile; add a pnpm setup step (e.g. actions/setup-node + pnpm/action-setup)
  7. Add package-lock.json to .gitignore to prevent accidental regeneration
  8. Update install instructions in README.md and CLAUDE.md
  9. Run pnpm audit and resolve any critical/high findings before merging

Additional test scenarios

  • pnpm run shorts:wizard launches without error
  • pnpm run dev starts the Next.js dev server
  • pnpm run build produces a successful production build
  • pnpm audit exits 0 (or documents any accepted exceptions)

Hard constraints

  • pnpm-lock.yaml must be committed; package-lock.json must be deleted and gitignored
  • No dependency version bumps in this PR — pure tooling migration only
  • CI must use --frozen-lockfile to prevent silent lock-file drift

Dependency issues

  • Independent of all current refactor phases (0–9); can be done on a standalone chore/migrate-npm-to-pnpm branch off main

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions