Skip to main content

Phase 1. npm registry client

FieldValue
MEPMEP-72 §Phases
StatusNOT STARTED
Tracking issue(pending)
Tracking PR(pending)
Commit(pending)

Gate

TestPhase1NpmRegistry in package3/typescript/npmregistry/phase01_test.go: subtests fetch_packument, resolve_range, download_tarball, verify_integrity, read_provenance. The first fetches https://registry.npmjs.org/zod against a recorded golden response and asserts the parsed Packument exposes all published versions plus the dist-tags map. The second resolves ^3.22.0 against the packument and asserts the highest matching version is selected. The third downloads the tarball at dist.tarball, verifies the SHA-512 in dist.integrity (subresource-integrity format: sha512-<base64>), and unpacks to a scratch directory. The fourth verifies the unpacked tree's content-addressed hash matches a recorded golden. The fifth reads the _attestations array on the packument and exposes the predicateType = "https://slsa.dev/provenance/v1" entries.

The phase also gates against a fixture replay of the May 2026 packument for all 24 fixture-corpus packages. The replay uses a recorded HTTP cassette (testdata/cassettes/*.json) so the gate runs hermetically without network access.

Lowering decisions

The client is a pure-Go HTTP/2 client using net/http with a custom http.Transport that pins TLS to TLS 1.3 minimum. The registry endpoint is configurable via Options.RegistryURL (default https://registry.npmjs.org); private registries (Verdaccio, GitHub Packages, Artifactory) are supported by overriding the base URL.

Packument fetches use Accept: application/vnd.npm.install-v1+json to get the abbreviated packument (deps-only, no readme, no author bios). Full packument is available via Accept: application/json but is ~10x the size and only needed when the bridge surfaces a deprecation reason or repository URL; phase 1 ships only the abbreviated path.

The dist.integrity field is the canonical hash (subresource-integrity format introduced in npm 5.0, December 2017). The bridge verifies sha512 (mandatory; sha1 legacy entries are rejected with a clear diagnostic). Phase 9's lockfile records both the SRI hash AND the MEP-57 BLAKE3 secondary hash of the unpacked tree.

The _attestations field is the Sigstore-attestation pointer added by npm Trusted Publishing (GA April 2024). When present, the bridge fetches the attestation bundle from https://registry.npmjs.org/-/npm/v1/attestations/<pkg>@<version> and verifies the Sigstore signature against the Fulcio CA bundle. When absent, the bridge sets sigstore-attested = false in the lockfile and emits a build-time warning (configurable to error via [ts.capabilities] sigstore-required = true).

Tarball download uses streaming gunzip + tar extraction; the helper allocates a scratch directory under the workspace's WorkDir/tarballs/<pkg>@<version>/ and writes the extracted tree there. Symlinks in tarballs are refused (some npm packages have CVE-grade symlink-traversal payloads); the extractor enforces the no-parent-traversal rule and rejects any entry with .. in its path.

Files changed

FilePurpose
package3/typescript/npmregistry/client.goClient, Options, NewClient, FetchPackument, DownloadTarball, FetchAttestation
package3/typescript/npmregistry/packument.goPackument, PackumentVersion, Dist, DistTags, ResolveRange
package3/typescript/npmregistry/attestation.goAttestation, PredicateType, VerifySigstore
package3/typescript/npmregistry/tarball.gostreaming gunzip + tar extraction with symlink + parent-traversal refusal
package3/typescript/npmregistry/phase01_test.goTestPhase1NpmRegistry sentinel
package3/typescript/npmregistry/testdata/cassettes/*.jsonrecorded HTTP responses for all 24 fixture packages

Test set

  • TestPhase1NpmRegistry/fetch_packument
  • TestPhase1NpmRegistry/resolve_range
  • TestPhase1NpmRegistry/download_tarball
  • TestPhase1NpmRegistry/verify_integrity
  • TestPhase1NpmRegistry/read_provenance
  • Fixture-corpus replay for all 24 packages.

Cross-references