# vet v2.0.0 vet v2 is a new release line. It keeps the job of vet, to find supply chain risk before it reaches production, and changes the command line, the report, the policy and the config. This page lists each change that a v1 user must make. v1 gets security fixes only. ## What is new - **Controls.** Each finding comes from a control with a stable id, such as `malware`, `vulnerability`, `dependency-cooldown`, `untrusted-registry`, `typosquat`, `dangerous-trigger` or `mcp-server-added`. The families are malware, vulnerability, cooldown, lockfile, hygiene, license, reputation, workflow, agent-config and ai-bom. `vet policy control list` prints them. - **One report.** Every format comes from the same records: a header, the manifests, the packages, the findings, the diagnostics and a trailer with the summary and the gate. - **Pull request mode.** `vet scan --base-ref REF` reports only what the change adds. - **Saved scans.** vet saves each scan. `vet report show` renders it again in any format with no new scan. A scan that stops continues on the next run. - **Policy v2.** Rules with `fail` or `warn`, and suppressions with a reason and an expiry. A rule that reads `package` and not `finding` runs on each package, so it can block a package that no control reports. A suppression `purl` takes a name glob, with a `control`. - **What blocked.** Each finding says "Blocked by" or "Warned by", with the policy rule or the `--fail-on` value, in the PR comment, the step summary, the terminal table and the JSON `gate` object. A rule can have `help` and `link` text for the report. - **Dependency cooldown.** `dependency-cooldown` reports a version that the registry published in the last 2 days, the same window as pmg, as a `high` finding. A policy rule decides the gate. The `skip` option takes the packages of your own organization. The finding names the window and the setting that set it. - **Agent mode.** For `CLAUDECODE` or `AI_AGENT`, or with `--mode agent`, vet writes JSON, never prompts and prints one `ERR:` line for each error. - **Code usage, AI and crypto inventory.** With `plugins.codeusage.enabled: true`, vet reads the source files and reports which packages the code imports, and the AI and crypto capabilities that it calls. The `cyclonedx` report holds them as an xBOM and a CBOM (CycloneDX 1.7). - **Installed packages.** `vet scan --packages installed` reads the packages on disk: `node_modules`, Python `site-packages`, Go binaries, installed gems and Rust binaries built with cargo-auditable. An image scan reads installed and declared packages by default. With `--packages all`, the `installed-not-locked` control reports a package in `node_modules` that the lockfile does not list. - **Impostor commits.** The `actionrefs` enricher checks each action pinned to a commit SHA against the branches and the tags of its repository. `impostor-commit` reports a commit that none of them contains, such as a commit from a fork. `pin-comment-mismatch` reports a pin comment that names a release tag of another commit. Set `GITHUB_TOKEN` for more than 60 GitHub API calls an hour. - **Endpoint audit.** `vet endpoint audit` checks the AI tools, MCP servers, agent skills, editor extensions and global npm packages of the machine. - **CGO builds.** Every install channel ships a build with CGO, so code usage works. See [install.md](../install.md). - **GitHub Action.** `safedep/vet` is now a GitHub Action. It comments on a pull request that adds a package, a workflow or a finding, fails the check on an attack with the attacks gate (`--fail-on attacks`), and reads the policy at the base commit. `vet ci init` adds it to a repository, and `vet ci update` moves its pins. See [github-action.md](../github-action.md). The `vet-action` repository stays on v1. ## Exit codes | Code | v1 | v2 | | --- | --- | --- | | 0 | No violation | The gate passed, or no gate was set | | 1 | A violation with `--filter-fail`, or an error | The gate failed | | 2 | Not used | A usage or config error | | 3 | Not used | A runtime error | | 130 | Not used | A signal stopped vet. The scan continues on the next run | ## Commands | v1 | v2 | | --- | --- | | `vet scan -D DIR` | `vet scan DIR` | | `vet scan --lockfiles FILE`, `-L`, `--manifests`, `-M`, `--lockfile-as`, `--type` | `vet scan DIR`. vet finds each lockfile and manifest. `--exclude GLOB` skips paths | | `vet scan --purl PURL` | `vet scan PURL` | | `vet scan --image IMAGE` | `vet scan oci://IMAGE` or `vet scan image.tar` | | `vet scan --github URL` | `vet scan URL` | | `vet scan --github-org ORG` | Removed. SafeDep Cloud scans an organization | | `vet scan --vsx`, `--agent-skill` | `vet endpoint audit` | | `vet scan --homebrew` | Removed | | `vet scan --code`, `vet code scan` | `plugins.codeusage.enabled: true` in the config file. vet analyzes the code during the scan, with no separate database | | `vet code ...` (other commands) | Removed. The JSON and `cyclonedx` reports hold the code usage and the capabilities | | `vet scan --github-actions-pin` | `vet fix github-actions run` | | `vet scan --malware` | Always on. The malware control runs on each scan | | `vet scan --malware-query` | Always on. v2 reads the known results, and does not start an analysis | | `vet inspect malware` | `vet scan pkg:/@` | | `vet query --from DIR` | `vet report show` | | `vet cloud login`, `vet cloud key ...`, `vet cloud whoami` | `vet auth login`, `vet auth status`, `vet auth logout` | | `vet cloud query`, `vet cloud register`, `vet cloud quickstart` | Removed. Use SafeDep Cloud | | `vet server mcp`, `vet agent ...` | Removed | | `vet ai discover` | `vet endpoint audit` | | `vet doc generate` | Removed. The pages are under `docs/cmd/` | | `vet endpoint scan` | `vet endpoint audit` | | `vet version` | `vet version` | ## Scan flags | v1 | v2 | | --- | --- | | `--filter EXPR --filter-fail` | A rule in a policy file, or `--fail-on SEVERITY` | | `--filter-suite FILE` | `--policy FILE` | | `--policy FILE`, `--policy-suite` (policy v1) | `--policy FILE` (policy v2). The format changed | | `--exceptions FILE`, `--exceptions-extra` | A suppression in the policy file | | `--fail-fast` | Removed. The gate runs after the scan | | `--trusted-registry URL` | `plugins.lockfile.options.trusted_registries` in the config file | | `--transitive`, `--transitive-depth` | Removed. vet reads the full graph from the lockfile | | `--enrich=false` | Removed. A scan needs the data to run the controls | | `--concurrency N` | `scan.concurrency` in the config, or `VET_SCAN_CONCURRENCY` | | `--malware-analysis-timeout`, `--malware-analysis-min-confidence`, `--malware-trust-tool-result` | Removed | | `--skip-github-dependency-graph-api` | Removed | | `--image-no-remote` | Removed. vet tries the local Docker daemon, then the registry | | `--json-dump-dir DIR` | `--report json=FILE` | | `--silent`, `--no-banner` | `-q`. vet prints its banner only in rich mode, and `-q` hides it | | `--verbose`, `--debug`, `--log FILE` | `-v`. The `--mode` flag sets the terminal view | | `--experimental` | Removed | ## Report flags `--report FORMAT=PATH` writes one format to a file. One scan writes many formats. `-o FORMAT` writes one format to stdout. | v1 | v2 | | --- | --- | | `--report-json FILE` | `--report json=FILE`. The schema changed. See below | | `--report-sarif FILE`, `--report-sarif-vulns`, `--report-sarif-malware` | `--report sarif=FILE`. It has every finding | | `--report-markdown FILE`, `--report-markdown-summary FILE` | `--report markdown=FILE` | | `--report-cdx FILE`, `--report-cdx-app-name` | `--report cyclonedx=FILE` | | `--report-summary` and its options | The terminal view of `vet scan` | | `--report-csv`, `--report-html`, `--report-graph` | Removed. Convert the `json` or `jsonl` report | | `--report-gitlab FILE` | `--report gitlab=FILE` | | `--report-bitbucket-meta`, `--report-bitbucket-annotations` | `--report bitbucket=FILE`. One file holds the report and the annotations | | `--report-defect-dojo` and its options | Removed | | `--report-sync` and its options | Removed. The SafeDep Cloud report plugin comes in v2.1 | ## Environment variables | v1 | v2 | | --- | --- | | `SAFEDEP_API_KEY`, `SAFEDEP_TENANT_ID` | The same. A key with no tenant is now a usage error | | `VET_API_KEY`, `VET_CONTROL_TOWER_TENANT_ID` | `SAFEDEP_API_KEY`, `SAFEDEP_TENANT_ID` | | `VET_INSIGHTS_API_KEY` | `SAFEDEP_API_KEY` | | `VET_COMMUNITY_MODE` | Removed. With no credentials, vet uses the community endpoints | | `VET_INSIGHTS_API_URL`, `VET_DATA_PLANE_API_URL`, `VET_SYNC_API_URL` | `VET_CLOUD_ENDPOINTS_API` | | `VET_COMMUNITY_SERVICES_API_URL` | `VET_CLOUD_ENDPOINTS_COMMUNITY` | | `VET_CONTROL_PLANE_API_URL` | Removed | | `VET_LOG_LEVEL` | `-v` | | `VET_DISABLE_BANNER` | Removed. `-q` or `--mode plain` hides the banner | | `VET_FORCE_DARK_MODE`, `VET_FORCE_LIGHT_MODE` | `output.color` in the config, or `NO_COLOR` | | `VET_GITHUB_CLIENT_ID` | Removed | | `VET_CLOUD_GRPC_TIMEOUT`, `VET_CLOUD_GRPC_MAX_ATTEMPTS` | Removed | Each key of the config file has a `VET_*` variable: the key in upper case with `_` for `.`, for example `VET_POLICY_FAIL_ON` for `policy.fail_on`. `vet config show --origin` shows the source of each value. ## The JSON report The v1 JSON report was one document with `meta`, `manifests` and `packages`, and each package held its violations, advices and insight data. The v2 report is a stream of records between a header and a trailer. `vet report schema get` prints its JSON Schema, and `schema_version` names its version. | v1 | v2 | | --- | --- | | `meta` | `header`: the tool, the scan id, the target, the mode and the base ref | | `manifests[]` | `records[]` with `kind: manifest` | | `packages[].package`, `packages[].manifests` | `records[]` with `kind: package`, with `manifest_ids`, `uses`, `insight`, `malware` and `action`. `uses` holds the fields of each manifest when the manifests disagree | | `packages[].vulnerabilities[]` | `records[]` with `kind: finding` and `control_id: vulnerability` | | `packages[].malware_info[]` | `records[]` with `kind: finding` and `family: malware` | | `packages[].violations[]` | `records[]` with `kind: finding`. A finding that a suppression hides has `suppression` | | `packages[].advices[]` | `finding.remediation` | | `packages[].threats[]` | `records[]` with `kind: finding` from the lockfile control | | Not present | `records[]` with `kind: diagnostic`: a backend that did not answer, a file that vet could not read | | Not present | `records[]` with `kind: capability`: an AI or crypto capability of the code, with its calls | | Not present | `trailer.summary` and `trailer.gate` | ## Policy A v1 filter was a CEL expression over a package. A v2 rule is a CEL expression over a finding, its package and its manifest. A v2 rule that reads only `package` runs on each package, as a v1 filter did. It makes its own finding with the control id `policy`. ```yaml # v1 filter suite filters: - name: critical-vulns value: vulns.critical.exists(p, true) # v2 policy version: 2 rules: - id: critical-vulns when: finding.control_id == "vulnerability" && finding.severity == "critical" action: fail ``` A v1 license filter becomes the license control. The control reads SPDX expressions, so `MIT OR GPL-3.0-only` passes a deny list with GPL-3.0-only. v1 did not support `AND`. A v2 entry matches one license, so name each license that the v1 filter matched, such as `GPL-3.0-only` and `GPL-3.0-or-later`. With a list set, vet also reports a package with no license data as `license-unknown`. See [License lists](../controls.md#license-lists). ```yaml # v1 vet scan --filter 'licenses.contains_license("GPL-3.0")' --filter-fail # v2: config.yml, then vet scan --fail-on high plugins: license: options: deny: [GPL-3.0-only, GPL-3.0-or-later] ``` A v1 exception becomes a v2 suppression: ```yaml suppressions: - purl: pkg:npm/left-pad@1.3.0 reason: Reviewed. Not reachable. expires: 2026-12-31 ``` The `purl` of a PyPI package keeps the version as the manifest writes it, such as `pkg:pypi/anthropic-sdk@0.1.0`. Compare a package in a rule with `package.is()` and `package.version_cmp()`, not with `package.purl`. `vet policy init` writes a starter policy. `vet policy schema get` prints the fields that a rule reads. `vet policy validate` checks a file. ## Install v2.0.0 ships on Homebrew (`brew install safedep/tap/vet`), on the releases page and with `go install github.com/safedep/vet/v2/cmd/vet@latest`. The npm package and the container image stay on v1 for this release.