Repository navigation
1.0.0 readiness: version-resolution, parser, security, and release fixes - #28
Merged
Merged
Conversation
Set the workspace version to 1.0.0 and add distribution metadata: license (MIT), rust-version (1.85, matching edition 2024), repository, description, readme, keywords, categories, and publish = false (the project ships as a binary via release artifacts and `cargo install --path .`, not to crates.io). Add the semver dependency to the core crate for version-requirement resolution.
check_package forwarded the raw requested version to an exact-match lookup, so a range or partial requirement (`react` "18", "^4.17.0", `serde` "1", `requests` ">=2") was denied as a nonexistent "hallucinated" version — a false block on the primary agent workflow, on all three registries. Resolve the requirement per ecosystem to the best matching published version (semver for npm/cargo, PEP 440 specifiers for PyPI); only a concrete exact version that is genuinely absent is still flagged. Also compare canonical names in denylist/allowlist/dependency-confusion matching so an equivalent spelling (case, and `-`/`_`/`.` per PyPI/crates.io rules) cannot bypass a rule (e.g. denylisting `evil-pkg` now also blocks `evil_pkg`).
npm and PyPI manifests emitted local/git/URL dependencies as if they were registry packages, producing false "does not exist" findings for a project's own path/git/private deps (cargo already filtered these). Skip `file:`, git, tarball-URL, and `workspace:`/`link:` (npm) and `path`/`git`/`url` (Poetry) and PEP 508 `name @ url` dependencies; resolve npm aliases (`npm:real-pkg@1.2.3`) to the real target. PyPI requirement lines were split by operator list-order, so multi-specifier lines like `torch>1.9,<2.0` and `numpy!=1.24.0,>=1.20` were mis-split and silently dropped. Split on the leftmost operator instead.
get_info did not set server_info, so rmcp reported its own crate name and version (`rmcp`/<rmcp version>) to every MCP host. Advertise the crate's own name and version instead.
The npm and PyPI clients attached a private-registry bearer token unconditionally, while the base URL is env-overridable to http://, so a token could be sent in cleartext to a remote mirror. Gate the token on HTTPS (or an http loopback host for local testing), mirroring the audit and remote-config guards. The token is still sent only to the metadata host, never to the downloads or popularity hosts.
Fix the README rmcp badge (1.x) and the decision-payload metadata note (advisories are evidence, not metadata). Run clippy and test with --workspace so every crate is gated in CI. Add a 1.0.0 CHANGELOG and ignore stray .tmp/.cache/WORKFLOW.md.
Coverage Report
Per-file coverage (top 40 by missed lines)
|
Contributor
There was a problem hiding this comment.
Pull request overview
Prepares safe-pkgs for a 1.0.0 release by fixing pre-1.0 audit blockers across version resolution, dependency parsing, security fail-closed behavior, and release/CI metadata—improving correctness for common real-world dependency specs across npm, PyPI, and crates.io.
Changes:
- Add ecosystem-aware version resolution (
resolve_version_for) so ranged/partial versions don’t get incorrectly treated as nonexistent. - Harden policy matching via canonicalized package-name equivalence and skip non-registry dependency sources in npm/PyPI parsers.
- Improve release hygiene (1.0.0 bump, metadata, CI
--workspace, changelog) and MCP server identity reporting.
Reviewed changes
Copilot reviewed 14 out of 16 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
src/mcp/server.rs |
Advertises safe-pkgs identity/version via MCP server info. |
src/config/mod.rs |
Canonicalized matching for dependency-confusion config rules. |
src/checks.rs |
Uses ecosystem-aware canonicalization and version resolution in policy checks. |
README.md |
Updates rmcp badge/version note and metadata note. |
crates/registry/pypi/src/registry.rs |
Applies transport-safe token attachment for PyPI registry auth. |
crates/registry/pypi/src/lockfile.rs |
Skips non-registry Poetry/PEP508 URL deps; fixes multi-specifier parsing. |
crates/registry/npm/src/registry.rs |
Applies transport-safe token attachment for npm registry auth. |
crates/registry/npm/src/lockfile.rs |
Skips non-registry npm specs; supports npm alias targets. |
crates/http/src/lib.rs |
Adds helper to prevent sending bearer tokens over non-secure transports. |
crates/core/src/lib.rs |
Adds canonical name equivalence + ecosystem-aware version resolution logic + tests. |
crates/core/Cargo.toml |
Adds semver dependency required by new version-resolution code. |
CHANGELOG.md |
Adds 1.0.0 changelog including known limitation documentation. |
Cargo.toml |
Bumps workspace to 1.0.0; adds crate metadata and workspace rust-version/license/repository. |
Cargo.lock |
Updates lockfile for 1.0.0 versions and new deps. |
.gitignore |
Ignores .cache, .tmp, and WORKFLOW.md. |
.github/workflows/ci.yml |
Runs clippy/tests for the entire workspace. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+337
to
+340
| // Bare "owner/repo" GitHub shorthand: contains a slash, no protocol, and is not a | ||
| // scoped "@scope/name" registry package. | ||
| !spec.starts_with('@') && spec.contains('/') && !spec.contains(':') | ||
| } |
Comment on lines
+63
to
+77
| pub fn token_for_transport(base_url: &str, token: Option<String>) -> Option<String> { | ||
| let token = token?; | ||
| if let Ok(url) = reqwest::Url::parse(base_url.trim()) { | ||
| let is_https = url.scheme().eq_ignore_ascii_case("https"); | ||
| let is_http_loopback = url.scheme().eq_ignore_ascii_case("http") | ||
| && url.host_str().is_some_and(is_loopback_host); | ||
| if is_https || is_http_loopback { | ||
| return Some(token); | ||
| } | ||
| } | ||
| tracing::warn!( | ||
| "skipping registry bearer token: base URL is not HTTPS (would send credentials in cleartext)" | ||
| ); | ||
| None | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Prepares the project for a 1.0.0 release. Found during a multi-dimension pre-1.0 audit (parsers, crash-safety, security/fail-closed, check logic, release hygiene); every fix below was verified live against the built binary.
Blockers
check_package rejected ranged/partial versions as "hallucinated." The primary agent tool forwarded the raw version to an exact-match lookup, so
react"18", "^4.17.0",serde"1",requests">=2" were denied as nonexistent, on all three registries — a false block on the most common workflow ("add react 18"). Versions are now resolved per ecosystem to the best matching published version; only a concrete exact version that is genuinely absent is still flagged. Verified:check_package(react,"18")now resolves toreact@18.3.1and allows.MCP server advertised the wrong identity.
get_infodid not setserver_info, so every MCP host sawrmcp/<rmcp version>. Now advertisessafe-pkgs/1.0.0. Verified over stdio.Version bump. Workspace version
0.2.0→1.0.0(single source; propagates to all crates).Correctness (parsers)
file:, git, tarball URL,workspace:/link:) and PyPI (Poetrypath/git/url, PEP 508name @ url) dependencies are skipped rather than looked up on the public registry and reported as nonexistent — matching cargo's existing behavior. npm aliases (npm:real-pkg@1.2.3) now audit the real target. Verified: apackage.jsonwithfile:/github:deps audits only the real packages.torch>1.9,<2.0andnumpy!=1.24.0,>=1.20were mis-split by operator list-order and silently skipped; now split on the leftmost operator. Verified: all three deps parsed.Security
-/_/.per PyPI/crates.io) could bypass a rule. Verified: denylistingmy-internalnow blocksmy_internalon PyPI.Release hygiene
license = "MIT",rust-version = "1.85",repository,description,keywords,categories,publish = false(binary distribution).clippyandtestnow run with--workspace(previously only the root package was gated).CHANGELOG.md; ignored stray.tmp//.cache//WORKFLOW.md.Known limitation (documented, not fixed here)
A lockfile pinning the same package at multiple versions (e.g.
windows-sys0.48 and 0.52 in aCargo.lock) is audited at a single version; the other pinned version is not evaluated. This is an architectural change to the dependency model and is deferred to a focused follow-up (noted in CHANGELOG). All other audited findings were either fixed above or confirmed non-issues (no reachable panics, fail-closed audit trail, correct risk aggregation).Testing
cargo fmt/clippy --workspace --all-targets -D warnings/cargo test --workspace— all green (215 tests; new regression tests for version resolution, name canonicalization, npm non-registry filtering, and PyPI multi-specifier parsing).safe-pkgsv1.0.0 binary and the MCP server.