Skip to content

Imports: refuse a NuGet feed's .nupkg that is not the listed id and version - #843

Merged
schmitch merged 1 commit into
mainfrom
claude/813-nuget-feed-import-identity
Nov 3, 2026
Merged

schmitch merged 1 commit into
mainfrom
claude/813-nuget-feed-import-identity

Conversation

@schmitch

@schmitch schmitch commented Nov 3, 2026

Copy link
Copy Markdown
Member

Fixes #813

Problem

A NuGet feed import (PackageSource::Feed) left ImportOptions::expected unset, so publish_staged never checked that the downloaded .nupkg was the id and version the feed listed. A feed could serve another package's file for a listed version. With skip, the import stored the other package. With overwrite, it replaced a version Forge already held. The registration's SHA-512 check didn't help, because the same feed lists that hash.

Change

  • copy_package (crates/server/src/import/mod.rs) now passes expected: Some((item.name, item.version)) for every package source, as the npm and apt imports already do. A mismatched package is refused, and the item fails with "the package is X v, not Y v".
  • The comparison in publish_staged uses NuGet's lower_id and normalize_version. The feed's flat container lists versions in lowercase, but normalization lowercases prerelease labels and drops build metadata, so case and version-format differences still compare equal. The only package sources are Feed and Npm. A Nexus or other NuGet v3 source is read through the same Feed, so it is covered too.
  • Changelog fragment: docs/changes/2.2.0/813.security.md.

Tests

  • New: a_feed_cannot_plant_another_package_under_a_listed_version (crates/server/tests/import.rs). It sets up a feed that lists Acme.Foo 1.0.0 and Acme.Bar 1.0.0, with registration SHA-512s that match the files served. Those files are actually other bytes of Acme.Victim 2.0.0 (which Forge holds) and Acme.Fresh 3.0.0 (which Forge lacks). The test runs the import with skip and with overwrite. In both runs, both items fail with "the package is …, not …", nothing is stored under the listed or the planted ids, and Forge keeps its own Acme.Victim bytes. Without the fix this test fails: Acme.Fresh gets imported.
  • cargo test -p forge-server --test import: all 17 tests pass, including the existing NuGet and npm import tests.
  • cargo clippy -p forge-server --all-targets -- -D warnings, cargo fmt --all --check and cargo xtask changelog --check are clean.

This change has no admin-API or UI part.


Generated by Claude Code

…ersion

A feed import passed no expected identity to the hosted write path, so a
feed whose flat container served another package's file for a listed
version stored that other package and, with on_conflict overwrite,
replaced a version Forge already held. Every package import now passes
the listed id and version, like the npm and apt imports, and the item
fails with "the package is ..., not ...".

Fixes #813

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012e6DJb9nERqzebpXXBCBRg
@schmitch
schmitch added this pull request to the merge queue Nov 3, 2026
Merged via the queue into main with commit 0e596d2 Nov 3, 2026
19 checks passed
@schmitch
schmitch deleted the claude/813-nuget-feed-import-identity branch November 3, 2026 13:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants