Skip to content

Package Scanner

The Package Scanner is a cross-platform CLI for scanning dependency manifests and uploading package inventory to ShieldedStack. It is useful for seeding existing repositories, scanning projects that do not restore through the proxy, and adding scheduled CI inventory checks.

Scanner uploads are sent to the package proxy at <proxy-url>/scans. The proxy URL has no default: set it to your deployed proxy, or the scanner refuses to upload.

Binaries are published for five platforms, always at the same URL for the current release:

Platform File
Linux (x64) package-scanner-linux-amd64
Linux (ARM64) package-scanner-linux-arm64
macOS (Intel) package-scanner-darwin-amd64
macOS (Apple Silicon) package-scanner-darwin-arm64
Windows (x64) package-scanner-windows-amd64.exe

Each is served from https://tools.shieldedstack.com/u/<file>, and SHA256 checksums for all five are published together at https://tools.shieldedstack.com/r/checksums.txt.

Verify the download before you run it. The steps below do that, substituting the file for your platform.

Terminal window
curl -LO https://tools.shieldedstack.com/u/package-scanner-linux-amd64
curl -L -o checksums.txt https://tools.shieldedstack.com/r/checksums.txt
grep package-scanner-linux-amd64 checksums.txt | sha256sum -c -

sha256sum prints OK when the file matches. If it does not, stop and do not run the binary.

Then put it on your PATH:

Terminal window
install -m 755 package-scanner-linux-amd64 ~/.local/bin/package-scanner

Use package-scanner-linux-arm64 on ARM64 machines.

The download URLs always serve the current release, so an unpinned CI job picks up a new binary whenever one ships. Record the checksum you verified and check it on every run, as the GitHub Actions example does. The job then fails loudly when the binary changes rather than quietly running a different one.

<proxy-url> is the base URL of the package proxy, without a trailing slash. Pass it with --proxy-url <proxy-url>, or set SHIELDEDSTACK_PROXY_URL in the environment.

  • Multi-ecosystem support: npm, NuGet, pip, Go, Maven, Cargo, RubyGems, Dart, Composer, Hex, Swift.
  • Manifest-only scanning: Fast scans without dependency resolution.
  • Concurrent processing: Parallel manifest parsing.
  • Batch uploads: Efficient bulk submission to ShieldedStack.
  • Retry logic: Automatic retries with exponential backoff.
  • Rate limiting: Respects API rate limits.
  • Dry-run mode: Preview findings without uploading.
  • CI/CD ready: Non-interactive execution with clear exit codes.
  • package-lock.json v2/v3. Scoped names such as @babel/core are kept; workspace packages are skipped.
  • yarn.lock, both the Yarn 1 format and the Yarn 2+ format. Only registry packages are recorded; workspace, link, file, git, and patch entries are skipped, and npm aliases are recorded under the real package name.
  • pnpm-lock.yaml, lockfile versions 5, 6, and 9. Workspace projects, git and directory dependencies, and tarball URLs are skipped.
  • package.json fallback, used only when there is no package-lock.json, yarn.lock, or pnpm-lock.yaml beside it. Only exact versions such as 4.19.2 are recorded; ranges like ^4.19.0, tags, and file:, git, or workspace: specifiers are skipped because they do not say which version is installed. Commit a lockfile for complete coverage.
  • packages.lock.json.
  • *.csproj / *.fsproj / *.vbproj using PackageReference, with the version as an attribute, a child element, or VersionOverride. Under central package management the version comes from the nearest Directory.Packages.props. Version ranges and floating versions such as 2.* are skipped; packages.lock.json records the restored version.
  • packages.config.
  • paket.lock. Every package in a NUGET section is recorded, in all groups. GITHUB, GIT, and HTTP entries pin files rather than packages and are skipped.
  • requirements.txt. Only exact pins (== or ===) are recorded; ranges, URL, editable, and VCS requirements are skipped. Environment markers, extras, and --hash lines are handled.
  • Pipfile.lock.
  • poetry.lock. Git, directory, file, and URL sources are skipped; private indexes are included.
  • uv.lock. Packages from PyPI or a private index are included; git, path, URL, and the project’s own workspace members are skipped.

Python package names are recorded in their normalized form (Flask_Login becomes flask-login), matching how the proxy records them.

  • go.mod.
  • pom.xml. A dependency without a version takes it from the same file’s <dependencyManagement>; versions that come from an imported BOM or a parent POM are not resolved, and those dependencies are skipped.
  • gradle.lockfile, plus buildscript-gradle.lockfile and settings-gradle.lockfile. Gradle writes these only when dependency locking is enabled; build scripts are never evaluated.
  • Cargo.lock. Registry crates only; workspace members, path, and git dependencies are skipped.
  • Gemfile.lock. Gems from the GEM section only; GIT and PATH gems are skipped.
  • pubspec.lock.
  • composer.lock, covering packages and packages-dev.
  • mix.lock: Hex registry dependencies are included; Git and path dependencies are skipped. The scanner parses the lockfile’s literal Erlang-term subset and rejects executable or unsupported terms rather than evaluating them.
  • rebar.lock: Hex pkg dependencies in the current Rebar3 lockfile shape are included; Git and path dependencies are skipped. Other lockfile shapes are not supported.
  • Package.resolved (format versions 1, 2, and 3): source-control dependencies are named by repository path, such as github.com/apple/swift-nio, so they match OSV and GitHub advisories. Registry dependencies keep their registry identity. Branch, revision, and local pins are skipped. Package.swift is never evaluated. Swift is scanning-only; see Swift.
Terminal window
package-scanner --api-key YOUR_API_KEY --proxy-url <proxy-url> /path/to/project
Terminal window
export SHIELDEDSTACK_API_KEY=your_api_key
export SHIELDEDSTACK_PROXY_URL=<proxy-url>
package-scanner /path/to/project

With the default HTTP endpoint, this looks like:

Use HTTPS in production. The API key is a credential and must not be sent over an unencrypted connection.

Terminal window
export SHIELDEDSTACK_PROXY_URL=http://<host>:3003
package-scanner --api-key YOUR_API_KEY .

Preview findings without uploading:

Terminal window
package-scanner --dry-run --api-key YOUR_API_KEY --proxy-url <proxy-url> .
Terminal window
package-scanner --api-key YOUR_API_KEY --proxy-url <proxy-url> --exclude "test,examples" .
name: Scan Dependencies
on:
schedule:
- cron: '0 0 * * *'
push:
branches: [main]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Download Package Scanner
env:
SCANNER_SHA256: ${{ vars.SCANNER_SHA256 }}
run: |
curl -L https://tools.shieldedstack.com/u/package-scanner-linux-amd64 -o scanner
echo "${SCANNER_SHA256} scanner" | sha256sum -c -
chmod +x scanner
- name: Scan Dependencies
env:
SHIELDEDSTACK_API_KEY: ${{ secrets.SHIELDEDSTACK_API_KEY }}
SHIELDEDSTACK_PROXY_URL: ${{ vars.SHIELDEDSTACK_PROXY_URL }}
run: ./scanner --no-color .

Store SHIELDEDSTACK_API_KEY as a protected CI secret. Store SHIELDEDSTACK_PROXY_URL as a variable or secret depending on your internal policy.

Flag Environment Variable Description
--api-key SHIELDEDSTACK_API_KEY ShieldedStack API key. Required for uploads.
--proxy-url SHIELDEDSTACK_PROXY_URL Package proxy base URL. Required unless --dry-run is set.
--include - Only scan manifests matching these glob patterns, matched against the path relative to the target directory or the file name. * does not cross /.
--exclude - Additional exclude patterns.
--dry-run - Print findings without uploading.
--output-json - Write results to JSON file.
--format - Output format: text or json.
--no-color - Disable colored output.
--concurrency - Worker pool size.

Do not rely on the scanner binary’s built-in proxy URL default for on-prem deployments. Set SHIELDEDSTACK_PROXY_URL or pass --proxy-url explicitly.

The scanner automatically excludes these directories:

  • node_modules
  • bin
  • obj
  • .venv / venv
  • .git
  • __pycache__
  • .pytest_cache
  • .mypy_cache
  • 0: Success; all packages uploaded.
  • 1: Upload errors such as network, auth, or rate-limit failures.
  • 2: Scan failures such as manifest parsing errors.

For monorepos with multiple projects or workspaces, run the scanner multiple times with different API keys and target directories:

Terminal window
# Frontend project
package-scanner --api-key FRONTEND_KEY --proxy-url <proxy-url> ./frontend
# Backend project
package-scanner --api-key BACKEND_KEY --proxy-url <proxy-url> ./backend

Verify your API key is correct, has not expired, and belongs to the workspace you expect:

Terminal window
package-scanner --dry-run --api-key YOUR_API_KEY --proxy-url <proxy-url> .

Then run without --dry-run to test upload authentication.

Confirm the active proxy URL:

Terminal window
printf '%s\n' "$SHIELDEDSTACK_PROXY_URL"

The scanner posts inventory to <proxy-url>/scans.

If you hit rate limits with 429 responses, the scanner automatically retries with backoff. Current limit: 5,000 packages/hour per API key.

Use JSON output to see which manifests fail to parse:

Terminal window
package-scanner --format json --api-key YOUR_API_KEY --proxy-url <proxy-url> . 2>&1 | tee scan.log