Skip to content

v3.2.0

Latest

Choose a tag to compare

@SeanLF SeanLF released this 25 Sep 02:57
4d43e9d

Upgrading from 3.1: a few runs that passed can now fail, each where 3.1 read an unanswered question as "clean". --fail-if-vulnerable fails on a dependency whose advisories no source could answer for (vulnerabilities_checked: false), and --fail-if-critical / --fail-if-warning on one whose repository check failed (repository_check: "failed"); each names the dependency, a rerun usually clears it, and a suppression accepts it. Statuses that used to read ok for a dependency still_active couldn't check (no repository link, Google's go.googlesource.com, a private or deleted repository) now read unknown, without failing any gate. JSON gains fields and never loses one; the --sbom JSON now names its schema.

Added

  • The SBOM audit reads GitLab and Codeberg repositories. It only asked about github.com repositories before, so a GitLab or Codeberg project in an SBOM never got an archived signal. Both audits now resolve repositories through one shared lookup, and the JSON's new repository_source says which service answered (github, ecosyste.ms, gitlab, codeberg).
  • A JSON Schema for the --sbom output, docs/still_active.sbom.schema.json, which the output now names in $schema. Fields shared with the Gemfile audit reference its schema rather than copying it, and the suite validates real output against it. Writing it caught a gap: the Go toolchain's advisories carry source: "osv", which the shared vulnerability definition didn't allow; it does now.
  • An on-disk cache of public sources' answers. A rerun, or the baseline and current audits of one CI job, no longer ask the same questions twice: on a 300-component SBOM a second run made a handful of requests instead of hundreds and took half the time, with identical output. Each kind of answer is kept only as long as it holds (which advisories affect a version an hour, latest versions and advisory scores six hours, a published version's metadata a week; see docs/configuration.md), so a new advisory can surface up to an hour late, and --no-cache skips it. Nothing credentialed, no failure and no 404 is cached, and neither are the answers that must be current: OSV's check of which advisories really affect a version (it can drop a finding), the source-health canaries, and a malformed version record.
  • A source-health check before every audit, on both paths. deps.dev is an alpha API and the advisory source for nearly everything, so still_active now checks once that it answers in shape (the advisory canary, which the Gemfile audit never ran before) and, per audited ecosystem, that its index is current: one of a small set of packages that publish often (next, boto3, aws-partitions, ...) must show a version published in the last 30 days. The sets were picked by replaying two years of deps.dev history, where the longest quiet stretch for any set is 10 days. Where either fails, deps.dev's advisories for that ecosystem count as unchecked (so --fail-if-vulnerable fails closed on them), a warning says why, and both JSON outputs carry a source_health block. ruby-advisory-db and the Go toolchain's OSV lookup still answer on their own. It costs one request plus about one per ecosystem. It can't catch a single stale package in a live index.

Changed

  • Internal: every dependency's assessment is now a typed StillActive::Assessment. Both audits build it once all their passes are done. An unknown field is reported (and dropped) rather than read as a silent nil, and the answers that decide "clean or unknown" (vulnerabilities_checked, repository_check) can no longer be missing: a dependency whose assessment stopped part-way reads unchecked and failed, never fine. Output is unchanged (verified byte-for-byte, JSON, terminal, markdown and SARIF).
  • A third fewer requests to deps.dev. Every dependency's version record now comes from one batch request before the audit starts, instead of one request each: on a 300-component SBOM, 705 deps.dev requests became 475. Anything the batch doesn't answer cleanly is looked up one at a time as before, so it can't change a result. It doesn't make the run faster; it's less load on a free API.
  • About 30% faster on an SBOM, from reusing connections. Every request used to open its own TLS connection; HttpHelper now keeps one open per host and reuses it. On a 300-component, four-ecosystem SBOM, connections to deps.dev went from 705 to 12 and the run from about 24s to 17s, with identical output. A rate limit that says when to come back (a 429 or 503 with Retry-After of 10 seconds or less) now gets one retry after that wait instead of failing the lookup.
  • A failed advisory lookup is no longer reported as "0 vulnerabilities". Before, a deps.dev timeout, 5xx or 429 on a dependency's version record read exactly like a clean one, so a network blip could pass --fail-if-vulnerable with a vulnerable dependency in the tree. Now the lookup is retried once, and if no source answers (including a version record with no advisoryKeys list, which is what deps.dev schema drift looks like) the dependency carries vulnerabilities_checked: false, as does one whose assessment raised part-way: its status reads unknown rather than ok or legacy (the two verdicts that claim clean), the terminal and markdown show ?, both JSON summaries count vulnerabilities_unchecked, the enriched CycloneDX marks it, --fail-if-vulnerable fails closed on it with a warning naming it, and the --baseline diff reports a bump to an unchecked version as a regression instead of "closed vulns". A 404 is still an answer (a private package deps.dev has never seen stays checked), and on the native path a loaded ruby-advisory-db answers on its own. If this turns a run red, rerun it, or --ignore the dependency.
  • One fewer runtime dependency: gems is gone. Its two rubygems.org calls now go through the same HTTP layer as every other source, so they get the same timeouts, body cap and redirect rules. That also fixes a crash path: a 5xx or 429 from rubygems.org's gem-info endpoint escaped as an exception and cost the gem every signal, not just its repository link. Artifactory's copy of the same API shares the client.

Fixed

  • A deprecated Go module reads as deprecated whichever version is pinned, and its real latest version is reported. Go deprecates a module, not a version: a // Deprecated: comment in the latest go.mod covers every version, which is what go list -m -u shows, but deps.dev flags only the versions whose go.mod carries it. still_active read the pinned version's flag, so an older pin looked fine; and it skipped deprecated versions when picking the latest (right for npm, where one bad release gets deprecated), so for a deprecated Go module it reported a years-old version as the latest and an old pin as up to date. github.com/golang/protobuf@v1.3.5 now reads deprecated, latest v1.5.4, with Go's own message. A retracted Go version (deps.dev flags it the same way, as "Version retracted: ...") still marks only that version and is never reported as the latest.
  • Without a GitHub token, a repository ecosyste.ms hasn't crawled no longer reads as "not archived". ecosyste.ms answers first in that mode, and about one repository in twenty isn't in it; that "don't know" used to read as "not archived". GitHub is now asked about those, without a token (60 requests an hour), and one neither service knows reads unknown (repository_check: "unknowable"), never "not archived". Private-source repositories are no longer sent to ecosyste.ms, with or without a token.
  • A repository no service could check no longer reads as "not archived". When GitHub's API refused a repository lookup (a burst of concurrent requests trips its secondary rate limit, even with a token), archived came back blank, and a blank archived read as "not archived": in a real run, two archived repositories came out ok and stale. GitHub and ecosyste.ms, which mirrors the same fields, now back each other up, so a rate limit alone no longer loses the signal (verified on a 300-component SBOM with every GitHub call refused: the same repositories read archived). Each dependency now carries repository_check: answered; failed (a service couldn't answer; a rerun may help, so --fail-if-critical / --fail-if-warning fail closed on it, with a warning naming it, and an activity suppression accepts it); or unknowable (no service can say: no repository link, a host with no archived state such as Google's go.googlesource.com, or every service asked didn't know it, which is how a private or deleted repository looks; a rerun won't change it, so it doesn't fail the gates). Anything not answered turns an ok or legacy status into unknown and shows ? in the terminal and markdown; the JSON summaries tally repository_checks, CycloneDX marks it, and the --baseline diff reports a dependency that was answered and no longer is. A dependency whose assessment failed part-way counts as failed.
  • The Go toolchain (pkg:golang/stdlib) is assessed from endoflife.date and OSV, not deps.dev. deps.dev keys it go1.27.1 where the purl says 1.27.1, its index stops at go1.25.5, and its advisory list is the same for every version, so stdlib read as behind a stale "latest" and every stdlib pin reported zero advisories. It now reads its latest release from endoflife.date, its advisories from OSV for that exact version, and an end-of-life release line as deprecated, naming the line to move to. If OSV can't answer, its advisories are marked unchecked (see below) and a warning says so.
  • No more HTTP 400 warnings for repositories deps.dev doesn't index. deps.dev keys projects under github.com, gitlab.com and bitbucket.org only, so every golang.org/x/* module (hosted on go.googlesource.com), and anything on Codeberg or a self-hosted GitLab, printed a warning for a lookup that could never succeed. still_active no longer makes that request. A GitHub.com or www.github.com repository link is normalized, so those now get their scorecard.