CLI Reference
The packageprobe command-line interface is a thin wrapper over the same Rust
engine as the macOS app — every scan, policy evaluation, and export produces
identical results. The short alias pkgprb is interchangeable with packageprobe.
Run packageprobe --help, or packageprobe <command> --help for any subcommand,
to see this reference from the binary itself.
packageprobe [GLOBAL OPTIONS] <COMMAND> [ARGS]Licensing
Section titled “Licensing”The command-line binary is unlocked by the CLI add-on (CLI entitlement),
separate from the Base app. Every work command (scan, scan-image, sbom,
sarif, vex, sbom-diff, fix) first requires the CLI add-on; without it the
command prints an upgrade prompt and exits 3. On top of that, each work command
gates on its own capability entitlement:
scanandscan-imagerequireSCANNING(Base).--policyalso requiresPOLICY.sbom,sarif, andsbom-diffrequireSBOM(Compliance pack).vexrequiresVEX;fixrequiresCVE_FIX.
The account/inspection commands (trial, login, logout, license, config,
audit-verify) are exempt from the CLI gate so you can always activate. During the
7-day trial everything is unlocked.
Global options
Section titled “Global options”--config, --license-file, and --help apply to every subcommand;
-V/--version is accepted only at the top level.
| Option | Type | Description |
|---|---|---|
-c, --config <PATH> | path | Config file to use. Default: ~/.packageprobe/config.yaml. |
--license-file <PATH> | path | Validate against a license from this file instead of ~/.packageprobe/license.json. Overrides the PACKAGEPROBE_LICENSE_KEY stateless path for the entitlement check. Ignored by trial/login/logout/config/audit-verify (which manage the license file themselves). Note: license refresh always re-fetches and rewrites the default ~/.packageprobe/license.json, not the override. |
-h, --help | flag | Print help. Works on any subcommand (packageprobe <command> --help). |
-V, --version | flag | Print version. Top-level only — packageprobe --version; it is not accepted on subcommands. |
Exit codes
Section titled “Exit codes”Every subcommand uses one differentiated exit-code taxonomy — a stable CLI surface. Higher codes outrank lower ones (an incomplete gate can never masquerade as a clean violation).
| Code | Name | Meaning |
|---|---|---|
0 | OK | The work completed and no requested gate failed. For a gate: complete evidence, no violations (PASS). Not a claim that the scan found nothing — see the caution below. |
1 | Violation | Findings failed a gate — a policy violation, new --alerts issues since the last scan, a fix whose advisory survived a completed re-check, or a broken audit-verify hash chain. |
2 | Incomplete | No trustworthy verdict. Either incomplete/absent evidence (an incomplete scan, a policy rule whose vulnerability evidence is absent or degraded, an audit log with no records, a fix whose verification could not run) or a bad invocation (a clap usage error, a rejected flag value such as --format/--backend/--scope, or a fix argument that isn’t a directory). |
3 | Operational | The command could not run at all — config unreadable, scan couldn’t execute, an alert check that could not reach its data, a licence-server call that failed, a package manager that errored, or a missing entitlement (the CLI add-on or the command’s own capability). |
The invariant: a command exits 0 only when the work completed and no gate
you asked for failed. A failure to run is never 0 (that would be a false
all-clear) and never 1 (that would masquerade as a finding). So a CI step can
branch: 0 no requested gate failed · 1 fix the finding · 2 re-check the
invocation or add --vulns · 3 fix the environment/licence. A step that keys on
“non-zero == failure” is unaffected.
The account commands (trial, login, logout, license, config) exit 0
on success and 3 on error — they produce no findings, so a failure there is
always operational.
Commands
Section titled “Commands”Scan the configured paths for projects and their dependencies.
packageprobe scan [OPTIONS]| Flag | Type | Default | Description |
|---|---|---|---|
-o, --output <PATH> | path | stdout | Write the JSON report to a file instead of stdout. |
--vulns | flag | false | Enrich dependencies with vulnerability data from OSV (via the vuln-proxy). Slower on first run. |
--vuln-cache-ttl <HOURS> | u64 | 24 | Cache TTL for vulnerability data, in hours. |
--policy | flag | false | Evaluate configured policy rules and set the exit code accordingly. Requires POLICY. A vuln-dependent rule without --vulns fails closed (exit 2). |
--enrich-for-policy | flag | false | When --policy needs vulnerability evidence, auto-enable enrichment for this run instead of failing closed. Still fails closed if that enrichment is degraded. |
--alerts | flag | false | Report new vulnerabilities/violations since the last scan. Exits 1 when new alerts are found, 0 when the diff ran and found none (including the first scan, where there is no baseline), and 3 when the alert check returns an error or its config can’t be read. An unreadable or malformed previous scan cache (~/.packageprobe/scan-cache-prev.json — corrupt, truncated, empty, wrong-shape, permission-denied, or a dangling symlink) also exits 3, not 0: it is a baseline that exists but could not be read, which is not the same as a genuine first run and is never an all-clear (PKG-819). |
--scan-concurrency <N> | usize | config / min(cpus, 8) | Max concurrent project-discovery tasks. 1 forces sequential. |
--backend <BACKEND> | string | native | Discovery backend. Only native is accepted — any other value is rejected and the command exits 2. (The legacy config field discovery_backend: maps arbitrary values to native with a warning; the CLI flag does not.) |
The JSON output is a ScanEnvelope: schema_version, scanned_at, a results
object keyed by stable project id, and coverage/completeness fields
(scan_incomplete, enrichment_coverage, disabled_ecosystems, …). See the
data model for the full shape.
packageprobe scan # fast, no networkpackageprobe scan --vulns # + OSV enrichmentpackageprobe scan --vulns --policy # + policy gate (exit 1 on violation)packageprobe scan --vulns -o report.jsonscan-image
Section titled “scan-image”Scan a container image from a registry or a local tarball — no Docker daemon required (in-process OCI pipeline).
packageprobe scan-image <IMAGE> [OPTIONS]<IMAGE>— a registry ref (nginx:1.25,ghcr.io/owner/img:tag) or a local tarball (oci-archive:./image.tar,docker-archive:./image.tar).
| Flag | Type | Default | Description |
|---|---|---|---|
--vulns | flag | false | Enrich discovered artifacts with vulnerability data. |
--vuln-cache-ttl <HOURS> | u64 | 24 | Cache TTL for vulnerability data, in hours. |
--policy | flag | false | Evaluate policy against the image and set the exit code. Requires POLICY; severity rules need --vulns. |
--enrich-for-policy | flag | false | Auto-enable enrichment when --policy needs it, instead of failing closed. |
--base-image-suggestions | flag | false | Compare the base image against lighter/patched variants and recommend one with fewer CVEs. Requires --vulns; each candidate is its own scan (2–3× the time). |
--platform <OS/ARCH[/VARIANT]> | string | linux/amd64 | Platform for a multi-arch image (e.g. linux/arm64, linux/arm/v7). Fails visibly if the image doesn’t ship it. Pass all to scan every platform the (registry) image ships, one result each. |
-o, --output <PATH> | path | stdout | Write JSON output to a file. |
packageprobe scan-image nginx:1.25 --vulnspackageprobe scan-image oci-archive:./img.tarpackageprobe scan-image myimg:tag --platform linux/arm64 --vulns --policyGenerate an SBOM for a project from the last cached scan. Requires SBOM.
packageprobe sbom <PROJECT> [OPTIONS]<PROJECT>— the project name (as shown in scan output) or its stable id.
| Flag | Type | Default | Description |
|---|---|---|---|
-f, --format <FORMAT> | string | cyclonedx | cyclonedx (CycloneDX 1.6) or spdx (SPDX 2.3). |
-o, --output <PATH> | path | stdout | Write to a file. |
--workspace-shape <SHAPE> | enum | auto | auto (collapse when the project is part of a multi-package workspace), collapsed (always one SBOM for all workspace members), or per-member (just this project). |
packageprobe sbom my-app --format cyclonedx -o my-app.cdx.jsonGenerate a SARIF 2.1.0 log of vulnerability findings from the last cached scan
(for GitHub Code Scanning). Covers all projects. Requires SBOM (Compliance pack).
packageprobe sarif [-o results.sarif]| Flag | Type | Default | Description |
|---|---|---|---|
-o, --output <PATH> | path | stdout | Write to a file. |
Generate a VEX document for a scanned project. Requires VEX.
packageprobe vex <PROJECT> [OPTIONS]| Flag | Type | Default | Description |
|---|---|---|---|
-f, --format <FORMAT> | string | cyclonedx-vex | cyclonedx-vex, openvex, or csaf. |
-s, --scope <SCOPE> | enum | posture | posture (a statement per finding) or exceptions (only triaged, non-affected findings). |
-o, --output <PATH> | path | stdout | Write to a file. |
sbom-diff
Section titled “sbom-diff”Diff two CycloneDX SBOMs and report added / removed / changed components.
Requires SBOM.
packageprobe sbom-diff <PREVIOUS> <CURRENT> [OPTIONS]| Flag | Type | Default | Description |
|---|---|---|---|
-f, --format <FORMAT> | string | pretty | pretty, markdown, or json. |
-o, --output <PATH> | path | stdout | Write to a file. |
Fix vulnerable dependencies by upgrading to their fixed versions. Requires
CVE_FIX (Developer pack).
packageprobe fix <PROJECT> [OPTIONS]<PROJECT>— a path to the project directory, or a project name / stableprj_…id from the last cached scan (the same argumentsbomandvexaccept).
| Flag | Type | Default | Description |
|---|---|---|---|
--cve <ID> | string | — | Scope preview/apply to a specific CVE/GHSA (e.g. CVE-2021-23337). |
--apply | flag | false | Apply the fixes. Default is a dry-run preview. |
--all | flag | false | Fix all fixable vulnerabilities in the project. |
How <PROJECT> is resolved. fix --apply modifies whatever it resolves to,
so the arbitration is deterministic and the resolved directory is echoed as a
Target: line on stderr before anything runs:
- An existing directory always wins. The path form is unchanged, with or without a scan cache on disk.
- …unless the same string also names a different scanned project. That is
genuinely ambiguous, so it is refused (exit
2) rather than guessed. The error names both readings and the spelling that resolves each —./apifor the directory, the project’sprj_…id for the project. - Otherwise the argument is looked up as a project: stable id, then name. A name
shared by two scanned projects is refused, not picked — pass the
prj_…id. - A project whose recorded path no longer exists is a stale-cache error
(exit
2), never silently “no such project” — re-runpackageprobe scan. So is a project whose path was recorded relative (apaths:entry of.records./repoA, verbatim): a relative path names a different directory from every different working directory, and the scan’s own working directory is not recorded, so resolving it here could target an unrelated project that merely has the same layout. Pass the project’s path, or re-scan with an absolutepaths:entry. - An argument matching neither reading exits
2. It never falls back to a default target, the working directory, or “all projects” — so an unset variable (packageprobe fix "$PROJECT") fails loudly instead of acting. - A polyglot root — one directory holding projects from several ecosystems,
which a scan records as one project per ecosystem — is refused (exit
2), whichever form you use.fixpicks the project to modify by path, so with several projects at one path it cannot tell them apart, and picking one would be arbitrary. Upgrade the affected package with that ecosystem’s own package manager until per-ecosystem targeting lands.
If the scan cache cannot be read, the name reading is announced as unavailable on stderr; it is never silently reported as “no such project”.
What --apply upgrades: with --cve, only the dependency for that advisory;
otherwise (--all, or neither --all nor --cve) every previewed fix is
applied. In other words, a bare fix <project> --apply upgrades all fixable
dependencies — run it once without --apply first to preview the full set.
After each apply the project is re-enriched and the targeted advisory is
re-checked: a fix is reported “Fixed — verified” only when the advisory is
gone. If it persists (the package manager resolved a different version, or there
is no real fix) or the re-check couldn’t run, it reports “applied but NOT
confirmed” and the command exits non-zero — a still-vulnerable state never reads
as success. The two cases are distinguishable: a completed re-check that still
finds the advisory exits 1 (a finding), a re-check that could not run exits 2
(unproven), and a fix that failed to apply at all exits 3.
packageprobe fix /path/to/project # preview, by pathpackageprobe fix my-app # preview, by project namepackageprobe fix prj_9f3a1c2b # preview, by stable idpackageprobe fix /path/to/project --cve CVE-2021-23337 --applypackageprobe fix /path/to/project --all --applyconfig
Section titled “config”Print the resolved config path and its contents (as JSON).
packageprobe configaudit-verify
Section titled “audit-verify”Verify the integrity of the audit-log hash chain.
packageprobe audit-verify [OPTIONS]| Flag | Type | Default | Description |
|---|---|---|---|
--path <PATH> | path | ~/.packageprobe/logs/audit.jsonl | Audit log to verify. |
--include-rotated | flag | false | Walk all rotated log files (audit.jsonl.5 → .1 → active), not just the active file. Required when the chain spans rotation boundaries. |
--json | flag | false | Output the result as JSON. |
A missing or emptied log verifies as no evidence (exit 2), not “valid” — an
absent log is never proof of a clean history.
Start a 7-day free trial (all features unlocked).
packageprobe trial <EMAIL>Activate this machine with a license key.
packageprobe login <KEY> [--email <EMAIL>]| Flag | Type | Description |
|---|---|---|
--email <EMAIL> | string | Bind this activation to a specific seat on a multi-seat enterprise license. Single-seat licenses ignore it (activation falls back to max_machines enforcement). |
logout
Section titled “logout”Deactivate this machine (frees the seat).
packageprobe logoutlicense
Section titled “license”Inspect or manage the active license.
packageprobe license status # print current license status as JSONpackageprobe license refresh # re-fetch the license from the server, re-validate, # pick up pack changes, reset the 7-day rolling TTLCI/CD usage
Section titled “CI/CD usage”For ephemeral CI runners where per-machine seat activation doesn’t make sense,
validate statelessly with the PACKAGEPROBE_LICENSE_KEY environment variable —
no machine activation, no default license file. License resolution precedence is:
an explicit --license-file <PATH> wins if provided; otherwise
PACKAGEPROBE_LICENSE_KEY is checked before the default
~/.packageprobe/license.json. So in CI, set the env var and don’t pass
--license-file.
export PACKAGEPROBE_LICENSE_KEY=pp_live_...packageprobe scan --vulns --policyExit codes make this pipeline-friendly: gate a build on 0 (pass), branch on 1
(violation), and treat 2/3 as “don’t trust this result” — a degraded
enrichment or an incomplete scan exits non-zero so the step can’t stay green on an
incomplete result. The packageprobe/scan-action
GitHub Action wraps all of this (install, checksum-verified download, scan, SBOM,
policy, and optional PR-comment diffing).