Cargo registry: enforce duplicate and package protection checks at publish time
Everyone can contribute. Help move this issue forward while earning points, leveling up and collecting rewards.
Problem
Cargo (Rust) package publishing has two bugs. Both hide real failures from the user.
Rejections show up too late. The publish endpoint (PUT /api/v4/projects/:id/packages/cargo/api/v1/crates/new) returns HTTP 200 right away. It creates a placeholder package, attaches the uploaded crate, and queues Packages::Cargo::ExtractionWorker to process it later.
The real checks happen inside that worker, in Packages::Cargo::UpdatePackageFromMetadataService:
- Duplicate version check
- Package protection rule check
So cargo publish prints success even when GitLab rejects the crate. The only sign of failure is a package row with status error and a status_message. The Cargo client never sees that message. The user has to go look in the GitLab UI to find out their publish failed.
By contrast, lib/api/npm_project_packages.rb runs both checks synchronously, in the request, and returns a 403 for a protected package. Cargo does not do this.
This makes package protection rules advisory instead of blocking, for Cargo only. A customer could believe a crate name is protected when it is not actually enforced at push time. This is the more serious of the two problems.
Concurrent publishes report the wrong error. Every publish creates a new placeholder package. The exclusive lease in Packages::Cargo::UpdatePackageFromMetadataService is keyed on that placeholder's id:
packages:cargo:update_package_from_metadata_service:package:<package_id>If two publishes of the same crate name and version run at the same time, they get two different leases and run in parallel. Both pass the duplicate check. The second one then hits the database's unique index (index_cargo_metadata_on_project_normalized_name_version) and raises ActiveRecord::RecordNotUnique.
That error is not in Packages::ErrorHandling::CONTROLLED_ERRORS. So the package record says Unexpected error: ActiveRecord::RecordNotUnique instead of the correct message, Package already exists.
The stored data is fine. The unique index does its job and blocks the duplicate. Only the error message is wrong.
Proposal
Part 1: check earlier, fail fast.
Move the duplicate version check and the package protection rule check into the publish endpoint, before the placeholder package is created. Follow the pattern npm already uses.
- Parse the crate name and version out of the uploaded body first.
Packages::Cargo::ExtractMetadataContentServicealready does this parsing, so it can be reused. - Run both checks against that parsed data.
- Return a real HTTP error status if either check fails, so
cargo publishfails immediately. - Keep the existing worker-side checks in place as a backstop.
Part 2: fix the concurrent-publish error message. Either option works.
| Option | What it does |
|---|---|
| Re-key the lease | Base the exclusive lease on project id plus normalized name plus normalized version, taken after metadata is parsed. This prevents the race entirely. |
| Add to controlled errors | Add ActiveRecord::RecordNotUnique to Packages::ErrorHandling::CONTROLLED_ERRORS, so the existing race produces the correct Package already exists message. |
Why this matters before Beta
The Beta feedback issue puts Cargo support in front of real users. A publish that reports success and then silently fails is the kind of bug that burns early trust fast.
Package protection rules that do not actually block a push are worse. A customer could reasonably believe a crate name is protected, publish something they shouldn't be able to, and only find out later.
This should land before the package_registry_cargo_support feature flag is turned on for GitLab.com.
References
- Cargo MVC MR 4: !254282 (merged)
- Parent MVC issue: #33060
- Feature flag rollout issue: #525330
- Beta feedback issue: #603050