Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Cargo

deps-cargo provides full LSP support for Rust’s package manager.

Basics

Manifest fileCargo.toml
Lock file (in-use version)Cargo.lock (versions 3 and 4)
Registrycrates.io — sparse index (index.crates.io) for version lookups, REST API (crates.io/api/v1) for package-name search
Version syntaxCargo’s own requirement grammar (semver::VersionReq): ^, ~, =, <, >, *, and bare comparison operators

deps-cargo parses every dependency-like table — [dependencies], [dev-dependencies], [build-dependencies], their [target.<cfg-expr-or-triple>] variants, and a workspace root’s [workspace.dependencies] — with byte-accurate position tracking, so hover, diagnostics, completion, and code actions all anchor on the exact name/version/features span in the file, not just the dependency’s line.

[dependencies]
serde = "1.0"
tokio = { version = "1", features = ["full"] }

Hovering serde’s version shows the latest crates.io release, whether the declared requirement is satisfied, and a link to the crate’s crates.io page; an outdated requirement gets an inlay hint (❌ 1.0.219) and a matching diagnostic with a “Update to latest version” code action. Typing inside the features array offers completion sourced from the latest stable version’s real feature list. workspace = true inheritance, path =/git = dependencies, and a package = "..." rename (aliasing the TOML key to a different registry lookup name) are all recognized and routed correctly — a renamed dependency’s diagnostics and completions key off the real crate name, not the local alias.

Custom/Private Registries

A Cargo dependency declared as registry = "<alias>" or registry-index = "<url>" resolves against that registry’s own sparse index — hover, diagnostics, completion, and code actions all work against it exactly as they do for a plain crates.io dependency, instead of showing no version data at all.

Resolution: registry = "<alias>" is resolved by reading [registries.<alias>] from the same .cargo/config.toml hierarchy Cargo itself consults — every ancestor directory’s .cargo/config.toml between the opened manifest and the filesystem root, closest directory winning — plus $CARGO_HOME/config.toml as the lowest-precedence tier. registry-index = "<url>" needs no config lookup: it is already a concrete index URL. Only sparse+https:// (or a bare https://) index URLs are supported; http:// and any URL carrying user:pass@ are rejected. Cargo’s CARGO_REGISTRIES_<NAME>_INDEX/_TOKEN environment variable overrides are also honored.

Authentication: a registry token is attached to requests against a registry resolved from $CARGO_HOME/config.toml (or its own CARGO_REGISTRIES_<NAME>_TOKEN environment variable) only. A registry alias resolved from a workspace .cargo/config.toml — a file a cloned, untrusted repository fully controls — never gets a credential attached, even if an identically-named alias is configured with one in $CARGO_HOME. This is deliberate: it prevents a hostile repository from redirecting a familiar alias name (e.g. "github") to an attacker-controlled host and harvesting whatever token the user’s real, differently-scoped registry of that name would have used.

The token is sent verbatim in the Authorization header, with no Bearer prefix, exactly as Cargo itself sends it.

Mirroring crates.io ([source] replace-with): a workspace’s [source.crates-io] replace-with = "<name>" chain, terminating at a [source.<name>] registry = "sparse+https://…" entry, reroutes every plain (un-aliased) dependency to that mirror — hover/diagnostics/completion reflect the mirror’s data, the crates.io hover link stays intact (Cargo verifies per-version checksum equality against crates.io for a mirror, so its content is exactly as trustworthy as crates.io’s own), and OSV vulnerability scanning still runs against it. A chain terminating at a directory (vendored), local-registry, or git-index (non-sparse) source instead leaves plain dependencies resolving against crates.io unchanged — vendoring/mirroring through those mechanisms doesn’t guarantee the same version set as crates.io, so degrading to no data would be worse than the pre-existing crates.io answer.

Reachability policy (registries.workspace_registries, security): a workspace-declared registry index (the registry/registry-index alias path, or a [source] mirror) is checked against this setting before it is ever fetched — a hostile cloned repository can write both, and this LSP parses on file open, before any build runs. This setting is shared with npm’s .npmrc resolution, PyPI’s custom-index resolution, NuGet’s NuGet.Config resolution, Go’s GOPROXY chain, and GitLab CI/CD’s self-hosted-instance resolution — one process-wide HttpCache policy governs every ecosystem’s workspace-declared registry fetches; see npm, PyPI, NuGet, Go, and GitLab CI/CD for what that sharing means in practice. Three values:

ValueBehavior
"public_only" (default)Only a publicly-routable host is fetched — blocks loopback, link-local, RFC1918/CGNAT, unique-local-v6, and cloud-metadata-range hosts (e.g. 169.254.169.254) declared by a workspace file. A corporate https://index.mycorp.dev-style registry still works, since a DNS name cannot be classified as internal without resolving it — see the residual-risk note below.
"off"No workspace-declared index is ever fetched — the only complete boundary. Applies to the alias path as well as [source].
"all"Public hosts plus the private hosts and CIDR ranges listed in the DEPS_LSP_PRIVATE_REGISTRY_HOSTS environment variable (see Configuration) — the escape hatch for a workspace that legitimately points at an RFC1918 registry. Without that variable it behaves like "public_only": the setting is repository-controllable, so the variable is the effective control, and a listed host is reachable on any port. It never allows loopback, link-local, cloud-metadata, unspecified or reserved addresses, which are refused under every value.

Proxies. A workspace-declared registry connects directly and bypasses the system proxy by default, so the connect-time check sees the real address; see Proxies and guarded registry traffic for the opt-in variable.

Hosts that resolve to a blocked address. The check above looks at the declared host name. A name that resolves, when the connection is made, to a blocked address class (for example a public name that points at 10.0.0.5) is refused too, in every ecosystem, and the dependency shows a policy-specific message instead of a generic fetch failure. The message names the address class only, never the address. For the classes "all" does not allow, it says the host “is never a registry under any policy”; for the others it says the host is “blocked by registries.workspace_registries policy” and adds that private hosts need "all" and DEPS_LSP_PRIVATE_REGISTRY_HOSTS. Cached data for a registry is still served while revalidation is blocked, since no connection is made.

$CARGO_HOME/config.toml-configured registries are never policy-checked, under any of the three values — that file is the user’s own trusted configuration, not something a cloned repository controls. A blocked index is never silent: it is logged, and a registry/registry-index dependency line additionally gets an informational diagnostic naming the blocked host class (a [source]-chain block is log-only, since it is a property of .cargo/config.toml, not of any one dependency line). This diagnostic is not Cargo-specific (issue #925): npm’s .npmrc registry=/@scope:registry= resolution, PyPI’s --index-url/Poetry source =/uv index = resolution, and NuGet’s NuGet.Config <packageSources>/<packageSourceMapping> resolution all surface the same informational diagnostic on the affected dependency’s own line when a declared registry is blocked, instead of degrading silently to the public registry. Go’s GOPROXY chain and GitLab CI/CD’s registries.gitlab_instance_host/component: host resolution were the last two ecosystems to close this gap (issues #967, #968) — every ecosystem with a workspace-declared, policy-gated registry host now surfaces the same kind of diagnostic instead of leaving the block visible only in the server log.

Beyond that initial URL-string check, public_only (and off/all) is also enforced at connect time: the address a workspace-declared index’s hostname actually resolves to, and the target of any redirect hop the fetch follows, are both checked against the setting too — not just the declared URL string at parse time. This closes a DNS-rebinding gap (issue #455) where a workspace file declares a host that classifies as public at parse time (https://evil.example/) but resolves to a blocked address (an RFC1918/CGNAT range, or one rebound after parse time) at actual fetch time.

Tightening the setting (e.g. all -> public_only/off) now takes effect on already-open documents immediately: workspace/didChangeConfiguration re-parses every open document whose ecosystem consults this policy and forces a full version refetch, purging any cached version obtained through the now-untrusted registry rather than requiring the document to be edited or reopened first (issue #592).

Known limitations:

  • Editing .cargo/config.toml does not take effect until the affected Cargo.toml is next reparsed (edited, or the document reopened) — there is no dedicated file watcher for it yet.
  • The sparse index protocol has no search endpoint, so package-name completion (typing a brand-new dependency) always searches crates.io, even inside a workspace whose default registry is mirrored elsewhere.
  • Git-index (non-sparse) private registries remain unsupported, matching prior behavior.