Metadata-Version: 2.4
Name: depshieldx
Version: 0.9.3
Summary: Safer package install and scan tool 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, with cryptographic provenance checks (PyPI/npm/Maven/NuGet/Pub/RubyGems/Composer), CVE lookups, Docker + Trivy deep mode with behavioral tracing, and signed receipts
Author-email: Stephen Toriola <stephenola21@gmail.com>
License: Apache License
        Version 2.0, January 2004
        https://www.apache.org/licenses/
        
        TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
        
        1. Definitions.
        
        "License" shall mean the terms and conditions for use, reproduction,
        and distribution as defined by Sections 1 through 9 of this document.
        
        "Licensor" shall mean the copyright owner or entity authorized by
        the copyright owner that is granting the License.
        
        "Legal Entity" shall mean the union of the acting entity and all
        other entities that control, are controlled by, or are under common
        control with that entity. For the purposes of this definition,
        "control" means (i) the power, direct or indirect, to cause the
        direction or management of such entity, whether by contract or
        otherwise, or (ii) ownership of fifty percent (50%) or more of the
        outstanding shares, or (iii) beneficial ownership of such entity.
        
        "You" (or "Your") shall mean an individual or Legal Entity
        exercising permissions granted by this License.
        
        "Source" form shall mean the preferred form for making modifications,
        including but not limited to software source code, documentation
        source, and configuration files.
        
        "Object" form shall mean any form resulting from mechanical
        transformation or translation of a Source form, including but
        not limited to compiled object code, generated documentation,
        and conversions to other media types.
        
        "Work" shall mean the work of authorship, whether in Source or
        Object form, made available under the License, as indicated by a
        copyright notice that is included in or attached to the work
        (an example is provided in the Appendix below).
        
        "Derivative Works" shall mean any work, whether in Source or Object
        form, that is based on (or derived from) the Work and for which the
        editorial revisions, annotations, elaborations, or other modifications
        represent, as a whole, an original work of authorship. For the purposes
        of this License, Derivative Works shall not include works that remain
        separable from, or merely link (or bind by name) to the interfaces of,
        the Work and Derivative Works thereof.
        
        "Contribution" shall mean any work of authorship, including
        the original version of the Work and any modifications or additions
        to that Work or Derivative Works thereof, that is intentionally
        submitted to Licensor for inclusion in the Work by the copyright owner
        or by an individual or Legal Entity authorized to submit on behalf of
        the copyright owner. For the purposes of this definition, "submitted"
        means any form of electronic, verbal, or written communication sent
        to the Licensor or its representatives, including but not limited to
        communication on electronic mailing lists, source code control systems,
        and issue tracking systems that are managed by, or on behalf of, the
        Licensor for the purpose of discussing and improving the Work, but
        excluding communication that is conspicuously marked or otherwise
        designated in writing by the copyright owner as "Not a Contribution."
        
        "Contributor" shall mean Licensor and any individual or Legal Entity
        on behalf of whom a Contribution has been received by Licensor and
        subsequently incorporated within the Work.
        
        2. Grant of Copyright License. Subject to the terms and conditions of
        this License, each Contributor hereby grants to You a perpetual,
        worldwide, non-exclusive, no-charge, royalty-free, irrevocable
        copyright license to reproduce, prepare Derivative Works of,
        publicly display, publicly perform, sublicense, and distribute the
        Work and such Derivative Works in Source or Object form.
        
        3. Grant of Patent License. Subject to the terms and conditions of
        this License, each Contributor hereby grants to You a perpetual,
        worldwide, non-exclusive, no-charge, royalty-free, irrevocable
        (except as stated in this section) patent license to make, have made,
        use, offer to sell, sell, import, and otherwise transfer the Work,
        where such license applies only to those patent claims licensable
        by such Contributor that are necessarily infringed by their
        Contribution(s) alone or by combination of their Contribution(s)
        with the Work to which such Contribution(s) was submitted. If You
        institute patent litigation against any entity (including a
        cross-claim or counterclaim in a lawsuit) alleging that the Work
        or a Contribution incorporated within the Work constitutes direct
        or contributory patent infringement, then any patent licenses
        granted to You under this License for that Work shall terminate
        as of the date such litigation is filed.
        
        4. Redistribution. You may reproduce and distribute copies of the
        Work or Derivative Works thereof in any medium, with or without
        modifications, and in Source or Object form, provided that You
        meet the following conditions:
        
        (a) You must give any other recipients of the Work or
        Derivative Works a copy of this License; and
        
        (b) You must cause any modified files to carry prominent notices
        stating that You changed the files; and
        
        (c) You must retain, in the Source form of any Derivative Works
        that You distribute, all copyright, patent, trademark, and
        attribution notices from the Source form of the Work,
        excluding those notices that do not pertain to any part of
        the Derivative Works; and
        
        (d) If the Work includes a "NOTICE" text file as part of its
        distribution, then any Derivative Works that You distribute must
        include a readable copy of the attribution notices contained
        within such NOTICE file, excluding those notices that do not
        pertain to any part of the Derivative Works, in at least one
        of the following places: within a NOTICE text file distributed
        as part of the Derivative Works; within the Source form or
        documentation, if provided along with the Derivative Works; or,
        within a display generated by the Derivative Works, if and
        wherever such third-party notices normally appear. The contents
        of the NOTICE file are for informational purposes only and
        do not modify the License. You may add Your own attribution
        notices within Derivative Works that You distribute, alongside
        or as an addendum to the NOTICE text from the Work, provided
        that such additional attribution notices cannot be construed
        as modifying the License.
        
        You may add Your own copyright statement to Your modifications and
        may provide additional or different license terms and conditions
        for use, reproduction, or distribution of Your modifications, or
        for any such Derivative Works as a whole, provided Your use,
        reproduction, and distribution of the Work otherwise complies with
        the conditions stated in this License.
        
        5. Submission of Contributions. Unless You explicitly state otherwise,
        any Contribution intentionally submitted for inclusion in the Work
        by You to the Licensor shall be under the terms and conditions of
        this License, without any additional terms or conditions.
        Notwithstanding the above, nothing herein shall supersede or modify
        the terms of any separate license agreement you may have executed
        with Licensor regarding such Contributions.
        
        6. Trademarks. This License does not grant permission to use the trade
        names, trademarks, service marks, or product names of the Licensor,
        except as required for reasonable and customary use in describing the
        origin of the Work and reproducing the content of the NOTICE file.
        
        7. Disclaimer of Warranty. Unless required by applicable law or
        agreed to in writing, Licensor provides the Work (and each
        Contributor provides its Contributions) on an "AS IS" BASIS,
        WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
        implied, including, without limitation, any warranties or conditions
        of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
        PARTICULAR PURPOSE. You are solely responsible for determining the
        appropriateness of using or redistributing the Work and assume any
        risks associated with Your exercise of permissions under this License.
        
        8. Limitation of Liability. In no event and under no legal theory,
        whether in tort (including negligence), contract, or otherwise,
        unless required by applicable law (such as deliberate and grossly
        negligent acts) or agreed to in writing, shall any Contributor be
        liable to You for damages, including any direct, indirect, special,
        incidental, or consequential damages of any character arising as a
        result of this License or out of the use or inability to use the
        Work (including but not limited to damages for loss of goodwill,
        work stoppage, computer failure or malfunction, or any and all
        other commercial damages or losses), even if such Contributor
        has been advised of the possibility of such damages.
        
        9. Accepting Warranty or Additional Liability. While redistributing
        the Work or Derivative Works thereof, You may choose to offer,
        and charge a fee for, acceptance of support, warranty, indemnity,
        or other liability obligations and/or rights consistent with this
        License. However, in accepting such obligations, You may act only
        on Your own behalf and on Your sole responsibility, not on behalf
        of any other Contributor, and only if You agree to indemnify,
        defend, and hold each Contributor harmless for any liability
        incurred by, or claims asserted against, such Contributor by reason
        of your accepting any such warranty or additional liability.
        
        END OF TERMS AND CONDITIONS
        
Project-URL: Homepage, https://github.com/tee-wealth001/depshieldx
Project-URL: Documentation, https://tee-wealth001.github.io/depshieldx/
Project-URL: Repository, https://github.com/tee-wealth001/depshieldx
Project-URL: Issues, https://github.com/tee-wealth001/depshieldx/issues
Keywords: security,pypi,pip,npm,cargo,crates.io,rust,go,golang,maven,java,nuget,dotnet,csharp,pub,dart,flutter,rubygems,ruby,bundler,composer,packagist,php,supply-chain,provenance,sigstore,trivy,receipts
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Information Technology
Classifier: Intended Audience :: System Administrators
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Build Tools
Classifier: Topic :: System :: Installation/Setup
Classifier: Topic :: Utilities
Requires-Python: >=3.11.4
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: click
Requires-Dist: requests>=2.32.4
Requires-Dist: aiohttp>=3.12.14
Requires-Dist: packaging
Requires-Dist: pypi-attestations>=0.0.28
Requires-Dist: sigstore>=3.0
Requires-Dist: PyYAML>=6.0
Requires-Dist: node-semver>=0.9.0
Requires-Dist: semver>=3.0
Requires-Dist: defusedxml>=0.7.1
Provides-Extra: dev
Requires-Dist: build>=1.2.2; extra == "dev"
Requires-Dist: twine>=6.1.0; extra == "dev"
Requires-Dist: pytest>=8.3; extra == "dev"
Requires-Dist: jsonschema>=4.23; extra == "dev"
Provides-Extra: packaging
Requires-Dist: pyinstaller>=6.0; extra == "packaging"
Dynamic: license-file

# depshieldx

[![PyPI version](https://img.shields.io/pypi/v/depshieldx.svg)](https://pypi.org/project/depshieldx/)
[![Docs](https://img.shields.io/badge/docs-github%20pages-10b981)](https://tee-wealth001.github.io/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](#npm--yarn--pnpm-support), [Cargo / crates.io Support](#cargo--cratesio-support), [Go Modules Support](#go-modules-support), [Maven / Maven Central Support](#maven--maven-central-support), [NuGet Support](#nuget-support), [Pub Support](#pub-support), [RubyGems Support](#rubygems-support), and [Composer Support](#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:

```bash
python -m pip install depshieldx
```

If your machine has multiple Python versions, use a Python `3.11.4+` interpreter explicitly:

```bash
python3.11 -m pip install depshieldx
```

### Standalone binaries (no Python runtime required to run `depshieldx` itself)

Each [GitHub Release](https://github.com/tee-wealth001/depshieldx/releases) 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`.

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 + `pip` on the host, standalone binary or not. If the binary can't find one on `PATH`, it fails with a clear error rather than doing something unsafe.
- Using it against **npm/yarn/pnpm** packages only requires Node.js/`npm` on 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 `go` toolchain itself, not as a separate `depshieldx` step. 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.shasum` is empty for essentially every real package, so there is usually no checksum to verify at all -- the git commit `reference` Packagist 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:

```bash
depshieldx install requests
```

Run the deeper validation path:

```bash
depshieldx install requests --deep
```

Scan without installing:

```bash
depshieldx scan requests
```

Scan a requirements file:

```bash
depshieldx scan -r requirements.txt
```

Install from `pyproject.toml`:

```bash
depshieldx install --pyproject pyproject.toml
```

## Requirements

`depshieldx` is safest when the local runtime tools are current:

- Python `3.11.4` or newer
- `pip` `25.3` or newer
- Docker installed and running for `--deep`
- Trivy installed for the deeper container scan path

Install local development and release tooling with:

```bash
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:

- `fast`
- `deep`

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:

- `install` installs on the host after the checks pass
- `scan` stops after the checks and does not install anything

This same behavior applies to:

- direct package names
- multiple package names in one command
- `requirements.txt`
- `uv.lock`
- `pyproject.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:

```bash
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:

```bash
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](#routing) 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](#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 npm` instead

## 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:

```bash
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:

```bash
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](#routing) 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](#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](#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 via `cargo add`
- `Cargo.toml`-as-input -- only `Cargo.lock` or bare crate names via `--ecosystem cargo` are 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:

```bash
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:

```bash
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](#routing) 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](#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](#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 via `go get`
- `go.mod`-as-input -- only `go.sum` or bare module paths via `--ecosystem go` are accepted
- cryptographic provenance verification of the kind PyPI/npm have (per-package Sigstore signing) -- Go's real checksum-transparency verification already happens inside the `go` toolchain itself, not as a separate `depshieldx`-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`:

```bash
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](#routing) 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](#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](#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](#routing) -- Maven has no native "add a dependency" CLI command to intercept
- `pom.xml`-as-input -- only explicit `groupId:artifactId[:version]` coordinates via `--ecosystem maven` are 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:

```bash
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:

```bash
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](#routing) 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](#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](#provenance-and-attestations).

What's still explicitly **not** supported for NuGet:

- `.csproj`-as-input -- only `packages.lock.json` or bare package names via `--ecosystem nuget` are accepted
- cryptographic chain verification of the repository signature -- `depshieldx` has 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:

```bash
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:

```bash
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](#routing) 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](#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](#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 -- only `pubspec.lock` or bare package names via `--ecosystem pub` are 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:

```bash
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:

```bash
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](#routing) 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](#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](#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 -- only `Gemfile.lock` or bare gem names via `--ecosystem rubygems` are accepted
- cryptographic signature verification of any kind -- rubygems.org's Sigstore support is still opt-in/in-progress, and the older X.509 `gem cert` scheme 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) -- `depshieldx` always resolves and verifies against the platform-agnostic "ruby" build, matching what a real `bundle lock` and 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:

```bash
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:

```bash
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](#routing) 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](#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](#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 -- only `composer.lock` or bare package names via `--ecosystem composer` are 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`; `depshieldx` relies on this default rather than working around it, and never allow-lists anything itself

## Commands

Main commands:

- `depshieldx install`
- `depshieldx scan`
- `depshieldx uninstall`
- `depshieldx ui`
- `depshieldx routing status`
- `depshieldx routing enable`
- `depshieldx routing disable`
- `depshieldx receipts list`
- `depshieldx receipts verify <path>`
- `depshieldx receipts delete`

Get help at any level:

```bash
depshieldx --help
depshieldx install --help
depshieldx scan --help
depshieldx uninstall --help
depshieldx ui --help
depshieldx receipts --help
```

## Local UI

`depshieldx ui` opens a local, read-only browser view over cached receipts and related cache entries.

- binds to `127.0.0.1` only
- uses port `0` by default so the OS can choose a free port
- opens the browser automatically unless you pass `--no-open`

Examples:

```bash
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 Checks` workflow manually
- for PyPI, push a version tag such as `v0.1.0` -- this also triggers the `Release Binaries` workflow, which builds and attaches the standalone per-platform binaries to the matching GitHub Release

## Common Examples

Install one package:

```bash
depshieldx install fastapi
```

Install multiple packages:

```bash
depshieldx install langchain requests --deep
```

Scan only:

```bash
depshieldx scan fastapi --fast
depshieldx scan fastapi --deep
```

Use a requirements file:

```bash
depshieldx install -r requirements.txt
depshieldx scan -r requirements.txt --deep
```

Use a lockfile:

```bash
depshieldx install --lockfile uv.lock
depshieldx scan --lockfile uv.lock
```

Use a `pyproject.toml` file:

```bash
depshieldx install --pyproject pyproject.toml
depshieldx scan --pyproject pyproject.toml --deep
```

Open the local cache UI:

```bash
depshieldx ui
depshieldx ui --port 8765
depshieldx ui --no-open
```

Uninstall packages:

```bash
depshieldx uninstall requests
depshieldx uninstall -r requirements.txt
depshieldx uninstall --pyproject pyproject.toml
```

npm packages and lockfiles:

```bash
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:

```bash
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:

```bash
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:

```bash
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:

```bash
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:

```bash
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:

```bash
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:

```bash
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.lock` is parsed directly
- `package-lock.json`, `yarn.lock`, and `pnpm-lock.yaml` are parsed directly
- `Cargo.lock` is 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 dropped
- `go.sum` resolution reads the sibling `go.mod`'s full resolved module graph (via `go list -m all`) rather than parsing `go.sum` itself, since `go.sum` is a checksum allowlist, not the resolved graph -- it can list more module versions than actually ship
- `packages.lock.json` is 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 as `Cargo.lock`
- `pubspec.lock` is parsed directly (it's real YAML); only `source: hosted` entries are resolved against the registry -- `source: git`/`source: path`/`source: sdk` entries 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 are
- `Gemfile.lock` is parsed directly (Bundler's own custom text format, not YAML/JSON); only the `GEM` section's resolved specs are read -- `GIT`/`PATH` sections have no registry checksum to verify against and are skipped, the same "not every entry is registry-fetchable" case pubspec.lock's own `source: hosted`-only filtering already is
- `composer.lock` is parsed directly (it's real JSON); direct-vs-transitive dependency status is read from the sibling `composer.json`'s own `require`/`require-dev` tables, the same "need the sibling manifest" case `Cargo.lock`/`go.sum` already are, since neither of `composer.lock`'s own `packages`/`packages-dev` arrays marks its entries either way
- other PyPI lockfile-style inputs are treated like requirement-style pinned targets

## Output Modes

Human-readable summary:

```bash
depshieldx install requests --output summary
```

JSON only:

```bash
depshieldx install requests --output json
```

Summary plus JSON:

```bash
depshieldx install requests --output both
depshieldx install requests --full-report
```

## What The Summary Means

Key summary lines:

- `Scan verdict`
- `CVE sources across all resolved packages`
- `Provenance verdict`
- `Attestation verification`
- `Sandbox verdict`
- `Trivy verdict`
- `Host install`
- `Receipts`

`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:

```text
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: true` flag -- see [RubyGems Support](#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.shasum` is 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 `reference` Packagist 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](#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:

```text
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.toml` runs also write per-requested-package receipts when possible

Receipt commands:

```bash
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`.

```bash
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 through `depshieldx install <package>`
- `npm install` / `npm i` / `npm ci` / `yarn install` / `pnpm install` with no package named -- routed through `depshieldx install --lockfile <lockfile-in-cwd>`, only when that lockfile is present
- `npm install <package...>` / `npm i <package...>` -- one or more package names with no other flags, routed through `depshieldx install <package...> --ecosystem npm`
- `yarn add <package>` / `pnpm add <package>` are **not** intercepted yet -- ad-hoc resolution in this phase only covers `npm install <package>`, so yarn/pnpm named installs pass straight through to the real tool
- `cargo add <crate...>` -- one or more crate names with no other flags, routed through `depshieldx install <crate...> --ecosystem cargo`. `cargo install` (binary crates) is not intercepted -- depshieldx's cargo support only covers `cargo add`
- `go get <module...>` -- one or more module paths with no other flags, routed through `depshieldx install <module...> --ecosystem go`. `go install` (binary programs) is not intercepted -- depshieldx's Go support only covers `go get`
- `dotnet add package <name>` / `dotnet add package <name> --version <version>` -- exactly one package, no other flags, routed through `depshieldx install <name>[@version] --ecosystem nuget`. No project positional and no other `dotnet add package` flags (`--framework`, `--prerelease`, ...) are intercepted -- anything beyond this exact shape passes straight through to the real `dotnet`
- `dart pub add <package...>` -- one or more package names with no other flags, routed through `depshieldx 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 real `dart`
- `bundle add <gem...>` (no other flags) -- one or more gem names, routed through `depshieldx install <gem...> --ecosystem rubygems`. `bundle add <gem> --version <version>` (exactly one gem) is also intercepted, routed through `depshieldx 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 real `bundle`
- `composer require <package...>` (no other flags) -- one or more `vendor/package[:constraint]` targets, routed through `depshieldx 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 real `composer` rather 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](#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_DIR`
- `DEPSHIELDX_RECEIPTS_DIR`
- `DEPSHIELDX_NO_ROUTING_PROMPT=1`
- `DEPSHIELDX_ROUTE_DEEP=1`

## Cache Location

By default, local state lives under:

```text
~/.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:

```bash
depshieldx ui
```

## Exit Codes

- `0`: success
- `10`: blocked by resolution, provenance, vulnerability checks, or Trivy
- `11`: deep mode could not use Docker and the install or scan was skipped for that reason
- `12`: host install was attempted but failed

## Limitations

- deep mode depends on Docker being available (for npm, a small local `node:20` + `strace` image is built on first use; for Cargo, a small local `rust:1-slim` + `strace` image is built on first use; for Go, a small local `golang:1-bookworm` + `strace` image is built on first use; for Maven, a small local `maven:3-eclipse-temurin-21` + `strace` image, with Maven's own default-lifecycle plugin set pre-warmed in, is built on first use; for NuGet, a small local `mcr.microsoft.com/dotnet/sdk:8.0` + `strace` image is built on first use; for Pub, a small local `dart:3` + `strace` image is built on first use; for RubyGems, a small local `ruby:3` + `strace` image is built on first use; for Composer, a small local `php:8.4-cli` + `strace` image (plus the `zip` PHP extension and `unzip`, and Composer itself copied from the official `composer:2` image) is built on first use -- see [npm / yarn / pnpm Support](#npm--yarn--pnpm-support), [Cargo / crates.io Support](#cargo--cratesio-support), [Go Modules Support](#go-modules-support), [Maven / Maven Central Support](#maven--maven-central-support), [NuGet Support](#nuget-support), [Pub Support](#pub-support), [RubyGems Support](#rubygems-support), and [Composer Support](#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 `strace` rather 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 `pip` versions
- Cargo resolution, install, and deep mode all shell out to a local `cargo` on `PATH`; there is no preflight check for this, so a missing Rust toolchain only surfaces later, as a resolution failure
- Go resolution, install, and deep mode all shell out to a local `go` on `PATH` the same way; there is no preflight check for this either, so a missing Go toolchain only surfaces later, as a resolution failure
- Maven resolution, install, and deep mode all shell out to a local `mvn` on `PATH` the same way; there is no preflight check for this either, so a missing Java/Maven toolchain only surfaces later, as a resolution failure
- NuGet resolution, install, and deep mode all shell out to a local `dotnet` on `PATH` the same way; there is no preflight check for this either, so a missing .NET SDK only surfaces later, as a resolution failure
- Pub resolution, install, and deep mode all shell out to a local `dart` on `PATH` the same way; there is no preflight check for this either, so a missing Dart SDK only surfaces later, as a resolution failure
- RubyGems resolution, install, and deep mode all shell out to a local `bundle` on `PATH` the same way; there is no preflight check for this either, so a missing Ruby/Bundler install only surfaces later, as a resolution failure
- Composer resolution, install, and deep mode all shell out to a local `composer` on `PATH` the same way; there is no preflight check for this either, so a missing PHP/Composer install only surfaces later, as a resolution failure
- `Cargo.lock` parsing 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.json` parsing 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's `skipped_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 `go` toolchain 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](#routing)
- NuGet's repository-signature check is presence-only, not a real certificate-chain validation -- `depshieldx` has no trust-root story for X.509 elsewhere, so a forged or expired certificate chain would still record as "signed"
- both `dotnet add package` and `dotnet remove package` (and so `depshieldx`'s own NuGet install/uninstall/routing) are scoped to exactly one package per invocation -- the real `dotnet` CLI 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 -- `depshieldx` skips it explicitly rather than silently querying the wrong system, so a Pub scan's `deps-dev: no vulnerabilities` means "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 actual `flutter build`, not anything `dart run`/`dart pub get` trigger) is out of scope, since `depshieldx`'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 cert` scheme 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: true` flag (confirmed directly against the real 2019 `rest-client` hijack incident) -- since a 404 there is ambiguous between "never existed" and "yanked and purged", `depshieldx` reports it as an honest, non-blocking warning rather than asserting a confirmed yank
- `depshieldx` never runs `bundle install`/`bundle lock --local` on the host for RubyGems deep mode -- confirmed directly `bundle install` triggers real native-extension compilation, unlike every other ecosystem's own host-side restore/fetch step; the Trivy-facing `Gemfile.lock` is written directly from the already-known resolution instead, and only the fully-isolated sandbox container actually runs `bundle install`
- platform-specific RubyGems variants (a gem publishing separate prebuilt binaries per OS/CPU) are out of scope -- `depshieldx` always resolves and verifies against the platform-agnostic "ruby" build, matching what a real `bundle lock` and 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 `reference` Packagist 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 -- `depshieldx` skips it explicitly rather than silently querying the wrong system, so a Composer scan's `deps-dev: no vulnerabilities` means "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 `depshieldx` never does) and project-level script hooks (root-project-only by design, never triggered by a dependency's own `composer.json`) are both out of scope, since neither is a real risk in `depshieldx`'s own default, unmodified install flow
- `depshieldx` never runs `composer install`/`composer update` on the host to build the Trivy-facing `composer.lock` for 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, so `depshieldx` writes 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:

```bash
depshieldx install <package>
```

Use `--deep` when you want the extra Docker + Trivy validation step.
