Package safety checks for AI agents before install.
Rust MCP server + CLI with allow/deny decisions, risk scoring, and audit logs.
|
|
safe-pkgs checks package risk before dependency actions and returns one machine-enforceable decision.
Decision payload includes:
allow:trueorfalserisk:low | medium | high | criticalreasons: human-readable findingsevidence: structured findings (kind, stableid,severity,message,facts)metadata: package context (latest, publish date, downloads, advisories)fingerprints: deterministic hashes (config,policy)
Install once:
cargo install --path . --lockedRun MCP server:
safe-pkgs serve --mcpRun a one-off audit:
safe-pkgs audit /path/to/project-or-lockfile
safe-pkgs audit /path/to/requirements.txt --registry pypiWindows MCP hosts (Claude Desktop, etc.) should use:
safe-pkgs-mcp.exesafe-pkgs does not require a paid plan, hosted account, or API key for built-in checks.
- Runs locally as a Rust binary (MCP server or CLI).
- Uses public package/advisory endpoints by default:
- npm registry + npm downloads API + npms popularity index
- crates.io API
- PyPI JSON API + pypistats + top-pypi index
- OSV advisory API
- Stores cache and audit logs locally on your machine.
Supported registries:
npm(default)cargo(crates.io)pypi(Python packages)
View support map:
- Command:
safe-pkgs support-map - Docs:
docs/check-support-map.md
Global file:
~/.config/safe-pkgs/config.toml
Project override:
.safe-pkgs.toml(merged on top of global)
Minimal example:
min_version_age_days = 7
min_weekly_downloads = 50
max_risk = "medium"
[cache]
ttl_minutes = 30
[allowlist]
packages = ["my-internal-pkg"]
[denylist]
packages = ["event-stream@3.3.6"]Full configuration schema:
docs/configuration-spec.md
macOS/Linux:
{
"servers": {
"safe-pkgs": {
"type": "stdio",
"command": "/path/to/safe-pkgs",
"args": ["serve", "--mcp"]
}
},
"inputs": []
}Windows (no console window):
{
"servers": {
"safe-pkgs": {
"type": "stdio",
"command": "safe-pkgs-mcp.exe"
}
},
"inputs": []
}{
"allow": true,
"risk": "low",
"reasons": [
"lodash@3.10.1 is 1 major version behind latest (4.17.21)"
],
"evidence": [
{
"kind": "check",
"id": "staleness.behind_latest",
"severity": "low",
"message": "lodash@3.10.1 is 1 major version behind latest (4.17.21)",
"facts": {
"package_name": "lodash",
"resolved_version": "3.10.1",
"latest_version": "4.17.21",
"major_gap": 1
}
}
],
"fingerprints": {
"config": "c7d9f5b8b9a8f2a9f6b1f42f0e8e8c8a63f2b2ef8fdde1f3cd9ea4f5a2c08a0b",
"policy": "fca103ee4fd5b86595a6a6e933f8a5f87db0ce087f80744dc1ea9cdbf58f7a6f"
},
"metadata": {
"latest": "4.17.21",
"requested": "3.10.1",
"published": "2015-08-31T00:00:00Z",
"weekly_downloads": 45000000
}
}Input lockfile (package-lock.json) used for this example:
{
"name": "demo",
"lockfileVersion": 2,
"dependencies": {
"react": {
"version": "18.2.0",
"dependencies": {
"loose-envify": {
"version": "1.4.0"
}
}
}
}
}Audit output:
{
"allow": true,
"risk": "low",
"total": 2,
"denied": 0,
"packages": [
{
"name": "react",
"requested": "18.2.0",
"allow": true,
"risk": "low",
"reasons": [],
"evidence": []
},
{
"name": "loose-envify",
"requested": "1.4.0",
"allow": true,
"risk": "low",
"reasons": [],
"evidence": [],
"dependency_ancestry": {
"paths": [
{ "ancestors": ["react"] }
]
}
}
],
"fingerprints": {
"config": "<sha256>",
"policy": "<sha256>"
}
}paths[].ancestors lists only ancestors (root to immediate parent), excluding the package itself.
For direct dependencies, dependency_ancestry is omitted.
evidence.id is stable and machine-oriented:
- Built-in checks:
<check_id>.<reason_code>(example:staleness.behind_latest) - Custom rules:
custom_rule.<rule_id>(example:custom_rule.low-downloads) - Policy/runtime items: explicit IDs (example:
denylist.package,risk.medium_pair_escalation)
- Fail-closed behavior: check/runtime failures are surfaced and do not silently allow installs.
- Local audit trail: append-only audit log for decision review.
- Deterministic policy context: responses include
policy_snapshot_version, config and policy fingerprints, and enabled check set. - Local cache: SQLite cache keyed by policy fingerprint + package tuple with TTL expiry.
- Getting started:
docs/getting-started.md - Full config schema:
docs/configuration-spec.md - Registry check matrix:
docs/check-support-map.md - Cache and policy fingerprinting:
docs/cache-deep-dive.md
Prioritized planned work:
- Dependency confusion defenses for internal/private package names
- Policy simulation mode (
what-if) without enforcement - Metrics/log schema for latency, cache hit ratio, and registry error rates
- Support remote audit storage backends
- Support remote config sources (GitHub repo, HTTP endpoint, etc.)
- Support for private registries
- Policy waivers with expiry
- Package provenance checks (where ecosystem metadata supports it)
- Publisher trust signals (account age, maintainer churn, ownership changes)
- Performance/scale improvements (request coalescing + bounded concurrency for large lockfiles)
- NVD advisory enrichment
- Optional Snyk advisory provider
- Socket.dev integration
- GitHub Actions integration for CI auditing
- Registry-driven MCP schema and docs generation (single source of truth)
- HTTP Streamable MCP server option
- More validated editor config examples
- Git hook integration for pre-commit checks
cargo fmt --all -- --check
cargo clippy --all-targets -- -D warnings
cargo testInstall:
rustup component add llvm-tools-preview
cargo install cargo-llvm-covSummary:
cargo llvm-cov --workspace --all-features --summary-onlyHTML report:
cargo llvm-cov --workspace --all-features --htmlReport path:
target/llvm-cov/html/index.html
pip install mkdocs mkdocs-material
mkdocs serveSet SAFE_PKGS_EVALUATION_TIME to an RFC3339 timestamp to force a fixed policy evaluation time (useful for replay/debug runs):
SAFE_PKGS_EVALUATION_TIME=2026-01-01T00:00:00Z safe-pkgs audit /path/to/project$env:SAFE_PKGS_EVALUATION_TIME = "2026-01-01T00:00:00Z"
safe-pkgs audit C:\path\to\project
