fix(build): publish the attested tarballs to npm - #2939
Closed
rytimx wants to merge 3 commits into
Closed
Conversation
Since the nx 19 -> 23 upgrade (#2928), nx release publish re-packs each package at publish time and serializes package.json dependencies in project-graph order, while the attestation step signs the pnpm pack tarballs (which preserve the repo file's key order). Packages with enough internal workspace deps get reordered, so the published tarball SHA256 no longer matches any attested subject and attestation verification fails for the published artifacts (every release since 2.24.0: @imtbl/sdk, passport, minting-backend, checkout-sdk, checkout-widgets). Publish the exact tarballs generated by pack-npm-packages and signed by actions/attest instead of letting nx re-pack, and verify post-publish that the registry serves the attested bytes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
View your CI Pipeline Execution ↗ for commit 8539cc4
💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗ ☁️ Nx Cloud last updated this comment at |
Address adversarial review findings: - Derive the publish list from the attested tarballs themselves (tar -xzOf package/package.json) with an explicit topological sort, instead of parsing pnpm -r exec output which interleaves progress and banner noise on stdout. - Guard npm view/publish against consuming loop stdin. - Replace the count assertion with a clearer ambiguity check (duplicate or unreadable tarballs refuse to publish, with a message naming the invariant). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Found by local end-to-end testing against the real 2.24.4 tarball set: - npm publish --dry-run still validates against the registry and errors on already-published versions, so the already-published skip check now runs in dry-run mode too (a dry_run dispatch without a version bump reports every package as Skipping and exits green). - npm view stdout is not guaranteed pure (update notices, wrapper banners), so the verify step extracts the tarball URL defensively. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
alex-connolly
approved these changes
Aug 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Since the nx 19 → 23 upgrade (#2928), the tarballs published to npm no longer match the tarballs signed by the
Generate SDK Package Attestationsstep, so attestation verification (gh attestation verify) fails for the published artifacts of every release since 2.24.0.Root cause: the workflow attests the output of
pnpm pack-npm-packages, butnx release publishre-packs each package at publish time. nx 23 serializes thepackage.jsondependenciesmap in project-graph order rather than the repo file's (alphabetical) order, so the two tarballs differ byte-wise for any package with enough internalworkspace:*dependencies for the ordering to change. Affected on every release since 2.24.0:@imtbl/sdk,@imtbl/passport,@imtbl/minting-backend,@imtbl/checkout-sdk,@imtbl/checkout-widgets(≥5 internal deps each). Packages with ≤2 internal deps coincidentally still match.Verified evidence:
imtbl-sdk-2.24.0.tgz=39b7fc1e…, while the registry serves01243a75…;@imtbl/config@2.24.0matches its attested digest exactly.npm notice shasummatches the registry bytes — the divergence is pack-vs-publish within a single run.nx release publish.package.jsonfor@imtbl/sdk@2.24.0lists dependencies in project-graph order; the repo file andpnpm packoutput are alphabetical.Change
Release to NPMnow publishes the exact tarball files that were packed and attested (npm publish <tgz>), in topological order, instead of letting nx re-pack. Byte identity between attested and published artifacts is guaranteed by construction, so no future toolchain upgrade can silently reintroduce this drift.--tag alpha|latestanddry_runbehaviour, and fails loudly if any packed+attested tarball is left unprocessed.NPM_CONFIG_PROVENANCEbehave the same for tarball publishes.Verify Published Artifacts Match Attested Tarballsstep: after publishing, downloads each published tarball from the registry and asserts its SHA256 equals the attested file (with retries for registry propagation), closing the loop end-to-end.Testing
@imtbl/configcorrectly ordered before its dependent@imtbl/sdk).--dry-rundoes not exercise sigstore signing, so provenance must be proven on a real publish):workflow_dispatchwithdry_run: true— exercises listing, ordering, and the ambiguity check. Without a version bump every package reportsSkipping … - already published; that is the expected green outcome.npm publish --dry-runfor unpublished versions, and the verify step passes on matching bytes / fails loudly on a tampered tarball. The integrity checker's own Go verification code was also run live against the registry: all 12 packages that already carry pnpm-pack bytes verify successfully, confirming checker compatibility with this pipeline's output.2. Let the next merge to main publish an alpha — this runs the identical publish + verify code for real.
3. Confirm provenance exists for the new alpha via
https://registry.npmjs.org/-/npm/v1/attestations/@imtbl%2Fsdk@<version>(2 attestations expected).4. Only then cut a public release.
npm publishresults inCannot find module 'sigstore'npm/cli#9722).Impact
External consumers: none. Versions, dist-tags,
npm installbehaviour, existing lockfiles, npm provenance, and the jsDelivr CDN are all unchanged. The only observable difference is cosmetic: the publishedpackage.jsondependency list returns to alphabetical order (matching the repo file, as it was pre-2.24.0). JSON key order has no runtime meaning and nothing (npm, bundlers, lockfiles) depends on it.Release process: same triggers and inputs.
release_type/dry_runbehave as before; thenpm_releasestep id is preserved so Slack notifications are untouched; the loop replicates nx's topological publish order and skip-if-already-published (which also makes re-runs after a partial failure safe). Alphas on every merge to main exercise the new path daily.Known risks, both bounded and fail-safe (worst case is a failed release run, never a wrong artifact on npm):
npm publish <tarball> --provenanceshould be confirmed once viadry_runand an alpha, since tarball publishes have had npm quirks historically.Notes
gh attestation verify; this fix is forward-looking from the next release. npm's own provenance still covers those versions.pnpm pack-npm-packages); the divergence was introduced by the re-pack insidenx release publishafter the nx 19→23 upgrade — this PR removes that second packing path entirely.🤖 Generated with Claude Code