Skip to content

fix(plugin): verify install checksums against the artifact actually downloaded - #406

Merged
acamarata merged 3 commits into
mainfrom
fix/per-platform-plugin-checksums
Sep 11, 2026
Merged

acamarata merged 3 commits into
mainfrom
fix/per-platform-plugin-checksums

Conversation

@acamarata

Copy link
Copy Markdown
Collaborator

Summary

A CLI plugin (one with a binaryName) downloads its per-platform release asset first, falling back to the source tarball only on failure — but the registry carried exactly one checksum per plugin (the source tarball's), so installLocked was hashing the downloaded platform tarball and comparing it against the source tarball's checksum. That comparison can never match, so every plugin whose per-platform asset downloads successfully would fail checksum verification at install time.

  • downloadPluginPackageForTier now returns which artifact it fetched (ArtifactKindSource or a platform string from PlatformArch()) alongside the download path.
  • internal/plugin/registry_parse.go parses the registry's new nested checksums.platforms object into PluginManifest.PlatformChecksums (map[string]string, keyed by the exact platform strings PlatformArch() returns).
  • installLocked resolves the checksum that matches the artifact actually on disk via the new resolveArtifactChecksum: source uses manifest.Checksum exactly as before (same FIX-CLI-6 leniency when empty); a platform artifact uses its matching PlatformChecksums entry.
  • A platform artifact with no matching registry checksum is refused unconditionally — never falls through to the source-checksum leniency, and no env var changes that. That leniency exists for the tracked source-checksum coverage gap; it was never meant to cover an unverified downloaded executable.
  • PlatformChecksums round-trips through the registry cache (Registry.MarshalJSON), matching every other field in this package per TestRegistryRoundTripLosesNoField.

Companion registry-side change (adds checksums.platforms to the schema, backfills it for all 32 binaryName free plugins, and extends the CI checksum gate to verify it): nself-org/plugins branch p6/release-v1.0.1-repackaged-free-plugins (PR #81).

Test plan

  • gofmt -l . empty (excluding pre-existing vendor findings)
  • make vet clean
  • go test ./internal/plugin/... — 412 passed (was 398 before this change)
  • New tests cover: platform artifact verifies against its matching platform checksum, a mismatch is rejected, and a missing platform checksum is refused unconditionally (regardless of NSELF_PLUGIN_REQUIRE_CHECKSUM, a present source checksum, or nil vs. missing-key map shape)
  • Manual install against a real registry.json carrying checksums.platforms (blocked on the companion plugins PR merging first)

…ownloaded

A plugin with a binaryName has downloadPluginPackageForTier try its
per-platform release asset first, falling back to the source tarball only
when that download fails. But the registry carried exactly one checksum per
plugin (the source tarball's), and installLocked always verified against it
regardless of which artifact was actually on disk — so any binaryName plugin
whose per-platform asset downloaded successfully had its platform tarball's
bytes hashed and compared against the source tarball's checksum, which can
never match.

downloadPluginPackageForTier now reports which artifact it fetched
(ArtifactKindSource, or a platform string from PlatformArch()) alongside the
path. installLocked resolves the checksum that matches via the new
resolveArtifactChecksum: the source artifact uses manifest.Checksum exactly
as before (including its existing FIX-CLI-6 warn-and-proceed leniency when
empty); a platform artifact uses the matching entry in the new
PluginManifest.PlatformChecksums map, parsed from the registry's
checksums.platforms object.

A platform artifact with no matching registry checksum is refused
unconditionally — it never falls through to the source-checksum leniency,
and no env var (NSELF_PLUGIN_REQUIRE_CHECKSUM included) changes that. That
leniency exists for the documented, tracked source-checksum coverage gap; it
was never a license to install a downloaded executable with zero
verification. A release that predates PlatformChecksums, or one platform
whose checksum was never backfilled, is a registry data gap to fix upstream,
not a flag to bypass here.

PlatformChecksums round-trips through the registry cache (Registry.MarshalJSON)
the same way every other field in this package must, per
TestRegistryRoundTripLosesNoField.
… policy

resolveArtifactChecksum: the source artifact always uses manifest.Checksum
(including the empty-string case, left for verifyChecksum's own leniency to
handle); a platform artifact resolves to its matching PlatformChecksums
entry, never the source checksum or a different platform's; a platform
artifact with no matching entry is refused regardless of
NSELF_PLUGIN_REQUIRE_CHECKSUM, a present source checksum, or whether the map
is nil versus just missing that one key.

Three end-to-end tests exercise the same policy through verifyChecksum: a
correct platform checksum passes, a mismatched one is rejected (mirroring
the existing source-checksum mismatch test), and a missing one never reaches
verifyChecksum's lenient empty-string branch at all.
…ile cap

The per-artifact checksum change pushed this file to 309 lines, past the
engineering-standard cap enforced by internal/repoqa's
TestFileSizeBudgetNotExceeded (budget: 0 files allowed over). Tightened the
new Step 5 comments and removed a pre-existing duplicated sentence in the
Step 4 comment — no behavior change, same logic, back to 300 lines exactly.
@acamarata
acamarata merged commit 829cb67 into main Sep 11, 2026
31 checks passed
@acamarata
acamarata deleted the fix/per-platform-plugin-checksums branch September 11, 2026 14:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant