CLI Tool
Install and use the Ossprey CLI to scan projects and packages from your terminal or CI/CD pipeline.
Account requiredThis tool requires an account with Ossprey. Visit ossprey.com to sign up for free, then either run
ossprey loginto sign in via your browser, or provide an API key.
The Ossprey CLI (ossprey) is a command-line scanner for the Ossprey supply-chain malware platform. It catalogues your project's dependencies into a custom SBOM (software bill of materials) format, submits it to the Ossprey API, and fails the build if any of those packages are known to contain malware. It parses the manifests of JS/Python projects, and resolves transitive dependencies with no installation.
The CLI supports:
- One-command setup — log in, create an API key, and run a first scan — via the
initcommand - Scanning a project's dependencies via the
scancommand - Ad-hoc checking of dependencies via the
checkcommand - JS/Python package manager pass-through, checking packages before installation —
ossprey npm i <package>etc., or transparently via PATH shims - Blocking commits that add known-malicious packages via a git pre-commit hook
- Skipping your own internal packages — from private registries or npm scopes — via trusted sources
The CLI ships as a single self-contained binary via GitHub Releases. There is no pip, npm or Homebrew package. It currently covers Python and JavaScript projects.
Installation
Prebuilt binariesPrebuilt binaries are published for Linux, macOS and Windows (amd64 and arm64). No interpreter or runtime is required. If you need help, please raise an issue on GitHub.
One-liner (Linux / macOS)
curl -fsSL https://github.com/ossprey/ossprey-cli/releases/latest/download/install.sh | sudo shThe script detects your OS and architecture, downloads the matching binary, verifies its sha256 checksum, and installs it to /usr/local/bin/ossprey.
Before installing, check out the full contents of the script here.
Override the defaults with environment variables to pin to a version:
# Pin a specific version
curl -fsSL https://github.com/ossprey/ossprey-cli/releases/latest/download/install.sh \
| OSSPREY_VERSION=v0.1.0 sudo -E sh
# Install to a user-writable dir (no sudo)
curl -fsSL https://github.com/ossprey/ossprey-cli/releases/latest/download/install.sh \
| OSSPREY_INSTALL_DIR=$HOME/.local/bin shOne-liner (Windows PowerShell)
Runs natively on Windows — no WSL, no admin rights needed. From any PowerShell prompt:
irm https://github.com/ossprey/ossprey-cli/releases/latest/download/install.ps1 | iexThe script detects your architecture, downloads the matching ossprey.exe, verifies its sha256 checksum, installs it to %LOCALAPPDATA%\Programs\ossprey, and adds that directory to your user PATH (open a new terminal to pick it up). Pin a version with $env:OSSPREY_VERSION, or change the location with $env:OSSPREY_INSTALL_DIR.
Manual download
Grab the binary directly from the releases page:
ossprey-linux-amd64— Linux x86_64ossprey-linux-arm64— Linux arm64ossprey-darwin-amd64— macOS Intelossprey-darwin-arm64— macOS Apple Siliconossprey-windows-amd64.exe— Windows x86_64ossprey-windows-arm64.exe— Windows arm64
chmod +x the binary and drop it on your PATH. Each asset ships with a .sha256 sidecar for verification. Pin a specific tag by replacing latest/download with download/<tag> in the URL.
From source
git clone https://github.com/ossprey/ossprey-cli.git
cd ossprey-cli
make tidy # first time
make build # produces bin/osspreyRequires Go 1.25 or above.
Once installed, verify it is working:
ossprey --version
ossprey --helpUpdating
The CLI can update itself:
ossprey update # update in place to the latest release
ossprey update --check # just report whether an update is available
ossprey update --version v0.2.0 # install a specific versionupdate downloads the release binary for your OS and architecture, verifies its sha256 checksum, and atomically replaces the running executable. If the binary lives in a root-owned directory (the /usr/local/bin default on Linux/macOS), run sudo ossprey update; the Windows default is user-writable, so no elevation is needed.
Authentication
There are two ways to authenticate:
Browser login (interactive use). Run ossprey login once — it opens your browser, you confirm a one-time code, and the CLI stores the resulting tokens locally (in your platform's user config dir; override with OSSPREY_CONFIG_DIR). Scans then authenticate automatically and tokens refresh silently. ossprey whoami shows the current login; ossprey logout removes it.
API key (CI / non-interactive use). Create a key in the dashboard or with ossprey init, and provide it via flag or environment variable. Keys look like ospy_ followed by 64 hex characters.
Credentials are resolved in order:
--api-keyflag- the stored
ossprey loginsession OSSPREY_API_KEYenvironment variable (recommended for CI/CD)API_KEYenvironment variable (legacy fallback — preferOSSPREY_API_KEY)
A logged-in session wins over API keys exported in the shell; run ossprey logout (or pass --api-key) to force key auth.
The --local, --dry-run-safe and --dry-run-malicious modes do not talk to the API and do not need credentials.
Managing API keysFor details on generating and managing your API key, see Security & API Keys.
init: one-command setup
init: one-command setupossprey init [path]ossprey init gets you from a fresh install to a working, CI-ready setup in one command. It runs three steps:
- Log in — reuses a stored login if there is one, otherwise runs the same browser flow as
ossprey login. - Create an API key and print it once. Keys default to a one-year expiry (
--key-nameand--key-expiryoverride;--no-keyskips the step). - Optionally scan the project with the new key, so a clean scan is proof the key works before you paste it into CI. Answer up front with
--scanor--no-scan; non-interactive runs skip the scan unless--scanis passed.
The key is shown once and cannot be recovered later — Ossprey stores only a hash. For scripted setup, --key-stdout prints only the key on stdout so you can pipe it straight into a secret store:
ossprey init --key-stdout | gh secret set OSSPREY_API_KEYRe-running init creates a new key each time (accounts are capped at 10 keys — delete unused ones in the dashboard, or pass --no-key). init writes no files and doesn't install the pre-commit hook or PATH shims — it prints those commands at the end for you to run yourself.
scan — scan a project
scan — scan a projectCatalogue a directory and check it for malware.
ossprey scan [path] [flags]path defaults to the current directory.
Flags:
-o,--output— write the OSSBOM JSON to a file, in addition to running the scan.-v,--verbose— verbose logging.--local— catalogue only: dump the OSSBOM to stdout and exit. No API submission, no verdict, no key required.--dry-run-safe— skip API submission and emit an empty vulnerability list. No key required.--dry-run-malicious— skip API submission and inject a test finding against the first component. Useful for testing alerting and CI/CD failure behaviour. No key required.--no-version-lookup— don't query the registry to resolve unpinned dependencies; leave them versionless. See Unpinned dependencies below.--url— override the Ossprey API URL (defaulthttps://api.ossprey.com).--api-key— provide the API key on the command line instead of an environment variable.--version— print the CLI version.
Usage examples
Scan the current directory:
export OSSPREY_API_KEY=ospy_...
ossprey scan .Scan a specific directory:
ossprey scan ./my-project --api-key YOUR_KEYDry run to test your setup (no API key needed):
ossprey scan ./my-project --dry-run-safe -vCatalogue only, write the OSSBOM to a file:
ossprey scan . --local -o sbom-output.jsoncheck — scan named packages
check — scan named packagesScan one or more packages by name, without a project on disk.
ossprey check --eco-system pypi [email protected]
ossprey check -e npm [email protected] [email protected]When a version is omitted, the latest published version is resolved from the registry (PyPI or npm) and checked. Both the name@version and pip's name==version forms are accepted.
Flags:
-e,--eco-system— package ecosystem,pypiornpm(required).--url— override the Ossprey API URL.--api-key— API key (or environment variable).--dry-run-safe/--dry-run-malicious— same behaviour as forscan.
Exit codes match scan: 1 on a malware verdict or error, 0 otherwise.
Package-manager forwarder
Wrap an install so packages are checked before they hit your machine. If any package is flagged, the install is blocked (exit 1) and the real package manager is never invoked; otherwise the command is forwarded unchanged.
ossprey npm install [email protected] [email protected]
ossprey pnpm add [email protected]
ossprey yarn add [email protected]
ossprey pip install foo==1.2.3
ossprey poetry add foo
ossprey uv pip install foo==1.2.3Supported managers: npm, pnpm, yarn, pip, pip3, poetry, uv. Non-install subcommands (npm run, pip list, …) are forwarded straight through with no check. Each manager's global options are understood before the subcommand, so workspace forms like pnpm --filter web add x are checked like any other install.
Note that fetch-and-execute commands (npm exec, pnpm dlx, yarn dlx, uv tool run) are not install verbs and are forwarded unchecked, and npx/uvx are separate binaries that bypass the forwarder entirely.
There are two modes, picked automatically:
- Named packages (e.g.
ossprey npm install foo bar) — every package named on the command line is checked. Flags, local paths, archives and VCS/URL targets are passed through; only real registry packages are checked. Transitive dependencies are not resolved here — runossprey scanafter install for full-tree coverage. - Manifest install (e.g. bare
ossprey npm install,npm ci,poetry install,uv sync, orpip install -r requirements.txt) — no packages are named, so the forwarder scans the current directory and checks every declared dependency before forwarding. It does not fall through unchecked.
If the registry can't be reached to resolve an unpinned named version, that package is skipped (fail-open) so a registry outage never blocks development. An install whose only targets are local paths or URLs — nothing checkable, and no manifest to scan — is forwarded with a warning.
The forwarder disables flag parsing so every argument reaches the real manager, so it is configured only through the environment:
OSSPREY_API_KEY— API key.OSSPREY_API_URL— override the API URL (defaulthttps://api.ossprey.com).
A session from ossprey login also counts (and takes precedence over OSSPREY_API_KEY), so on your own machine the forwarder usually needs no environment at all.
Scan on every install automatically
The forwarder only runs when somebody remembers to type ossprey first. Two ways to remove that step:
Shell aliases — one alias per manager in your ~/.bashrc / ~/.zshrc:
for mgr in npm pnpm yarn pip pip3 poetry uv; do alias "$mgr=ossprey $mgr"; doneAliases only exist in interactive shells, though — Makefiles, package.json scripts, CI jobs, and coding agents all miss them. For those, use PATH shims.
PATH shims — a real executable named after each manager, placed at the front of your PATH, so every install is intercepted wherever the command is run from:
ossprey shim installOr install them together with the CLI by adding --override-package-managers to the install one-liner ($env:OSSPREY_OVERRIDE_PACKAGE_MANAGERS = '1' for the PowerShell installer).
| Command | What it does |
|---|---|
ossprey shim install | Write the shims and put their directory first on PATH |
ossprey shim status | Show which managers are intercepted right now |
ossprey shim uninstall | Remove the shims and the PATH entry |
ossprey shim dir | Print the shim directory (for ENV PATH=… in a Dockerfile) |
Shims check only installs — npm run build and friends are exec'd straight through — and fail open: if the ossprey binary goes missing, the shim warns and runs the real manager anyway. Set OSSPREY_SHIM_BYPASS=1 to skip the check for a single command.
Pre-commit hook
ossprey precommit checks the dependencies a commit adds or version-bumps — staged changes to package.json, lockfiles, requirements.txt, pyproject.toml and friends — against Ossprey's database of already-confirmed malware. It runs no new scans: the check is a fast local diff plus one small lookup request, and a commit that touches no dependency manifest never calls the API.
Install it as a plain git hook:
cd your-repo
ossprey precommit installOr via the pre-commit framework, in .pre-commit-config.yaml (use hook id ossprey-system if the CLI is already on your PATH, or ossprey to build from source):
repos:
- repo: https://github.com/ossprey/ossprey-cli
rev: v0.12.1 # pin the latest release
hooks:
- id: ossprey-systemossprey precommit status shows whether the hook is installed; ossprey precommit uninstall removes it (only if Ossprey wrote it — an existing hook of yours is never overwritten).
The hook needs credentials (OSSPREY_API_KEY or a stored ossprey login session) and fails open: no key, network outage, or API error prints a one-line warning and lets the commit through. Exit 1 means exactly one thing — a staged package is known-malicious. git commit --no-verify bypasses it for a single commit.
Packages from trusted sources are left out of the lookup. Two things it deliberately does not check: unpinned ranges (a manifest change like "left-pad": "^1.3.0" with no lockfile staged alongside it is skipped — pin the version or commit a lockfile) and packages already committed (auditing what's already in the tree is ossprey scan's job).
Trusted sources
If you publish internal packages to a private registry, Ossprey can't look them up publicly: scans fill with Not found components, and your internal package names are sent with every scan. Declare the sources that are yours, and packages from them are neither checked nor sent.
ossprey trust add --npm-scope @my-org
ossprey trust add --registry https://my-org-123456789012.d.codeartifact.eu-west-1.amazonaws.com/pypi/internal/
ossprey trust list
ossprey trust remove --npm-scope @my-org| Rule | Trusts | Decided from |
|---|---|---|
--registry <url> | npm or PyPI packages fetched from that registry or index | Where your lockfile says each package came from |
--npm-scope @org | Every @org/* npm package | The package name |
Registries match on the full URL prefix, not the host. One CodeArtifact domain can hold an internal repository of packages you publish beside a public-proxy repository that mirrors PyPI. Trusting …/pypi/internal/ leaves …/pypi/public-proxy/ checked. Register the repository's root URL, without the trailing /simple.
The source is read from package-lock.json and yarn.lock (resolved), uv.lock (source.registry) and poetry.lock ([package.source] url), or from pip when there is no lockfile. A package whose source isn't recorded is checked. That includes pnpm-lock.yaml and Pipfile.lock, which don't record a URL. A package seen from a trusted registry in one lockfile and from any other source in another is also checked.
There is deliberately no name rule for PyPI. Anyone can publish my-org-anything to PyPI, and an internal name resolved from the public index (dependency confusion) is exactly what an ignore must never hide. An npm scope is safe to trust by name when your .npmrc maps it to your registry (@my-org:registry=https://…), because npm then never falls back to the public registry, or when your organisation owns the scope on npmjs.com.
Trust applies to scan, the pre-commit hook, the package-manager forwarder and PATH shims, in both blocking and passive mode:
- Bare installs (
npm install,npm ci,uv sync,poetry install) scan your lockfile, so both rules apply. - Named installs (
npm install @my-org/web) apply the npm scope rule only, because a package that isn't installed yet has no lockfile entry to read. ossprey checkignores trust: a package you name is checked.
Trusted packages are left out of the SBOM entirely, so --local and -o show exactly what was sent. The scan still reports them on stderr, as one line such as ossprey: 142 packages are from trusted sources; not checked or sent. OSSPREY_VERBOSE=1 lists them.
Trust is stored on your machine, in trust.json next to your login. For CI, set OSSPREY_TRUSTED_REGISTRIES and OSSPREY_TRUSTED_NPM_SCOPES (comma-separated), which add to the file. Trust is never read from the repository being scanned, because otherwise a pull request could trust its own registry. An entry that fails validation is ignored with a warning, and its packages are checked.
Only trust what you publishTrust a repository only if it contains nothing but packages you publish. A CodeArtifact, Artifactory or Nexus repository with an upstream connection to a public registry serves public packages under its own URL. Trusting it trusts every package it proxies, malware included.
Supported ecosystems
Python and JavaScript, via static catalogers. The CLI never executes your package manager. If your repo has only a manifest and no lockfile, expect direct dependencies only — supply a lockfile for full transitive coverage.
- Python —
requirements.txt,Pipfile.lock,poetry.lock,uv.lock,pdm.lock,setup.py,pyproject.toml, wheel / egg metadata. - JavaScript —
package.json,package-lock.json,yarn.lock,pnpm-lock.yaml.
Vendored dependency trees (anything under node_modules/) are skipped — the CLI catalogues what your manifests and lockfiles declare, so an installed tree is never double-counted.
Packages defined locally in your own project (workspace members, path dependencies) are flagged as local rather than looked up in a registry, so your first-party code is never mistaken for a published package.
Unpinned dependencies
When a dependency's version can't be determined — an unpinned range in a manifest such as click = "^8", with no lockfile to pin it against — the CLI defaults that component to the latest published version in its registry: the version a fresh install would pull today. This gives a realistic verdict for projects that use open ranges instead of silently dropping the dependency.
Registry lookups fail open. If a version can't be resolved (you're offline, or the package is private or removed), the component is left unversioned rather than dropped or failing the scan. Packages from trusted sources are never looked up.
To skip these lookups — for a fully offline catalogue, or for reproducibility — pass --no-version-lookup, or set OSSPREY_RESOLVE_LATEST=0. The environment variable also covers the package-manager forwarders, whose arguments are passed through untouched and so can't take the flag.
Environment variables
| Variable | Purpose |
|---|---|
OSSPREY_API_KEY | API key. Read by every command, including the forwarders. |
API_KEY | Legacy fallback for OSSPREY_API_KEY. |
OSSPREY_API_URL | Override the API URL for the package-manager forwarders (default https://api.ossprey.com). scan and check use the --url flag instead. |
OSSPREY_RESOLVE_LATEST | Set to 0 to disable resolving unpinned dependencies to the latest published version. |
OSSPREY_SCAN_CONCURRENCY | How many manifests to parse in parallel (default 8). Lower it on constrained CI runners. |
OSSPREY_CONFIG_DIR | Where ossprey login stores its credentials, and ossprey trust its trust.json (defaults to your platform's user config dir). |
OSSPREY_TRUSTED_REGISTRIES | Extra trusted registry URL prefixes, comma-separated. Adds to ossprey trust. |
OSSPREY_TRUSTED_NPM_SCOPES | Extra trusted npm scopes, comma-separated. Adds to ossprey trust. |
OSSPREY_SHIM_BYPASS | Set to 1 to skip the PATH-shim check for one command. |
OSSPREY_PRECOMMIT_TIMEOUT | Time budget for the pre-commit lookup before it fails open (default 10s). |
Exit codes
The CLI uses exit codes to communicate scan outcomes, which is important for CI/CD integration:
- Exit 0 — no malware found, a
--localdump, or the scan was skipped by the API (e.g. quota exhausted). - Exit 1 — malware was found, or the scan itself failed (bad path, catalog error, API/network error, missing key).
- Exit 2 — the CLI crashed unexpectedly. Please raise an issue with the output.
To distinguish "clean" from "errored" in CI, check stderr or parse the OSSBOM emitted via -o.
Scans skipped by quotaIf your daily or monthly package quota is exhausted, the API skips the scan and the CLI prints
Ossprey scan skipped:with the quota reset time, then exits0— a quota limit fails open so it never breaks your build. Check your usage on the Account page.
Output
ossprey scan prints No malware found on success, or one Error: WARNING: <pkg>:<ver> contains malware. Remediate this immediately line per finding on failure.
Pass -o sbom.json to also write the full OSSBOM JSON (components and vulnerabilities) to disk, or --local to emit it to stdout instead of calling the API.
CI/CD integration
GitHub Actions
The Ossprey CLI works as a step in any GitHub Actions workflow. Here is an example that scans your repository on every pull request:
name: Ossprey Scan
on:
pull_request:
branches: [main]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Ossprey
run: curl -fsSL https://github.com/ossprey/ossprey-cli/releases/latest/download/install.sh | sudo sh
# To pin a release, use:
# .../releases/download/<TAG>/install.sh
- name: Run Ossprey scan
env:
OSSPREY_API_KEY: ${{ secrets.OSSPREY_API_KEY }}
run: ossprey scan .The CLI exits non-zero on a malware verdict, which fails the workflow.
Keep your key secretStore your API key as a GitHub Actions secret called
OSSPREY_API_KEY. Never hard-code your key in a workflow file.ossprey init --key-stdout | gh secret set OSSPREY_API_KEYcreates a key and stores it as the secret in one step, without it ever touching your scrollback.
Other CI/CD systems
The CLI is a single static binary with no runtime dependencies, so it works in any environment. Install it via the one-liner or a pre-downloaded binary, set OSSPREY_API_KEY, and run ossprey scan . as a build step.
Updated 9 days ago
