deps-cli
Run deps-lsp's dependency-health checks from the command line, in CI, or in a shell script — no editor required.
This crate is part of the deps-lsp workspace. deps-cli check walks a workspace, routes every manifest it finds through the same 14-ecosystem
classification pipeline (deps-engine) that powers deps-lsp's hover and diagnostics, and
reports outdated/yanked/vulnerable/unsatisfiable/deprecated/license/mutable-ref-pin findings as
a table, as JSON, or as SARIF 2.1.0, with a CI-friendly exit code. deps-cli update is the
write-back counterpart: it plans and applies version-requirement edits to one manifest.
[!IMPORTANT]
deps-cliimplements no classification logic of its own — every verdict comes from the exact functiondeps-lspcalls for its LSP diagnostics, so adeps-cli checkresult and an editor's diagnostics for the same manifest never disagree.
Features
- Security-hardened workspace walk — routes every discovered file through the same
EcosystemRegistrythe LSP server uses, across all 14 supported ecosystems (Cargo, npm, PyPI, Go, Bundler, Dart, Maven, Gradle, Swift, Composer, NuGet, Deno, GitHub Actions, GitLab CI/CD).checkdoes not honor.gitignore/.ignoreby default, because in a CI gate (git checkout && deps-cli check .against an untrusted fork PR) both files are attacker-controlled input — a one-line addition to either would otherwise silently remove a manifest from the scan with no warning and exit code0. A compiled-in denylist (node_modules,target,vendor,.venv, and other common dependency/build/VCS directories) still keeps the scan fast and on-signal without depending on either file. Pass--respect-gitignoreto restore standard.gitignore/.ignoreawareness when scanning a target you trust as much as your owndeps.toml. A manifest reachable only through a symlink is detected and reported the same way regardless of this flag; pass--follow-symlinksto also resolve and scan it, bounded so it can never escape the walked root or loop forever. - Table, JSON, or SARIF output — a human-readable table (default) grouped by file and
severity, a versioned JSON document for machine consumption, or a SARIF 2.1.0 document for
github/codeql-action/upload-sarifand other SARIF consumers - Configurable failure policy —
--fail-onpicks which finding categories make the run exit non-zero, so a pipeline can gate on vulnerabilities without failing on merely-outdated dependencies - CI-safe by construction — a
deps.tomlauto-discovered from the scanned repository itself can never weaken what--fail-onobserves or redirect credentials to an attacker host; only an explicitly-passed--configis trusted with the full policy (see Configuration) updatesubcommand — plans and atomically writes version-requirement edits for one manifest's outdated or OSV-vulnerable dependencies, with--dry-run,--packageselection, andtable/jsonoutput (seeupdatesubcommand)- OSV-checked upgrade targets — an upgrade target (including a cooldown fallback) is
independently OSV-verified before
updatewrites it, and a flagged or unverified one is refused rather than silently substituted - Freshness cooldown —
updateskips a version published withinfreshness.cooldown_secs(default 3 days) and targets the newest already-cooled-down, verified candidate instead - Safe rewrites — an unresolved version placeholder (
${VAR},$(VAR),%VAR%,self.version, string interpolation) is never overwritten by a rewrite - Offline mode —
--offlineserves only already-cached registry data, useful for air-gapped CI runners or fast local iteration
Installation
From crates.io
[!TIP] Use
cargo binstall deps-clifor faster installation without compilation.
Install script
|
Detects your OS and CPU architecture, downloads the matching release archive, verifies its
SHA256 checksum, and installs deps-cli to ${CARGO_HOME:-~/.cargo}/bin (falling back to
~/.local/bin) — no Rust toolchain required. Pin a release with --tag <version> (or the
DEPS_CLI_VERSION env var); override the install directory with --install-dir <dir> (or
DEPS_CLI_INSTALL_DIR).
[!IMPORTANT] Windows is not supported by the script — download the
.ziprelease asset from the table below instead.
Pre-built binaries
Download from GitHub Releases:
| Platform | Architecture | Binary |
|---|---|---|
| Linux | x86_64 (glibc) | deps-cli-x86_64-unknown-linux-gnu |
| Linux | aarch64 (glibc) | deps-cli-aarch64-unknown-linux-gnu |
| Linux | x86_64 (musl) | deps-cli-x86_64-unknown-linux-musl |
| Linux | aarch64 (musl) | deps-cli-aarch64-unknown-linux-musl |
| macOS | x86_64 | deps-cli-x86_64-apple-darwin |
| macOS | Apple Silicon | deps-cli-aarch64-apple-darwin |
| Windows | x86_64 | deps-cli-x86_64-pc-windows-msvc.exe |
| Windows | ARM64 | deps-cli-aarch64-pc-windows-msvc.exe |
Docker
The GitHub Action image (ghcr.io/bug-ops/deps-lsp-github-action) also
bundles a prebuilt deps-cli binary, SHA256-verified against the matching GitHub release — no
Rust toolchain, and nothing to trust beyond the image itself. Its default entrypoint is fixed to
the Action's own check --format sarif contract, so override it to run deps-cli directly:
linux/amd64 and linux/arm64 only. Tags: latest, X, X.Y, X.Y.Z — see
Image tags.
From source
As a workspace dependency
To drive the check pipeline programmatically:
[]
= "2.0"
[!IMPORTANT] Requires Rust 1.98 or later.
Usage
# Check the current directory, human-readable table output (default)
# Check specific paths
# Fail the run only on real vulnerabilities and unsatisfiable requirements
# Machine-readable output for a CI step that parses results
# SARIF 2.1.0 output for github/codeql-action/upload-sarif
# CI-friendly: never touch the network, use only what's already cached
# Loosen the freshness window for this run only
# Use an explicit, fully-trusted config file
# Restore .gitignore/.ignore awareness (only for a fully-trusted scan target — see Features)
# Resolve and scan a symlinked manifest (only for a fully-trusted scan target — see Features)
Exit codes
CI pipelines script against the exit code directly:
| Code | Meaning |
|---|---|
0 |
Clean — no finding matched the --fail-on policy |
1 |
Policy violation — at least one finding matched --fail-on |
2 |
Execution error — a registry was unreachable, a deps.toml/manifest failed to parse, or a walked path was unreadable |
A real policy violation (1) always takes precedence over an unrelated execution error
elsewhere in the run — one malformed manifest in a large workspace never hides a genuine
--fail-on hit behind a less specific 2.
--fail-on categories
outdated, yanked, vulnerable, unsatisfiable, mutable-ref, license, deprecated.
Defaults to vulnerable,yanked,unsatisfiable when the flag is omitted. A finding that matches
none of these seven categories (for example, an unresolved or unknown package) is always
reported in the output but can never fail a run through this flag.
update subcommand
deps-cli update <MANIFEST> reads exactly one manifest, plans version-requirement edits for
its outdated (default mode) or OSV-vulnerable (--security-only) dependencies, and writes
them back atomically — the non-interactive counterpart to deps-lsp's "update all outdated"
code lens and vulnerability-fix quick action:
# Update every outdated dependency
# Only the named dependencies (repeatable)
# Only OSV-vulnerable dependencies, targeting each advisory's own recommended fix
# Plan without writing, inspecting the machine-readable result
# Honor [update].ignore rules from an explicit config, using cached registry data only
Flags: --package <NAME> (repeatable), --security-only, --dry-run, --format table|json, --config <PATH>, --offline, and --cooldown <DURATION> (--cooldown has no
effect under --security-only).
- Default mode honors the freshness cooldown (default 3 days) even with no flags: a version published inside the window is replaced by the newest already-cooled-down, OSV-verified candidate that does not drop below the in-use version (or, with no lock file, the declared requirement), or the dependency is skipped when none exists. A flagged or unverified candidate is refused and the run exits non-zero.
--security-onlyrewrites to each advisory's recommended fix, independently re-verified against OSV, and classifies every vulnerable dependency asapplied,requires-lockfile-update, orunfixable. It overrides every[update].ignorerule.[update].ignore({ name = "tokio", update_types = ["major"] }) is honored only from an explicit--config <path>—updatenever auto-discovers adeps.toml.- Exit codes:
0all selected updates applied or deliberately skipped,1at least one wanted fix could not be applied,2execution error. A non-zero exit does not imply the manifest is unmodified; inspect each item'soutcomein--format json.
See the update usage mdBook
section for the full reference.
Pre-commit hook
This repository ships a .pre-commit-hooks.yaml at its root
defining a deps-lsp-check hook (language: system, entry: deps-cli check), per FR-017.
[!NOTE]
language: systemmeans pre-commit does not install anything for this hook —deps-climust already be on yourPATH. The fastest way to get it there is the install script (curl -fsSL .../install-deps-cli.sh | sh);cargo install deps-cliandcargo install --path crates/deps-clifrom a checkout also work. An earlierlanguage: rusthook definition could not install from this repository's root at all, since it is a virtual workspace manifest (no[package]) — see #1074.
# .pre-commit-config.yaml
repos:
- repo: https://github.com/bug-ops/deps-lsp
rev: <tag>
hooks:
- id: deps-lsp-check
GitHub Action
crates/github-action wraps deps-cli check --format sarif as
a Docker-based action. It writes a SARIF file but does not upload it — wire
github/codeql-action/upload-sarif after it in your own workflow. Pin uses: to a released
tag rather than @main — see the action's own README for why:
- uses: bug-ops/deps-lsp/crates/github-action@v2.0.0
id: deps-check
with:
fail-on: vulnerable,yanked,unsatisfiable
- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: ${{ steps.deps-check.outputs.sarif-file }}
- name: Fail the build on a policy violation
if: steps.deps-check.outputs.exit-code == '1'
run: exit 1
The action itself only fails the job on an execution error — any exit code other than 0
(clean) or 1 (a --fail-on category matched), including a deps-cli panic or a refused
SARIF output path. A --fail-on policy violation (exit-code 1) does not fail the step;
sarif-file is usually still produced and uploaded then too, though it may be unset even at
exit-code 0 or 1 if the output path couldn't be written. It is your own workflow's
decision — shown above — whether to fail the build on a policy violation. See
crates/github-action/README.md for the full input/output
reference.
[!NOTE] Piping stdout directly to a file, as in
deps-cli check --format sarif > results.sarifabove, is at risk of truncation ifdeps-clicrashes mid-write — a partial SARIF file handed toupload-sariffails confusingly rather than cleanly. The GitHub Action wrapper does not have this risk: it capturesdeps-cli's exit code before deciding whethersarif-fileis set, so a crash leavessarif-fileunset instead of pointing at a truncated file (#1063). Prefer the Action, or apply the same exit-code check yourself, when scripting the CLI form directly in CI.
Configuration
deps-cli reuses deps-lsp's own PolicyConfig schema,
loaded from a deps.toml file:
[]
= true
= true
[]
= 259200 # 3 days, Dependabot's default
[]
= false # opt-in deps.dev cooldown signals; explicit --config only
[]
= [{ = "tokio", = ["major"] }] # honored only via --config
[]
= false
[]
= ["MIT", "Apache-2.0", "BSD-3-Clause"]
deps-cli looks for ./deps.toml (relative to the walked root) when --config is not given.
[!WARNING] An auto-discovered
deps.toml— one found by this default lookup, not passed explicitly via--config— has itsregistries,network,gossip, anddiagnostics.*_enabledsections (and a few other gate-relevant fields) reset to their safe defaults before use. This is deliberate: the repository a CI job is checking is not a trusted source for the policy that judges it, and a checked-indeps.tomlon an attacker-controlled branch must not be able to disable the check or redirect a registry credential to another host. Any section ignored this way is named in a warning on stderr. Only a config path given explicitly via--config(the operator's own choice, not the scanned repository's) is trusted in full.