Package namespacing is one of those perennial topics that seems to attract designs but not much implementation. I took my own preferred approach (DNS domains) and got some robots to implement a proof-of-concept.
The code isn't great (and the robots hallucinate various minor details) but overall it's enough to get a feel for how DNS-based namespacing would work.
Cargo branch (raw LLM output): GitHub - jmillikin/upstream__cargo at domain-namespaces-raw-llm-output · GitHub
crates.io branch (raw LLM output): GitHub - jmillikin/upstream__crates.io at domain-namespaces-raw-llm-output · GitHub
I want to get a feeling for how the Cargo / crates.io team feel about the overall concept before I put any effort into cleaning up the proof-of-concept code.
- My impression from past discussions is that those teams have historically objected to namespaces for aesthetic reasons ("why name your crate
boring.com/base64when you could just name itbase64inator9000?"). - But also opinions may currently be different than they were a year or two ago.
Rough outline of the design:
- Package names are allowed (not required) to start with a
{namespace}/prefix, where the namespace is a DNS-style dotted name likeexample.comorjdoe.github.io. - This is designed to address name squatting ("I want to publish my
wasmcrate but there's already an empty crate namedwasm").- Control over subsets of the global namespace ("My project is named
coolso I want to prevent someone else from publishing a crate namedcool-wasm) is out of scope. - You can use your own domain as a namespace if you care about this, but it doesn't prevent someone else from registering packages with a given prefix either in the global namespace or their own domain.
- Control over subsets of the global namespace ("My project is named
- Cargo strips off the namespace prefix before passing the crate name to
rustc, soexample.com/base64isbase64to the compiler.- If you want to depend on
base64andexample.com/base64in the same crate then you'll need to use existing Cargo mechanisms to rename one in the local compilation context.
- If you want to depend on
- When a crate is first created on crates.io, if it has a namespace then that namespace will be interpreted as a DNS name and verified by fetching a list of authorized user keys.
- The URL of the file is
https://{namespace}/.well-known/rust-lang.org/package-namespace.json. - This file contains a list of keys that identify who is allowed to create packages in that namespace.
- The key format is determined by the registry. In the case of crates.io it's computed as approximately
sha256((namespace, user_id))-- seegen-auth-key.pyfor details. Note thatuser_idpart of existing API responses, so no part of this key is secret.
- The URL of the file is
- The use of
https://{namespace}/.well-known/as opposed to DNS TXT records (etc) is to enable users to publish namespaced crates without having to first pay for a domain.- Specifically they can put something on
{username}.github.io.
- Specifically they can put something on
- Domain verification only happens when a crate is first created. Subsequent uploads use the existing permissions (either a crate owner, or a TrustedPub key).
- This means that if control of a domain is lost then the new owner can't use that access to publish new versions of existing crates.
- Whenever the crate name needs to be encoded somewhere that a
/is semantically meaningful (URLs, file paths) it gets encoded to%2F.- This includes the paths in source archives.
- Yes this means the crates.io URLs for namespaced crates are kinda ugly. I think this doesn't matter.
Screenshots:
Example package-namespace.json:
{
"authorized_keys": [
"sha256-kdRCstHTc6I8OxwyuW+QC8gFkn4tT8/g4a0M//cvCXk="
]
}
Example Cargo.toml:
cargo-features = ["domain-namespaces"]
[package]
name = "example.com/hello"
version = "0.1.0"
edition = "2024"
description = "Hello, world!"
license = "0BSD"
repository = "https://github.com/example/hello"
homepage = "https://example.com/hello"
documentation = "https://example.com/hello-docs"

