depshieldx
depshieldx is a safer wrapper around package install and scan workflows, for PyPI (Python), npm/yarn/pnpm (JavaScript), Cargo/crates.io (Rust), Go modules, Maven/Maven Central (Java), NuGet/NuGet.org (.NET), Pub/pub.dev (Dart/Flutter), RubyGems/rubygems.org (Ruby), and Composer/Packagist (PHP) -- see npm / yarn / pnpm Support, Cargo / crates.io Support, Go Modules Support, Maven / Maven Central Support, NuGet Support, Pub Support, RubyGems Support, and Composer Support for the ecosystem-specific details.
Before installing, it resolves the full package set, checks provenance for the exact artifacts that would be used, queries four vulnerability sources for the resolved versions, and can optionally run a deeper Docker + Trivy validation path with real behavioral tracing of the sandboxed install. Every completed install or scan also writes signed local receipt JSON files.
Installation
Install the published package from PyPI:
python -m pip install depshieldx
If your machine has multiple Python versions, use a Python 3.11.4+ interpreter explicitly:
python3.11 -m pip install depshieldx
Standalone binaries (no Python runtime required to run depshieldx itself)
Each GitHub Release also includes a standalone binary per platform (Windows x64, macOS x64/arm64, Linux x64) built with PyInstaller. Download it, put it on PATH, and run it directly -- no pip install and no separate Python interpreter needed just to launch depshieldx.
macOS / Linux -- move the downloaded binary somewhere already on PATH and make it executable:
chmod +x depshieldx-macos-arm64 # or depshieldx-linux-x64, depshieldx-macos-x64
sudo mv depshieldx-macos-arm64 /usr/local/bin/depshieldx
depshieldx --help
Windows -- rename the download to depshieldx.exe, then add its folder to PATH (one time only). In PowerShell, replace C:\tools\depshieldx below with wherever you put the file:
[Environment]::SetEnvironmentVariable("Path", $env:Path + ";C:\tools\depshieldx", "User")
Close and reopen your terminal for the change to take effect, then run depshieldx --help to confirm it's found. Without a GUI/PowerShell, the same thing is done through Settings -> System -> About -> Advanced system settings -> Environment Variables, editing the Path entry under "User variables" to add the folder, then reopening any open terminal windows.
That said, depshieldx doesn't reimplement pip or npm -- it wraps the real tools for actually resolving and installing packages, in either distribution:
- Using it against PyPI packages still requires a real Python +
pipon the host, standalone binary or not. If the binary can't find one onPATH, it fails with a clear error rather than doing something unsafe. - Using it against npm/yarn/pnpm packages only requires Node.js/
npmon the host -- no Python needed at all, in either distribution. - Using it against Cargo/crates.io packages only requires a Rust toolchain (
cargo) on the host -- no Python needed at all, in either distribution. - Using it against Go modules only requires a Go toolchain (
go) on the host -- no Python needed at all, in either distribution. - Using it against Maven/Maven Central packages only requires a Java + Maven toolchain (
mvn) on the host -- no Python needed at all, in either distribution. - Using it against NuGet/NuGet.org packages only requires a .NET SDK (
dotnet) on the host -- no Python needed at all, in either distribution. - Using it against Pub/pub.dev packages only requires a Dart SDK (
dart) on the host -- no Python needed at all, in either distribution. Only the standalone Dart SDK is needed, not the full Flutter SDK. - Using it against RubyGems/rubygems.org packages only requires Ruby with Bundler (
bundle) on the host -- no Python needed at all, in either distribution. Bundler ships as a default gem on modern Ruby installs. - Using it against Composer/Packagist packages only requires PHP with Composer (
composer) on the host -- no Python needed at all, in either distribution. - Deep mode, for any ecosystem, additionally requires Docker.
Project links:
- PyPI: https://pypi.org/project/depshieldx/
- Docs: https://tee-wealth001.github.io/depshieldx/
- Source: https://github.com/tee-wealth001/depshieldx
What It Does
- resolves the full dependency set before installation, for PyPI, npm/yarn/pnpm, Cargo/crates.io, Go modules, Maven/Maven Central, NuGet/NuGet.org, Pub/pub.dev, RubyGems/rubygems.org, or Composer/Packagist
- checks provenance for the selected release artifacts (PyPI attestations, or npm's SLSA provenance attestations -- both verified cryptographically via real Sigstore bundle verification, not just presence checks; crates.io and Go modules have no equivalent per-package attestation infrastructure, so Cargo and Go packages get structural checks instead -- yanked/retracted-release status, registry metadata -- rather than cryptographic verification. Go's checksums are still verified cryptographically, just transparently inside the
gotoolchain itself, not as a separatedepshieldxstep. Maven combines checksum + structural PGP-presence checks with real cryptographic Sigstore verification where a publisher has opted in, since January 2025. NuGet combines a real cryptographic checksum check against the registry's own published hash with structural repository-signature presence -- NuGet.org has no Sigstore equivalent, but it does unconditionally repository-sign every package with an X.509/Authenticode signature. Pub combines a real cryptographic checksum check against pub.dev's own published hash with structural discontinued/retracted-status checks -- pub.dev has no signing scheme of its own at all. RubyGems combines a real cryptographic checksum check against rubygems.org's own published hash with a structural yank signal -- rubygems.org has no default, always-present signing scheme either. Composer/Packagist has the weakest provenance story of any ecosystem here:dist.shasumis empty for essentially every real package, so there is usually no checksum to verify at all -- the git commitreferencePackagist records for the resolved archive is the only real, always-present content pin, and Packagist has no signing scheme of any kind) - queries 4 vulnerability sources for the resolved package versions:
- OSV
- GitHub Advisories
- CISA KEV
- deps.dev
- supports a deeper Docker + Trivy scan mode, plus real syscall-level behavioral tracing during sandboxed installs, for PyPI, npm/yarn/pnpm, Cargo/crates.io, Go modules, Maven/Maven Central, NuGet/NuGet.org, Pub/pub.dev, RubyGems/rubygems.org, and Composer/Packagist
- writes signed local receipts for installs and scans
Quick Start
Install with the default path:
depshieldx install requests
Run the deeper validation path:
depshieldx install requests --deep
Scan without installing:
depshieldx scan requests
Scan a requirements file:
depshieldx scan -r requirements.txt
Install from pyproject.toml:
depshieldx install --pyproject pyproject.toml
Requirements
depshieldx is safest when the local runtime tools are current:
- Python
3.11.4or newer pip25.3or newer- Docker installed and running for
--deep - Trivy installed for the deeper container scan path
Install local development and release tooling with:
python -m pip install -e ".[dev]"
Platform Support
depshieldx works best where the local Python, pip, Docker, and browser integration are set up cleanly.
- the local UI is localhost-only and uses the Python standard library browser/server stack, so it is the most platform-friendly part of the project
- the core fast scan and install flow is intended to be portable across macOS, Linux, and Windows
- routing now creates a Windows batch shim on Windows and a shell shim on POSIX systems
- deep mode depends on Docker and Trivy, and some of the sandbox internals are still Unix-oriented
Windows support is improving, but macOS and Linux still have the broadest day-to-day coverage in the codebase and docs.
Modes
depshieldx has two modes:
fastdeep
Plain install and plain scan default to fast.
deep is supported for PyPI, npm/yarn/pnpm, Cargo/crates.io, Go modules, Maven/Maven Central, NuGet/NuGet.org, Pub/pub.dev, RubyGems/rubygems.org, and Composer/Packagist input.
Fast mode
Fast mode:
- resolves the exact package versions that would be installed
- checks provenance for the resolved package set
- queries OSV, GitHub Advisories, CISA KEV, and deps.dev
- blocks if any resolved package or dependency is reported as vulnerable by the blocking sources
Fast mode does not use Docker or Trivy.
Deep mode
Deep mode does everything in fast mode first, then:
- installs the resolved package set in Docker
- runs Trivy on the sandboxed install result
- blocks if the Docker environment is unavailable or Trivy returns blocking findings
For install --deep, the host install only happens after the fast checks and the Docker + Trivy stage both pass.
depshieldx shells out to the local pip (or, for npm, the local npm; for Cargo, the local cargo; for Go, the local go; for Maven, the local mvn; for NuGet, the local dotnet; for Pub, the local dart; for RubyGems, the local bundle; for Composer, the local composer) for resolution, download, and host install steps, so keeping those tools up to date is part of the security model.
For PyPI, deep mode also traces filesystem writes, subprocess launches, and network access in-process during the sandboxed install (via sys.addaudithook) and actively blocks disallowed ones in real time. For npm, which has no equivalent in-process hook, behavioral tracing instead wraps the sandboxed npm install in strace, observing the same categories of activity across the whole install (including lifecycle scripts) rather than blocking individual syscalls live -- filesystem/network isolation is still enforced by the container itself either way. The npm sandbox runs in a small node:20 + strace image depshieldx builds and caches locally the first time it's needed.
Cargo/crates.io behavioral tracing works the same way as npm's: the sandboxed cargo build --offline runs wrapped in strace, observing filesystem, process, and network activity (including build.rs scripts and proc-macros) rather than blocking individual syscalls live. The Cargo sandbox runs in a small rust:1-slim + strace image built and cached locally the first time it's needed. One mechanical difference from npm/PyPI: since there's no new installed output that needs to survive the sandbox, Trivy scans a host-side vendor directory of the resolved .crate files (checksum-verified against crates.io) built before the container runs, rather than a bind-mounted install destination.
Go modules behavioral tracing works the same way: the sandboxed go build runs wrapped in strace, observing filesystem, process, and network activity (init() functions and //go:generate-produced code are Go's equivalent of Cargo's build.rs/proc-macros) rather than blocking individual syscalls live. The Go sandbox runs in a small golang:1-bookworm + strace image built and cached locally the first time it's needed. Like Cargo, Trivy scans a host-side go.mod/go.sum pair built before the container runs -- Trivy reads Go's manifest files natively, needing no extracted source tree the way Cargo's vendor directory does. One Go-specific wrinkle: unlike Cargo (which compiles every declared dependency regardless of use), Go only compiles what's actually imported, so a scratch program blank-imports every resolved module to force real compilation -- and not every module has an importable root package (some are subpackage-only, e.g. golang.org/x/crypto). Those are skipped from tracing rather than failing the whole build, and listed in the full JSON report's skipped_modules.
Maven behavioral tracing is different in one fundamental way: unlike Cargo's build.rs or Go's init(), a jar consumed as a plain Maven dependency has no code that runs automatically just by being resolved, or even sitting on the compile classpath. The one real exception is an annotation processor registered via META-INF/services (Lombok, MapStruct, Dagger, and similar) -- javac auto-discovers and invokes it during any compile it's present for, regardless of whether the compiled source actually uses its target annotations. So the sandboxed mvn compile (against a trivial scratch source file, wrapped in strace) traces real activity for that class of dependency, and genuinely zero extra activity for the (large majority) of ordinary libraries that register no processor -- an accurate verdict, not a coverage gap. The Maven sandbox runs in a small maven:3-eclipse-temurin-21 + strace image built and cached locally the first time it's needed, with Maven's own default-lifecycle plugin set pre-warmed into the image at build time (a real, Maven-specific requirement: compile is itself a Maven plugin goal, needing dozens of plugin/dependency jars resolved before it can run at all, which the sandbox can't fetch once it's offline). Like Cargo/Go, Trivy scans a host-side scratch pom.xml built before the container runs, listing every resolved coordinate as a pinned direct dependency.
NuGet behavioral tracing has the broadest code-execution surface of the four compiled ecosystems: a .nupkg consumed as a plain PackageReference has no code that runs automatically during dotnet restore alone, but any package shipping a build/*.targets or build/*.props file gets it imported and evaluated during dotnet build -- not a narrow processor-registration mechanism like Maven's, but MSBuild's own general build-time extensibility point, available to any package that uses it. So the sandboxed dotnet build (against a trivial scratch source file, wrapped in strace) traces this real, broader surface. The NuGet sandbox runs in a small mcr.microsoft.com/dotnet/sdk:8.0 + strace image built and cached locally the first time it's needed -- no plugin pre-warming needed, unlike Maven's compile goal. Like Cargo/Go/Maven, Trivy scans a host-side packages.lock.json built before the container runs (via a real, networked dotnet restore against the resolved set) -- Trivy's NuGet support needs a real lock file, detecting nothing from a bare .csproj.
Pub's own real code-execution surface is Dart's official Native Assets "hooks" feature: a package can ship a hook/build.dart file (a real Dart entry point, typically used to compile a native C/Rust library) that the toolchain invokes for the root package and every transitive dependency during dart run/dart test -- confirmed this fires even when nothing actually imports the package, the same "presence in the dependency graph is enough" pattern Maven's/NuGet's own surfaces have. dart pub get alone never triggers hooks, the same "resolve never executes code" property every other ecosystem here has -- so the sandboxed dart run (against a trivial scratch entry-point file, wrapped in strace) is what actually traces this surface; dart compile exe was deliberately not used instead, since it refuses to run hooks at all. The Pub sandbox runs in a small dart:3 + strace image built and cached locally the first time it's needed -- no plugin pre-warming needed. Like the other four, Trivy scans a host-side pubspec.lock built before the container runs (via a real, offline dart pub get against the resolved set's own freshly-built local package cache) -- Trivy's Pub support needs a real lock file, the same requirement NuGet's own support has.
RubyGems behavioral tracing looks different from every other ecosystem here in one fundamental way: a native-extension gem's own extconf.rb build script runs as an unavoidable, inherent part of installing the gem itself, not a separate later run/build step -- so there's no clean split between "resolve/restore, unstraced" and "run/build, straced" the way Pub's pub-get-then-dart-run or NuGet's restore-then-build splits have. Confirmed directly this also means bundle install is never safe to run on the host at all -- unlike cargo fetch --locked/go mod download/dotnet restore/dart pub get, none of which invoke a compiler, bundle install genuinely compiles native code for any gem that ships one. So depshieldx never shells out to bundle install/bundle lock on the host to build the Trivy-facing lockfile; it writes Gemfile.lock directly from the resolution it already has (itself produced by a real, safe bundle lock scratch resolve). The sandboxed bundle install --local -- the same command Docker deep mode's offline-install step already runs -- is what gets wrapped in strace instead, tracing the whole install (including any real native-extension compilation) in one pass. The RubyGems sandbox runs in a small ruby:3 + strace image built and cached locally the first time it's needed -- deliberately not -slim, since it needs to ship a real C toolchain (gcc/make) for that compilation to succeed rather than spuriously fail for one of the ecosystem's most common package shapes (nokogiri, sqlite3, pg, bcrypt, ...).
Composer behavioral tracing targets a narrower, but real and non-opt-in, surface: confirmed directly a plain composer install --no-plugins --no-scripts never executes a dependency's own code at all -- Composer 2.2+ blocks any package containing a Composer plugin by default unless the project explicitly allow-lists it (depshieldx never does), and Composer's script hooks are root-project-only by design, unlike NuGet's build/*.targets or Maven's annotation processors. The real surface is PHP's own "files" autoload mechanism: a package can declare "autoload": {"files": ["bootstrap.php"]}, and confirmed directly that file executes unconditionally the moment anything actually loads the generated vendor/autoload.php -- even with zero explicit reference to the package's own classes -- unlike ordinary PSR-4 class autoloading, which is lazy. So the sandboxed install runs composer install unstraced first (already proven safe), then straces a trivial scratch probe script that does nothing but load the autoloader. The Composer sandbox runs in a small php:8.4-cli + strace image built and cached locally the first time it's needed, with the zip PHP extension and unzip added (confirmed directly the base image ships neither, though Composer falls back to its own less-clean PHP ZipArchive extraction without unzip present) and Composer itself copied from the official composer:2 image.
Install vs Scan
install and scan use the same fast/deep validation logic.
The only difference is:
installinstalls on the host after the checks passscanstops after the checks and does not install anything
This same behavior applies to:
- direct package names
- multiple package names in one command
requirements.txtuv.lockpyproject.toml
npm / yarn / pnpm Support
depshieldx can resolve, check, and install npm packages too, with full fast and deep mode support.
Two ways to point it at npm:
A lockfile in the current directory -- auto-detected by filename, no flag needed:
depshieldx scan --lockfile package-lock.json
depshieldx scan --lockfile yarn.lock
depshieldx scan --lockfile pnpm-lock.yaml
depshieldx install --lockfile package-lock.json
One or more bare package names -- pass --ecosystem npm so depshieldx knows they aren't PyPI names:
depshieldx scan left-pad --ecosystem npm
depshieldx install left-pad --ecosystem npm
depshieldx install left-pad is-odd --ecosystem npm
Bare package-name resolution shells out to the real npm CLI in an isolated temp directory to compute the full, accurate transitive dependency tree, then checks that whole resolved set before installing. Installing pins each requested package to the exact version that was just checked (npm install left-pad@1.3.0), not a floating range, so nothing can drift to a different release between the scan and the install.
If you have the routing shim enabled, npm install <package> is also intercepted automatically and routed through depshieldx install <package> --ecosystem npm -- you don't need to change your muscle memory.
npm/yarn/pnpm now has full functional parity with PyPI: --deep (Docker + Trivy + real behavioral tracing via strace), depshieldx uninstall, and cryptographic provenance verification (real Sigstore bundle verification of npm's SLSA provenance attestations, not just presence checks -- see Provenance And Attestations) are all supported.
What's still explicitly not supported for npm/yarn/pnpm:
requirements.txt/pyproject.toml-style inputs -- those formats are inherently PyPI-specific; use a lockfile or--ecosystem npminstead
Cargo / crates.io Support
depshieldx can resolve, check, and install Cargo crates too, with full fast and deep mode support.
Two ways to point it at Cargo:
A Cargo.lock file in the current directory -- auto-detected by filename, no flag needed:
depshieldx scan --lockfile Cargo.lock
depshieldx install --lockfile Cargo.lock
One or more bare crate names -- pass --ecosystem cargo so depshieldx knows they aren't PyPI names:
depshieldx scan serde --ecosystem cargo
depshieldx install serde --ecosystem cargo
depshieldx install serde tokio --ecosystem cargo
Bare crate-name resolution shells out to the real cargo add CLI against a scratch package in an isolated temp directory to compute the full, accurate resolved dependency set, then checks that whole resolved set before installing.
If you have the routing shim enabled, cargo add <crate> is also intercepted automatically and routed through depshieldx install <crate> --ecosystem cargo -- you don't need to change your muscle memory.
"Install" here means cargo add -- adding the crate(s) to your project's Cargo.toml/Cargo.lock -- not cargo install (installing a binary crate). depshieldx does not currently support installing binary crates.
--deep is supported for Cargo the same way it is for PyPI and npm: the resolved crate set is fetched into a sandboxed container (rust:1-slim + strace) and scanned with Trivy, and the sandboxed cargo build --offline is traced with strace for filesystem, process, and network activity -- see Modes for details. depshieldx uninstall is also supported, via cargo remove.
Provenance checks for Cargo are structural only, not cryptographic: crates.io has no Sigstore/SLSA attestation infrastructure to verify against, unlike PyPI and npm. Checks are limited to things like yanked-release status and registry metadata -- see Provenance And Attestations.
What's still explicitly not supported for Cargo:
- installing binary crates (
cargo install) --depshieldx's Cargo support only covers dependency crates added viacargo add Cargo.toml-as-input -- onlyCargo.lockor bare crate names via--ecosystem cargoare accepted- cryptographic provenance verification -- crates.io has nothing to verify against
Go Modules Support
depshieldx can resolve, check, and install Go modules too, with full fast and deep mode support.
Two ways to point it at Go:
A go.sum file in the current directory -- auto-detected by filename, no flag needed:
depshieldx scan --lockfile go.sum
depshieldx install --lockfile go.sum
One or more bare module paths -- pass --ecosystem go so depshieldx knows they aren't PyPI names:
depshieldx scan github.com/pkg/errors --ecosystem go
depshieldx install github.com/pkg/errors --ecosystem go
depshieldx install github.com/pkg/errors golang.org/x/text --ecosystem go
Bare module-path resolution shells out to the real go get CLI against a scratch module in an isolated temp directory to compute the full, accurate resolved module graph -- go.sum alone can't reconstruct it, since it's a checksum allowlist (it can list more versions of a module than actually ship, every version Minimal Version Selection considered, not just the winner), not the resolved graph itself. --lockfile go.sum resolution reads the sibling go.mod's directory the same way, via go list -m all.
If you have the routing shim enabled, go get <module> is also intercepted automatically and routed through depshieldx install <module> --ecosystem go -- you don't need to change your muscle memory.
"Install" here means go get -- adding the module(s) to your project's go.mod/go.sum -- not go install (installing a binary program; since Go 1.18, go get itself never builds or installs anything). depshieldx does not currently support installing binary programs.
--deep is supported for Go the same way it is for PyPI, npm, and Cargo: the resolved module set is fetched into a sandboxed container (golang:1-bookworm + strace) via a local file-based Go module proxy built on the host, and scanned with Trivy -- Trivy reads go.mod/go.sum natively, needing no extracted source tree the way Cargo's vendor directory does. The sandboxed go build is traced with strace for filesystem, process, and network activity -- see Modes for details. Not every resolved module has an importable root package (some are subpackage-only, like golang.org/x/crypto); those are gracefully skipped from behavioral tracing rather than failing the whole build, and recorded as skipped in the full JSON report. depshieldx uninstall is also supported, via go get <module>@none.
Provenance checks for Go are structural only, not cryptographic: crates.io-style attestation infrastructure doesn't exist for Go modules either. Checksum verification against Go's real checksum-transparency log (sum.golang.org) already happens transparently inside the go toolchain itself during resolution -- what depshieldx checks independently is the retract directive (a module author retracting a previously published version), the closest Go equivalent to PyPI's/Cargo's yanked-release signal -- see Provenance And Attestations.
What's still explicitly not supported for Go:
- installing binary programs (
go install) --depshieldx's Go support only covers dependency modules added viago get go.mod-as-input -- onlygo.sumor bare module paths via--ecosystem goare accepted- cryptographic provenance verification of the kind PyPI/npm have (per-package Sigstore signing) -- Go's real checksum-transparency verification already happens inside the
gotoolchain itself, not as a separatedepshieldx-driven step
Maven / Maven Central Support
depshieldx can resolve, check, and install Maven coordinates too, with full fast and deep mode support.
Maven has no canonical lockfile to auto-detect the way Cargo.lock/go.sum/package-lock.json do, so coordinates are always passed explicitly with --ecosystem maven:
depshieldx scan org.apache.commons:commons-lang3:3.18.0 --ecosystem maven
depshieldx install com.google.code.gson:gson:2.11.0 --ecosystem maven
depshieldx install org.apache.commons:commons-lang3 --ecosystem maven
A bare groupId:artifactId (no version) resolves to that coordinate's latest release via Maven Central's search API. Resolution shells out to the real mvn CLI against a scratch pom.xml in an isolated temp directory (dependency:list) to compute the full, accurate transitive dependency graph, the same reasoning as Cargo's/Go's scratch-project resolve.
"Install" here means fetching every resolved coordinate -- transitive dependencies included, not just the ones you named -- into your local repository (~/.m2), pinned exactly so nothing can drift between scan and install. There is no depshieldx uninstall support for Maven: mvn dependency:get/dependency:resolve only ever download into the local repository, they never edit a pom.xml the way cargo remove/go get @none edit their manifests, so there's nothing well-defined to reverse. There is also no routing shim for Maven -- unlike pip install/npm install/cargo add/go get, Maven has no native CLI verb for "add a dependency" to intercept; dependencies are added by editing pom.xml directly.
--deep is supported for Maven the same way it is for PyPI, npm, Cargo, and Go: the resolved coordinate set (every real .jar and .pom, plus every <parent> POM and <dependencyManagement> BOM import needed to resolve them, walked recursively) is fetched into a sandboxed container (maven:3-eclipse-temurin-21 + strace, with Maven's own default-lifecycle plugin set pre-warmed into the image at build time) and scanned with Trivy -- Trivy reads the scratch pom.xml natively. The sandboxed mvn compile is traced with strace for filesystem, process, and network activity -- see Modes for details. Unlike Cargo's build.rs or Go's init(), a jar consumed as a plain Maven dependency runs no code automatically; the one real exception is an annotation processor registered via META-INF/services, which gets discovered and invoked by javac during any compile it's present for -- ordinary libraries that register no processor correctly trace zero build-time activity.
Provenance checks for Maven combine checksum verification (SHA-256 where published, falling back to SHA-1 for older releases -- MD5 is never trusted), structural PGP-signature presence (Maven Central has required PGP signatures since the 2010s, but with no central root of trust depshieldx can verify against), and real cryptographic Sigstore verification where a publisher has opted in (supported by Maven Central's Publisher Portal since January 2025) -- see Provenance And Attestations.
What's still explicitly not supported for Maven:
depshieldx uninstall-- see above, there's no well-defined manifest edit to reverse- the routing shim -- Maven has no native "add a dependency" CLI command to intercept
pom.xml-as-input -- only explicitgroupId:artifactId[:version]coordinates via--ecosystem mavenare accepted, no lockfile or manifest auto-detection
NuGet Support
depshieldx can resolve, check, and install NuGet packages too, with full fast and deep mode support.
Two ways to point it at NuGet:
A packages.lock.json file in the current directory -- auto-detected by filename, no flag needed:
depshieldx scan --lockfile packages.lock.json
depshieldx install --lockfile packages.lock.json
One or more bare package names -- pass --ecosystem nuget so depshieldx knows they aren't PyPI names:
depshieldx scan Newtonsoft.Json --ecosystem nuget
depshieldx install Newtonsoft.Json --ecosystem nuget
depshieldx install Newtonsoft.Json@13.0.3 --ecosystem nuget
A bare package name (no version) resolves to that package's latest version via NuGet.org's search API. Resolution shells out to the real dotnet restore CLI against a scratch .csproj in an isolated temp directory to compute the full, accurate transitive dependency graph, the same reasoning as Cargo's/Go's/Maven's scratch-project resolve.
If you have the routing shim enabled, dotnet add package <name> is also intercepted automatically and routed through depshieldx install <name> --ecosystem nuget -- you don't need to change your muscle memory.
"Install" here means dotnet add package -- adding the package to your project's .csproj/packages.lock.json. Unlike Maven, depshieldx uninstall is supported for NuGet, via dotnet remove package. Both directions are scoped to exactly one package per invocation -- dotnet add package/dotnet remove package themselves only ever accept a single package name.
--deep is supported for NuGet the same way it is for PyPI, npm, Cargo, Go, and Maven: the resolved package set (every real .nupkg) is fetched into a sandboxed container (mcr.microsoft.com/dotnet/sdk:8.0 + strace) and scanned with Trivy against a real, host-generated packages.lock.json -- Trivy's NuGet support needs a real lock file, it detects nothing from a bare .csproj. The sandboxed dotnet build is traced with strace for filesystem, process, and network activity -- see Modes for details. Unlike Maven's narrow annotation-processor exception, any package shipping a build/*.targets or build/*.props file gets it imported and evaluated during dotnet build, MSBuild's own general build-time extensibility point.
Provenance checks for NuGet combine a real cryptographic checksum check (SHA-512, verified against the exact hash NuGet.org's registration API publishes for that release) with structural repository-signature presence -- NuGet.org has no Sigstore/SLSA equivalent, but unlike Maven's opt-in PGP/Sigstore signing, it does unconditionally repository-sign every package it hosts with an X.509/Authenticode signature -- see Provenance And Attestations.
What's still explicitly not supported for NuGet:
.csproj-as-input -- onlypackages.lock.jsonor bare package names via--ecosystem nugetare accepted- cryptographic chain verification of the repository signature --
depshieldxhas no trust-root/certificate-chain-validation story for X.509 elsewhere, so presence is recorded structurally, the same way Maven's PGP-signature presence is
Pub Support
depshieldx can resolve, check, and install Pub (Dart/Flutter) packages too, with full fast and deep mode support.
Two ways to point it at Pub:
A pubspec.lock file in the current directory -- auto-detected by filename, no flag needed:
depshieldx scan --lockfile pubspec.lock
depshieldx install --lockfile pubspec.lock
One or more bare package names -- pass --ecosystem pub so depshieldx knows they aren't PyPI names:
depshieldx scan http --ecosystem pub
depshieldx install http --ecosystem pub
depshieldx install http@1.6.0 --ecosystem pub
A bare package name (no version) resolves to that package's latest version via pub.dev's own package API. Resolution shells out to the real dart pub get CLI against a scratch pubspec.yaml in an isolated temp directory to compute the full, accurate transitive dependency graph, the same reasoning as Cargo's/Go's/Maven's/NuGet's scratch-project resolve.
If you have the routing shim enabled, dart pub add <package...> is also intercepted automatically and routed through depshieldx install <package...> --ecosystem pub -- you don't need to change your muscle memory.
"Install" here means dart pub add -- adding the package(s) to your project's pubspec.yaml/pubspec.lock. Unlike NuGet's dotnet add package (limited to one package per invocation), dart pub add foo bar accepts any number of packages in one call, so depshieldx pins every resolved package -- transitive included -- as a direct dependency in one call, the same stronger scan-to-install drift guarantee Cargo/Go already have. depshieldx uninstall is also supported, via dart pub remove (also multi-package).
--deep is supported for Pub the same way it is for PyPI, npm, Cargo, Go, Maven, and NuGet: the resolved package set (every real .tar.gz archive) is fetched into a sandboxed container (dart:3 + strace) and scanned with Trivy against a real, host-generated pubspec.lock -- Trivy's Pub support needs a real lock file, the same requirement NuGet's own support has. The sandboxed dart run (against a trivial scratch entry-point file) is traced with strace for filesystem, process, and network activity -- see Modes for details. Pub's real code-execution surface is Dart's Native Assets "hooks" feature (hook/build.dart) -- a package shipping one gets it invoked during dart run/dart test, the same "presence in the dependency graph is enough" pattern NuGet's build/*.targets has, not something dart pub get alone ever triggers.
Provenance checks for Pub combine a real cryptographic checksum check (SHA-256, verified against the exact hash pub.dev's own package API publishes for that release) with structural signals -- pub.dev has no signing scheme of its own at all (no Sigstore, no PGP, no X.509), so integrity rests entirely on this checksum -- see Provenance And Attestations.
deps.dev does not support Pub as an ecosystem at all (confirmed directly against its own documented list of supported systems) -- depshieldx skips it explicitly for Pub scans rather than silently querying the wrong system, so deps-dev: no vulnerabilities for a Pub scan means "not checked", not "checked and clean".
What's still explicitly not supported for Pub:
pubspec.yaml-as-input -- onlypubspec.lockor bare package names via--ecosystem pubare accepted- cryptographic signature verification of any kind -- pub.dev has no signing infrastructure to verify against, unlike Maven/NuGet
- Flutter-specific tooling (native platform plugin builds,
flutter pub,flutter build) --depshieldx's Pub support is scoped to the standalone Dart SDK and hosted (pub.dev) dependencies only
RubyGems Support
depshieldx can resolve, check, and install RubyGems packages too, with full fast and deep mode support.
Two ways to point it at RubyGems:
A Gemfile.lock file in the current directory -- auto-detected by filename, no flag needed:
depshieldx scan --lockfile Gemfile.lock
depshieldx install --lockfile Gemfile.lock
One or more bare gem names -- pass --ecosystem rubygems so depshieldx knows they aren't PyPI names:
depshieldx scan rack --ecosystem rubygems
depshieldx install rack --ecosystem rubygems
depshieldx install rack@3.2.7 --ecosystem rubygems
A bare gem name (no version) resolves to that gem's latest version via rubygems.org's own gem API. Resolution shells out to the real bundle lock CLI against a scratch Gemfile in an isolated temp directory to compute the full, accurate transitive dependency graph, the same reasoning as Cargo's/Go's/Maven's/NuGet's/Pub's scratch-project resolve. Only the GEM section of a Gemfile.lock (rubygems.org-hosted gems) is resolved against the registry -- GIT/PATH sections have no registry checksum to verify against and are skipped, the same "not every entry is registry-fetchable" case Cargo's/npm's/Pub's own non-registry-sourced dependencies already are.
If you have the routing shim enabled, bundle add <gem...> is also intercepted automatically and routed through depshieldx install <gem...> --ecosystem rubygems -- you don't need to change your muscle memory.
"Install" here means bundle add -- adding the gem(s) to your project's Gemfile/Gemfile.lock. Unlike Cargo's/Go's/Pub's own "name@version" per-target syntax, bundle add gem1 gem2 --version X applies ONE shared version constraint to every named gem in the call (confirmed directly) -- there's no way to pin more than one independently-resolved exact version in a single invocation. So depshieldx loops one real bundle add <gem> --version <v> call per resolved gem instead, still ending up with every resolved gem -- transitive included -- pinned as a direct dependency, the same drift-prevention guarantee Cargo/Go/Pub already have. depshieldx uninstall is also supported, via bundle remove (multi-gem in one call).
--deep is supported for RubyGems the same way it is for PyPI, npm, Cargo, Go, Maven, NuGet, and Pub: the resolved gem set (every real .gem archive) is fetched into a sandboxed container (ruby:3 + strace) and scanned with Trivy against a real Gemfile.lock -- Trivy's Bundler support needs a real lock file, the same requirement NuGet's/Pub's own support has. Unlike every other ecosystem here, that Gemfile.lock is never produced by actually running bundle on the host: confirmed directly that bundle install/bundle lock --local either triggers real native-extension compilation or can't reliably resolve purely from a local cache, so depshieldx writes it directly from the resolution it already has instead. The sandboxed bundle install --local (the same command Docker deep mode's install step already runs) is what's traced with strace for filesystem, process, and network activity -- see Modes for details. RubyGems' real code-execution surface is a native-extension gem's own extconf.rb, run as an unavoidable part of installing it -- not a separate later step the way Pub's hooks/NuGet's build/*.targets are.
Provenance checks for RubyGems combine a real cryptographic checksum check (SHA-256, verified against the exact hash rubygems.org's own API publishes for that release) with a structural yank signal -- see Provenance And Attestations. Unlike NuGet's "unlisted" (still resolvable/queryable when pinned exactly) or Pub's "retracted" (stays in the registry's own version list forever, just flagged), a RubyGems version yanked long enough ago is removed outright -- confirmed directly against the real 2019 rest-client hijack incident (1.6.10-1.6.13): both the full versions list and the per-version registry endpoint have no record of it at all, a plain 404 rather than a yanked: true payload. Since that 404 is genuinely ambiguous between "never existed" and "yanked and purged", depshieldx surfaces it as an honest, non-blocking "could not verify this version" warning rather than asserting a specific cause it can't actually confirm.
Unlike Pub, deps.dev does support RubyGems as an ecosystem -- no explicit skip needed here.
What's still explicitly not supported for RubyGems:
.gemspec-as-input -- onlyGemfile.lockor bare gem names via--ecosystem rubygemsare accepted- cryptographic signature verification of any kind -- rubygems.org's Sigstore support is still opt-in/in-progress, and the older X.509
gem certscheme is opt-in and rarely used in practice, so integrity rests entirely on the SHA-256 checksum check, the same as Pub - platform-specific gem variants (a gem publishing separate prebuilt binaries per OS/CPU) --
depshieldxalways resolves and verifies against the platform-agnostic "ruby" build, matching what a realbundle lockand rubygems.org's own per-version API both default to when no platform is specified
Composer Support
depshieldx can resolve, check, and install Composer (PHP/Packagist) packages too, with full fast and deep mode support.
Two ways to point it at Composer:
A composer.lock file in the current directory -- auto-detected by filename, no flag needed:
depshieldx scan --lockfile composer.lock
depshieldx install --lockfile composer.lock
One or more bare package names -- pass --ecosystem composer so depshieldx knows they aren't PyPI names:
depshieldx scan monolog/monolog --ecosystem composer
depshieldx install monolog/monolog --ecosystem composer
depshieldx install monolog/monolog@3.10.0 --ecosystem composer
Unlike Pub/RubyGems/NuGet, a bare package name (no version) needs no separate registry lookup -- confirmed directly composer require vendor/package (with no version at all) already resolves and pins the latest stable release by itself. Resolution shells out to the real composer require --no-install CLI against a scratch composer.json in an isolated temp directory to compute the full, accurate transitive dependency graph, the same reasoning as Cargo's/Go's/Maven's/NuGet's/Pub's/RubyGems' own scratch-project resolve -- --no-install skips straight past Composer's own extract/activate step, so a scratch resolve never has anything to gain from (or risk on) it succeeding.
If you have the routing shim enabled, composer require <package...> is also intercepted automatically and routed through depshieldx install <package...> --ecosystem composer -- you don't need to change your muscle memory.
"Install" here means composer require -- adding the package(s) to your project's composer.json/composer.lock. Unlike RubyGems' bundle add (one shared --version across every named gem in a call), a real composer require pkg1:v1 pkg2:v2 ... accepts any number of independently-versioned packages in one call -- so depshieldx pins every resolved package, transitive included, as a direct dependency in one call, the same stronger scan-to-install drift guarantee Cargo/Go/Pub already have. depshieldx uninstall is also supported, via composer remove (also multi-package).
--deep is supported for Composer the same way it is for PyPI, npm, Cargo, Go, Maven, NuGet, Pub, and RubyGems: the resolved package set (every real dist .zip, checksum-verified when the registry actually publishes one) is fetched into a sandboxed container (php:8.4-cli + strace) and installed fully offline through Composer's own real "artifact" repository mechanism, then scanned with Trivy against a real, host-written composer.lock. Unlike NuGet/Pub, that lockfile is never produced by running composer on the host at all -- confirmed directly a minimal, hand-written composer.lock (just each package's name and version) is already sufficient for Trivy's own vulnerability detection, so depshieldx writes it directly from the resolution it already has, the same "don't shell out again for no reason" choice RubyGems makes (for an entirely different underlying reason -- Composer has no comparable installation-time code-execution risk to isolate). See Modes for how the sandboxed install itself is traced.
Provenance checks for Composer are the weakest of any ecosystem here, and honestly so: Packagist's own dist.shasum is empty for essentially every real package (confirmed directly across dozens of real packages and versions), so there is usually no checksum to verify at all. When a checksum genuinely is published, it's still verified as a real SHA-1 hash; when it isn't, depshieldx says so plainly ("no checksum published -- pinned by git reference only") rather than refusing or pretending it verified something it didn't. The git commit reference Packagist records for the resolved dist archive is the one real, always-present content pin. Structurally, depshieldx also surfaces Packagist's abandoned flag, which can be a plain boolean or a string naming a suggested replacement package (confirmed directly against a real abandoned package) -- see Provenance And Attestations.
Like Pub, deps.dev does not support Composer/PHP as an ecosystem at all (confirmed directly against its own documented list of supported systems) -- depshieldx skips it explicitly for Composer scans rather than silently querying the wrong system, so deps-dev: no vulnerabilities for a Composer scan means "not checked", not "checked and clean".
What's still explicitly not supported for Composer:
composer.json-as-input -- onlycomposer.lockor bare package names via--ecosystem composerare accepted- cryptographic signature verification of any kind -- Packagist has no Sigstore/PGP/X.509 signing scheme to verify against, unlike Maven/NuGet, and most packages don't even publish a checksum to fall back on
- Composer plugins -- as of Composer 2.2+, installing any package containing one is blocked by default unless explicitly allow-listed in the project's own
config.allow-plugins;depshieldxrelies on this default rather than working around it, and never allow-lists anything itself
Commands
Main commands:
depshieldx installdepshieldx scandepshieldx uninstalldepshieldx uidepshieldx doctordepshieldx routing statusdepshieldx routing enabledepshieldx routing disabledepshieldx receipts listdepshieldx receipts verify <path>depshieldx receipts deletedepshieldx cache clean
Get help at any level:
depshieldx --help
depshieldx install --help
depshieldx scan --help
depshieldx uninstall --help
depshieldx ui --help
depshieldx doctor --help
depshieldx receipts --help
depshieldx cache --help
depshieldx doctor checks every prerequisite this project performs piecemeal elsewhere -- the Python/pip version gate, Docker daemon availability, host Trivy availability, and each ecosystem's own toolchain on PATH -- in one pass, so a missing toolchain shows up before an install/scan run rather than mid-run.
depshieldx cache clean reclaims disk space from the local deep-scan bundle and provenance caches; see Local UI below for what's cached and where.
Local UI
depshieldx ui opens a local browser view over cached receipts and related cache entries -- mostly read-only, with one exception: each receipt row has a delete button to remove that one receipt.
- binds to
127.0.0.1only - uses port
0by default so the OS can choose a free port - opens the browser automatically unless you pass
--no-open
Examples:
depshieldx ui
depshieldx ui --port 8765
depshieldx ui --no-open
Release Notes
For a release build:
- confirm the included Apache 2.0 license still matches how you want to distribute the project
- build and verify distributions in a Python
3.11.4+environment - run
python -m build - run
python -m twine check dist/* - for TestPyPI, run the
Release Checksworkflow manually - for PyPI, push a version tag such as
v0.1.0-- this also triggers theRelease Binariesworkflow, which builds and attaches the standalone per-platform binaries to the matching GitHub Release
Common Examples
Install one package:
depshieldx install fastapi
Install multiple packages:
depshieldx install langchain requests --deep
Scan only:
depshieldx scan fastapi --fast
depshieldx scan fastapi --deep
Use a requirements file:
depshieldx install -r requirements.txt
depshieldx scan -r requirements.txt --deep
Use a lockfile:
depshieldx install --lockfile uv.lock
depshieldx scan --lockfile uv.lock
Use a pyproject.toml file:
depshieldx install --pyproject pyproject.toml
depshieldx scan --pyproject pyproject.toml --deep
Open the local cache UI:
depshieldx ui
depshieldx ui --port 8765
depshieldx ui --no-open
Uninstall packages:
depshieldx uninstall requests
depshieldx uninstall -r requirements.txt
depshieldx uninstall --pyproject pyproject.toml
npm packages and lockfiles:
depshieldx scan left-pad --ecosystem npm
depshieldx install left-pad --ecosystem npm
depshieldx install left-pad is-odd --ecosystem npm
depshieldx scan --lockfile package-lock.json
depshieldx install --lockfile yarn.lock
depshieldx scan --lockfile pnpm-lock.yaml
Cargo crates and lockfiles:
depshieldx scan serde --ecosystem cargo
depshieldx install serde --ecosystem cargo
depshieldx install serde tokio --ecosystem cargo
depshieldx scan --lockfile Cargo.lock
depshieldx install --lockfile Cargo.lock
Go modules and lockfiles:
depshieldx scan github.com/pkg/errors --ecosystem go
depshieldx install github.com/pkg/errors --ecosystem go
depshieldx install github.com/pkg/errors golang.org/x/text --ecosystem go
depshieldx scan --lockfile go.sum
depshieldx install --lockfile go.sum
Maven coordinates:
depshieldx scan org.apache.commons:commons-lang3:3.18.0 --ecosystem maven
depshieldx install com.google.code.gson:gson:2.11.0 --ecosystem maven
depshieldx install org.apache.commons:commons-lang3 --ecosystem maven
NuGet packages and lockfiles:
depshieldx scan Newtonsoft.Json --ecosystem nuget
depshieldx install Newtonsoft.Json@13.0.3 --ecosystem nuget
depshieldx scan --lockfile packages.lock.json
depshieldx install --lockfile packages.lock.json
Pub packages and lockfiles:
depshieldx scan http --ecosystem pub
depshieldx install http@1.6.0 --ecosystem pub
depshieldx scan --lockfile pubspec.lock
depshieldx install --lockfile pubspec.lock
RubyGems packages and lockfiles:
depshieldx scan rack --ecosystem rubygems
depshieldx install rack@3.2.7 --ecosystem rubygems
depshieldx scan --lockfile Gemfile.lock
depshieldx install --lockfile Gemfile.lock
Composer packages and lockfiles:
depshieldx scan monolog/monolog --ecosystem composer
depshieldx install monolog/monolog@3.10.0 --ecosystem composer
depshieldx scan --lockfile composer.lock
depshieldx install --lockfile composer.lock
Supported Inputs
depshieldx accepts:
- one package name (PyPI by default, npm with
--ecosystem npm, Cargo/crates.io with--ecosystem cargo, Go with--ecosystem go, Maven coordinates with--ecosystem maven, NuGet with--ecosystem nuget, Pub with--ecosystem pub, RubyGems with--ecosystem rubygems, or Composer with--ecosystem composer) - multiple package names (same ecosystem rule as above)
-r requirements.txt(PyPI only)--lockfile uv.lock(PyPI)--lockfile package-lock.json/yarn.lock/pnpm-lock.yaml(npm, auto-detected by filename)--lockfile Cargo.lock(Cargo, auto-detected by filename)--lockfile go.sum(Go, auto-detected by filename)--lockfile packages.lock.json(NuGet, auto-detected by filename)--lockfile pubspec.lock(Pub, auto-detected by filename)--lockfile Gemfile.lock(RubyGems, auto-detected by filename)--lockfile composer.lock(Composer, auto-detected by filename)--pyproject pyproject.toml(PyPI only)
Maven has no canonical lockfile, so it has no --lockfile equivalent -- coordinates are always passed explicitly via --ecosystem maven.
Current lockfile behavior:
uv.lockis parsed directlypackage-lock.json,yarn.lock, andpnpm-lock.yamlare parsed directlyCargo.lockis parsed directly; if the same crate is pinned at two different major versions, only the newest resolved version is kept and the older entry is silently droppedgo.sumresolution reads the siblinggo.mod's full resolved module graph (viago list -m all) rather than parsinggo.sumitself, sincego.sumis a checksum allowlist, not the resolved graph -- it can list more module versions than actually shippackages.lock.jsonis parsed directly; if a package appears across multiple target frameworks with disagreeing versions, only the newest resolved version is kept, the same "keep the newest" rule asCargo.lockpubspec.lockis parsed directly (it's real YAML); onlysource: hostedentries are resolved against the registry --source: git/source: path/source: sdkentries have no registry checksum to verify against and are skipped, the same "not every entry is registry-fetchable" case Cargo's/npm's git-sourced dependencies already areGemfile.lockis parsed directly (Bundler's own custom text format, not YAML/JSON); only theGEMsection's resolved specs are read --GIT/PATHsections have no registry checksum to verify against and are skipped, the same "not every entry is registry-fetchable" case pubspec.lock's ownsource: hosted-only filtering already iscomposer.lockis parsed directly (it's real JSON); direct-vs-transitive dependency status is read from the siblingcomposer.json's ownrequire/require-devtables, the same "need the sibling manifest" caseCargo.lock/go.sumalready are, since neither ofcomposer.lock's ownpackages/packages-devarrays marks its entries either way- other PyPI lockfile-style inputs are treated like requirement-style pinned targets
Output Modes
Human-readable summary:
depshieldx install requests --output summary
JSON only:
depshieldx install requests --output json
Summary plus JSON:
depshieldx install requests --output both
depshieldx install requests --full-report
What The Summary Means
Key summary lines:
Scan verdictCVE sources across all resolved packagesProvenance verdictAttestation verificationSandbox verdictTrivy verdictHost installReceipts
Scan verdict reflects the resolved package set, including dependencies.
Provenance verdict means the provenance checks did or did not block. A package can still pass provenance and show informational items such as:
- missing author or maintainer email
- missing PyPI attestations
That is expected. passed means "not blocked", not "perfect metadata".
Attestation verification describes how many attested selected files verified successfully. It does not mean every package had attestations.
historical/fixed CVEs mean the source knows about past vulnerabilities in the package history, but not in the exact versions currently selected for install.
Example summary:
Summary
Package: fastapi
Mode: fast
Install target: fastapi==0.135.2
Resolved packages: 10
Scan verdict: passed with 0 warning(s), 0 info item(s)
CVE sources across all resolved packages:
• cisa-kev: no vulnerabilities
• deps-dev: 0 advisories, 10 package record(s) checked
• github-advisories: no vulnerabilities
• osv: 0 affecting resolved version(s), 15 historical/fixed entries in resolved dependency history
Provenance verdict: passed with 0 warning(s), 0 info item(s)
Attestation verification: 7/7 attested file(s) verified, available
Host install: succeeded (fastapi==0.135.2, https://pypi.org/project/fastapi/0.135.2/)
Receipts: allowed (1 package receipt)
Receipt ID: abc123def4567890
Receipt path:
- /Users/you/.depshieldx-cache/receipts/20260331T000000Z-fastapi-0.135.2-abc123def4567890.json
For multi-package installs, the summary also includes:
- a requested-package source breakdown
- one receipt path per requested package
- one project link (PyPI, npm, crates.io, pkg.go.dev, nuget.org, pub.dev, rubygems.org, or packagist.org) per requested package when relevant
Provenance And Attestations
The provenance stage checks the exact artifacts selected for your environment, not every file on the release page.
For PyPI, it looks at things like:
- whether the release exists on PyPI
- whether the release is source-only
- whether the release is a pre-release
- whether homepage/project URLs exist
- whether author or maintainer email metadata exists
- whether the selected files have PyPI attestations
- whether those attestations verify successfully (a real Sigstore bundle check, not just presence)
For npm/yarn/pnpm, it looks at:
- whether the release is deprecated
- whether homepage/repository and author/maintainer metadata exist
- whether the resolved release has an integrity digest
- whether the release has npm provenance attestations, and whether its SLSA provenance attestation (the one signed via GitHub Actions OIDC through a real Fulcio certificate) verifies successfully -- npm's separate "publish" attestation, signed with npm registry's own key rather than a Fulcio certificate, is recorded but not cryptographically verified here, since that's a different trust model
For Cargo/crates.io, there is no cryptographic attestation infrastructure to check against -- crates.io does not support Sigstore/SLSA provenance the way PyPI and npm do. Instead, depshieldx checks:
- whether the resolved version has been yanked
- whether homepage/repository metadata exists
- crates.io's self-reported Trusted Publishing metadata (provider, repository, run ID), where present -- recorded for reference only, since crates.io does not sign or publish a verifiable attestation for it
For Go modules, checksum verification against the real checksum-transparency log (sum.golang.org, a cryptographically-signed Merkle tree) already happens transparently inside the go toolchain itself during resolution -- there's no separate per-package attestation to verify the way PyPI/npm have. Instead, depshieldx checks:
- whether the resolved version has been retracted (a module author publishing a later version that lists a prior one as retracted -- Go's closest equivalent to a yanked release)
- basic module metadata
For Maven/Maven Central, provenance combines a checksum with structural and (where available) real cryptographic signals:
- checksum verification (SHA-256 where Central publishes one, falling back to SHA-1 for older releases -- MD5 is never trusted, it's cryptographically broken)
- whether the resolved artifact has a PGP signature (Central has required these since the 2010s) -- presence is recorded structurally only, since there's no central root of trust binding an arbitrary PGP key to a real-world identity the way Sigstore's Fulcio certificates are
- whether the resolved artifact has a Sigstore bundle and, if so, whether it verifies successfully (a real cryptographic check, not just presence) -- supported by Maven Central's Publisher Portal since January 2025, but still new and opt-in; most published artifacts don't have one yet
For NuGet/NuGet.org, provenance combines a real cryptographic checksum check with structural signature-presence:
- checksum verification (SHA-512, verified against the exact hash NuGet.org's own registration API publishes for that release -- a real cryptographic hash comparison, not a structural check)
- whether the resolved package has a repository signature -- NuGet.org unconditionally repository-signs every package it hosts with an X.509/Authenticode signature, so presence is checked directly against the
.nupkg's own signature entry, but the certificate chain itself is not cryptographically validated, the same "presence, not chain verification" limitation as Maven's PGP-signature check - whether the resolved version is unlisted (NuGet's structural yank-equivalent) or marked deprecated
For Pub/pub.dev, provenance combines a real cryptographic checksum check with structural signals -- pub.dev has no signing scheme of its own at all:
- checksum verification (SHA-256, verified against the exact hash pub.dev's own package API publishes for that release -- a real cryptographic hash comparison, not a structural check)
- whether the resolved package is discontinued (a real, package-level flag an author or pub.dev admin can set, optionally naming a replacement)
- whether the resolved version has been retracted (a publisher can retract a version within 7 days of publishing it -- Pub's closest equivalent to a yanked release)
For RubyGems/rubygems.org, provenance combines a real cryptographic checksum check with a structural yank signal -- rubygems.org has no default, always-present signing scheme either (Sigstore support is still opt-in/in-progress, the older X.509 gem cert scheme is opt-in and rarely used):
- checksum verification (SHA-256, verified against the exact hash rubygems.org's own API publishes for that release -- a real cryptographic hash comparison, not a structural check)
- whether the resolved version has been yanked, where the registry still reports it (a version yanked long enough ago is fully removed from the registry instead, rather than staying queryable with a
yanked: trueflag -- see RubyGems Support for the real, confirmed-directly difference from NuGet's/Pub's own yank-equivalent signals)
For Composer/Packagist, provenance is structural more often than cryptographic -- Packagist has no signing scheme of its own at all, and most packages don't even publish a checksum to fall back on:
- checksum verification when Packagist's
dist.shasumis actually published for the resolved version (a real SHA-1 hash comparison) -- confirmed directly this is empty for essentially every real package, so the common case is an honest "no checksum published, pinned by git reference only" info note instead of a check - the git commit
referencePackagist records for the resolved dist archive -- the one real, always-present content pin regardless of whether a checksum is published - whether the package is
abandoned(a real, package-level flag that can be a plain boolean or a string naming a suggested replacement, confirmed directly against a real abandoned package -- see Composer Support)
Either way, a block only happens when verification was actually attempted and failed -- not attestations being absent at all, since most packages on PyPI and npm don't publish them, Cargo/Go have no per-package attestations to check in the first place, most Maven artifacts published today are PGP-only, not yet Sigstore-signed, and Pub/RubyGems/Composer have no default signing scheme to check in the first place either.
Vulnerability Sources
Fast and deep mode both query these four sources concurrently:
- OSV
- GitHub Advisories
- CISA KEV
- deps.dev
deps.dev output is shown as:
deps-dev: 0 advisories, 43 package record(s) checked
That means:
- how many advisory references deps.dev reported
- how many resolved package-version records were successfully checked
Receipts
Every completed install or scan attempts to write signed local receipt JSON files.
Important receipt behavior:
- single-package runs produce one receipt
- multi-package runs produce one receipt per requested package
- requirements, lockfile, and
pyproject.tomlruns also write per-requested-package receipts when possible
Receipt commands:
depshieldx receipts list
depshieldx receipts verify ~/.depshieldx-cache/receipts/<receipt>.json
depshieldx receipts delete
Receipts include package-level details such as:
- package and resolved version
- project link
- provenance summary
- scan summary
- historical/fixed CVE entries for that package
Routing
depshieldx can optionally install small shims so simple pip install <package>, npm install [package], yarn install, pnpm install, cargo add <crate>, go get <module>, dotnet add package <name>, dart pub add <package...>, bundle add <gem...>, and composer require <package...> commands go through depshieldx.
depshieldx routing status
depshieldx routing enable
depshieldx routing disable
Routing is platform-aware:
- on macOS and Linux it creates shell shims (
pip,npm,yarn,pnpm,cargo,go,dotnet,dart,bundle,composer) - on Windows it creates batch shims (
pip.bat,npm.bat,yarn.bat,pnpm.bat,cargo.bat,go.bat,dotnet.bat,dart.bat,bundle.bat,composer.bat)
What each shim intercepts:
pip install <package>-- a single package name, routed throughdepshieldx install <package>npm install/npm i/npm ci/yarn install/pnpm installwith no package named -- routed throughdepshieldx install --lockfile <lockfile-in-cwd>, only when that lockfile is presentnpm install <package...>/npm i <package...>-- one or more package names with no other flags, routed throughdepshieldx install <package...> --ecosystem npmyarn add <package>/pnpm add <package>are not intercepted yet -- ad-hoc resolution in this phase only coversnpm install <package>, so yarn/pnpm named installs pass straight through to the real toolcargo add <crate...>-- one or more crate names with no other flags, routed throughdepshieldx install <crate...> --ecosystem cargo.cargo install(binary crates) is not intercepted -- depshieldx's cargo support only coverscargo addgo get <module...>-- one or more module paths with no other flags, routed throughdepshieldx install <module...> --ecosystem go.go install(binary programs) is not intercepted -- depshieldx's Go support only coversgo getdotnet add package <name>/dotnet add package <name> --version <version>-- exactly one package, no other flags, routed throughdepshieldx install <name>[@version] --ecosystem nuget. No project positional and no otherdotnet add packageflags (--framework,--prerelease, ...) are intercepted -- anything beyond this exact shape passes straight through to the realdotnetdart pub add <package...>-- one or more package names with no other flags, routed throughdepshieldx install <package...> --ecosystem pub. Non-hosted descriptor syntax ("foo@{path: ...}","foo@{git: ...}","foo@{sdk: ...}") and section prefixes (dev:foo,override:foo) are not intercepted -- depshieldx's Pub support only covers hosted (pub.dev) packages, and anything using that syntax passes straight through to the realdartbundle add <gem...>(no other flags) -- one or more gem names, routed throughdepshieldx install <gem...> --ecosystem rubygems.bundle add <gem> --version <version>(exactly one gem) is also intercepted, routed throughdepshieldx install <gem>@<version> --ecosystem rubygems.bundle add <gem1> <gem2> --version <version>(a shared version across multiple gems -- confirmed directly this is real Bundler behavior) is not intercepted, since that shape doesn't map to a safe per-gem depshieldx target; it passes straight through to the realbundlecomposer require <package...>(no other flags) -- one or morevendor/package[:constraint]targets, routed throughdepshieldx install <package...> --ecosystem composer. Unlike depshieldx's own "name@version" convention (always an exact pin elsewhere in this project), Composer's own "name:constraint" syntax accepts arbitrary ranges and branch aliases, not just exact versions -- a colon-target whose constraint isn't a plain exact version is not intercepted, and the whole command passes straight through to the realcomposerrather than risk silently misrepresenting a range as a pin
There is no Maven shim, and none is planned -- unlike pip install/npm install/cargo add/go get/dotnet add package/dart pub add/bundle add/composer require, mvn has no native CLI verb for "add a dependency" to intercept in the first place; Maven dependencies are added by editing pom.xml directly. See Maven / Maven Central Support.
Anything else (flags mixed in with a package name, other subcommands like run, global installs) passes straight through to the real tool untouched.
Useful environment variables:
DEPSHIELDX_CACHE_DIRDEPSHIELDX_RECEIPTS_DIRDEPSHIELDX_NO_ROUTING_PROMPT=1DEPSHIELDX_ROUTE_DEEP=1
Cache Location
By default, local state lives under:
~/.depshieldx-cache
That directory can contain:
- provenance cache entries
- deep-scan cache entries
- receipts
- routing state
You can inspect those cached results in the local browser UI with:
depshieldx ui
Exit Codes
0: success10: blocked by resolution, provenance, vulnerability checks, or Trivy11: deep mode could not use Docker and the install or scan was skipped for that reason12: host install was attempted but failed
Limitations
- deep mode depends on Docker being available (for npm, a small local
node:20+straceimage is built on first use; for Cargo, a small localrust:1-slim+straceimage is built on first use; for Go, a small localgolang:1-bookworm+straceimage is built on first use; for Maven, a small localmaven:3-eclipse-temurin-21+straceimage, with Maven's own default-lifecycle plugin set pre-warmed in, is built on first use; for NuGet, a small localmcr.microsoft.com/dotnet/sdk:8.0+straceimage is built on first use; for Pub, a small localdart:3+straceimage is built on first use; for RubyGems, a small localruby:3+straceimage is built on first use; for Composer, a small localphp:8.4-cli+straceimage (plus thezipPHP extension andunzip, and Composer itself copied from the officialcomposer:2image) is built on first use -- see npm / yarn / pnpm Support, Cargo / crates.io Support, Go Modules Support, Maven / Maven Central Support, NuGet Support, Pub Support, RubyGems Support, and Composer Support) - deep mode also depends on Trivy being installed
- deep mode is slower than fast mode
- npm's, Cargo's, Go's, Maven's, NuGet's, Pub's, RubyGems', and Composer's behavioral tracing (Docker deep mode) all observe syscalls via
stracerather than actively blocking them in real time the way PyPI's in-process guards do; filesystem/network isolation is still enforced by the container itself either way - the safety guarantees depend in part on the local Python and
pipversions - Cargo, Go, Maven, NuGet, Pub, RubyGems, and Composer resolution/install/deep mode all shell out to a local toolchain binary on
PATH(cargo,go,mvn,dotnet,dart,bundle,composerrespectively);install/scanthemselves still don't preflight-check any of these before attempting resolution, so a missing toolchain surfaces as a resolution failure mid-run. Rundepshieldx doctorfirst to check every ecosystem's toolchain (plus Docker/Trivy and the Python/pip prerequisite every operation needs) up front instead. Cargo.lockparsing keeps only the newest resolved version when the same crate appears pinned at two different major versions; the older entry is silently dropped --packages.lock.jsonparsing follows the same "keep the newest" rule when a package disagrees across target frameworks- not every Go module resolved for deep mode has an importable root package (some are subpackage-only, e.g.
golang.org/x/crypto); those are skipped from behavioral tracing rather than failing the whole sandboxed build, and listed in the full JSON report'sskipped_modules - some packages publish no PyPI or npm attestations at all; that is usually informational, not a red flag -- Cargo/crates.io and Go modules have no per-package attestation infrastructure at all, so this is categorically true for every crate/module, not just some; Maven has real Sigstore support but it's still new and opt-in, so most Maven artifacts today are in the same boat; NuGet, Pub, RubyGems, and Composer have no default Sigstore equivalent at all
- attestation verification can depend on upstream trust metadata availability
- npm's own "publish" attestation (signed with npm registry's own key, not a Fulcio certificate) is recorded structurally but not cryptographically verified -- only npm's SLSA provenance attestation is, since that's the one signed via GitHub Actions OIDC the same way PyPI's Trusted Publishing attestations are
- Cargo has no cryptographic provenance verification at all -- crates.io currently has nothing equivalent to verify against
- Go modules have no separate per-package cryptographic provenance step either -- checksum verification against the real checksum-transparency log already happens transparently inside the
gotoolchain itself during resolution - Maven's Sigstore verification only recognizes one confirmed-real OIDC issuer (GitHub Actions) for now -- a real signature from a different, equally-legitimate issuer is recorded as "signed via Sigstore, but by an unrecognized issuer" (informational), not verified as trusted
- a BOM import's version that's a
${property}placeholder is resolved for Maven deep mode only against that same POM's own<properties>block, not Maven's fuller cross-file property inheritance -- an unresolvable entry is skipped rather than guessed at, and surfaces as a clear Maven error from the sandboxed build if it turns out to matter - there is no Maven routing shim -- see Routing
- NuGet's repository-signature check is presence-only, not a real certificate-chain validation --
depshieldxhas no trust-root story for X.509 elsewhere, so a forged or expired certificate chain would still record as "signed" - both
dotnet add packageanddotnet remove package(and sodepshieldx's own NuGet install/uninstall/routing) are scoped to exactly one package per invocation -- the realdotnetCLI itself doesn't accept more than one package name at a time - Pub has no cryptographic signature verification at all -- pub.dev has no Sigstore/PGP/X.509 signing scheme to verify against, unlike Maven/NuGet; integrity rests entirely on the SHA-256 checksum check
- deps.dev does not support Pub as an ecosystem at all --
depshieldxskips it explicitly rather than silently querying the wrong system, so a Pub scan'sdeps-dev: no vulnerabilitiesmeans "not checked", not "checked and clean" - Pub's behavioral tracing only covers Dart's Native Assets "hooks" mechanism (
hook/build.dart) -- Flutter-specific native platform plugin code (compiled during an actualflutter build, not anythingdart run/dart pub gettrigger) is out of scope, sincedepshieldx's Pub support targets the standalone Dart SDK, not Flutter - RubyGems has no cryptographic signature verification at all -- Sigstore support for rubygems.org is still opt-in/in-progress and the older X.509
gem certscheme is rarely used in practice, so integrity rests entirely on the SHA-256 checksum check, the same as Pub - a RubyGems version yanked long enough ago is removed outright from the registry rather than staying queryable with a
yanked: trueflag (confirmed directly against the real 2019rest-clienthijack incident) -- since a 404 there is ambiguous between "never existed" and "yanked and purged",depshieldxreports it as an honest, non-blocking warning rather than asserting a confirmed yank depshieldxnever runsbundle install/bundle lock --localon the host for RubyGems deep mode -- confirmed directlybundle installtriggers real native-extension compilation, unlike every other ecosystem's own host-side restore/fetch step; the Trivy-facingGemfile.lockis written directly from the already-known resolution instead, and only the fully-isolated sandbox container actually runsbundle install- platform-specific RubyGems variants (a gem publishing separate prebuilt binaries per OS/CPU) are out of scope --
depshieldxalways resolves and verifies against the platform-agnostic "ruby" build, matching what a realbundle lockand rubygems.org's own per-version API both default to - Composer has no cryptographic signature verification of any kind -- Packagist has no Sigstore/PGP/X.509 signing scheme to verify against at all, unlike Maven/NuGet, and most packages don't even publish a checksum to fall back on; integrity for those packages rests entirely on the git commit
referencePackagist records for the resolved dist archive - deps.dev does not support Composer/PHP as an ecosystem at all, the same gap it has for Pub --
depshieldxskips it explicitly rather than silently querying the wrong system, so a Composer scan'sdeps-dev: no vulnerabilitiesmeans "not checked", not "checked and clean" - Composer's behavioral tracing only covers packages using the "files" autoload mechanism -- Composer plugins (blocked by default as of 2.2+ unless the project explicitly allow-lists one, which
depshieldxnever does) and project-level script hooks (root-project-only by design, never triggered by a dependency's owncomposer.json) are both out of scope, since neither is a real risk indepshieldx's own default, unmodified install flow depshieldxnever runscomposer install/composer updateon the host to build the Trivy-facingcomposer.lockfor Composer deep mode -- confirmed directly a minimal, hand-written lockfile (just each package's name and version) is already sufficient for Trivy's own vulnerability detection, sodepshieldxwrites it directly from the resolution it already has instead- vulnerability-source coverage depends on the upstream services
FAQ
Does it scan dependencies too?
Yes. The resolved dependency set is scanned, not just the top-level package you typed.
Will install --deep and scan --deep behave the same way?
Yes, except install performs the final host install and scan does not.
What should I use most of the time?
Use:
depshieldx install <package>
Use --deep when you want the extra Docker + Trivy validation step.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file depshieldx-0.10.0.tar.gz.
File metadata
- Download URL: depshieldx-0.10.0.tar.gz
- Upload date:
- Size: 402.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bfbf9cf456c908eefb878bc6bf53344e90e013fe9a7e0ad8ea37918edd696b35
|
|
| MD5 |
3175c0a58ad76b650f4b4c8ba070b2b7
|
|
| BLAKE2b-256 |
9c3fa91d87c6118be165f90c5ccc904cb3387e29b6411e732625d2183398ce07
|
Provenance
The following attestation bundles were made for depshieldx-0.10.0.tar.gz:
Publisher:
publish.yml on tee-wealth001/depshieldx
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
depshieldx-0.10.0.tar.gz -
Subject digest:
bfbf9cf456c908eefb878bc6bf53344e90e013fe9a7e0ad8ea37918edd696b35 - Sigstore transparency entry: 2565611890
- Sigstore integration time:
-
Permalink:
tee-wealth001/depshieldx@a9168fe56489e3d04e7e27dbfad596ec4f0f64e4 -
Branch / Tag:
refs/tags/v0.10.0 - Owner: https://github.com/tee-wealth001
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@a9168fe56489e3d04e7e27dbfad596ec4f0f64e4 -
Trigger Event:
push
-
Statement type:
File details
Details for the file depshieldx-0.10.0-py3-none-any.whl.
File metadata
- Download URL: depshieldx-0.10.0-py3-none-any.whl
- Upload date:
- Size: 321.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6944034e27cee2e872fee838b61afd7511940e96329218a1f2a79c695ac3ba2b
|
|
| MD5 |
cfe5b7cf3e17a9809d449190637e9a31
|
|
| BLAKE2b-256 |
2726f6ffb8327b33938a40e3e60aba83887af83c7e07aeaa8e0f21e8d7f43094
|
Provenance
The following attestation bundles were made for depshieldx-0.10.0-py3-none-any.whl:
Publisher:
publish.yml on tee-wealth001/depshieldx
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
depshieldx-0.10.0-py3-none-any.whl -
Subject digest:
6944034e27cee2e872fee838b61afd7511940e96329218a1f2a79c695ac3ba2b - Sigstore transparency entry: 2565611968
- Sigstore integration time:
-
Permalink:
tee-wealth001/depshieldx@a9168fe56489e3d04e7e27dbfad596ec4f0f64e4 -
Branch / Tag:
refs/tags/v0.10.0 - Owner: https://github.com/tee-wealth001
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@a9168fe56489e3d04e7e27dbfad596ec4f0f64e4 -
Trigger Event:
push
-
Statement type: