vulns/match: return :never_affected when Homebrew skipped the interval

first_fixed_version now distinguishes three outcomes for a currently-
:fixed hit: the pkg_version at the :fixed -> :affected boundary (or at
the last loadable revision, best-effort); :never_affected when the walk
reaches :not_applicable or the start of the formula's history without
seeing :affected (Homebrew jumped from below introduced straight past
fixed and never shipped an affected build); and nil when the current
aggregate is not :fixed. dev-cmd/advisory-match drops :never_affected
candidates instead of emitting {introduced: 0, fixed: <first-shipped>}.
This commit is contained in:
Andrew Nesbitt committed 2026-07-28 13:31:56 +01:00
1 parent 1ac19d222d
commit 2c1daef60a
3 files changed
+55 -11

No files matched your search

+4 -1
View File
@@ -61,7 +61,10 @@ module Homebrew
next if status&.state == :not_applicable
first_fixed = matcher.first_fixed_version(formula, hit) unless args.no_history?
emitter << matcher.to_brew_record(formula, hit, first_fixed:)
next if first_fixed == :never_affected
boundary = first_fixed if first_fixed.is_a?(String)
emitter << matcher.to_brew_record(formula, hit, first_fixed: boundary)
end
end
rescue Homebrew::Vulns::OSV::Error => e
+25
View File
@@ -689,6 +689,31 @@ RSpec.describe Homebrew::Vulns::Match do
expect(matcher.first_fixed_version(requests, hit)).to eq "2.1"
end
it "returns :never_affected when Homebrew jumped from below introduced straight past fixed" do
# Advisory {introduced: 2.0, fixed: 3.0}; Homebrew went 1.0 -> 4.0 and
# never shipped a 2.x, so no BREW record should be emitted.
stub_history(["4.0", "1.0"])
current = formula("requests") do
T.bind(self, T.class_of(Formula))
url "https://files.pythonhosted.org/packages/aa/bb/cc/requests-4.0.tar.gz"
end
hit = make_hit(
vuln("id" => "CVE-1", "affected" => [
{ "package" => { "ecosystem" => "PyPI", "name" => "requests" },
"ranges" => [{ "type" => "ECOSYSTEM",
"events" => [{ "introduced" => "2.0" }, { "fixed" => "3.0" }] }] },
]),
ev(:registry, ecosystem: "PyPI", name: "requests", subject_version: "4.0"),
)
expect(matcher.first_fixed_version(current, hit)).to eq :never_affected
end
it "returns :never_affected when the formula was already past fixed at its first revision" do
stub_history(["2.31.0"])
expect(matcher.first_fixed_version(requests, hit_fixed_at("2.28.1"))).to eq :never_affected
end
it "keeps versionless (distro) evidence uncheckable at historical revisions too" do
stub_history(["2.31.0", "2.30.0", "2.28.1", "2.28.0"])
# A distro record whose Debian range would spuriously match our formula
+26 -10
View File
@@ -565,10 +565,21 @@ module Homebrew
# per-evidence range check with each revision's subject versions keeps
# `last_affected` and exclusive-bound semantics intact and stops as soon
# as any subject (primary or a resource) drops back into `:affected`, so
# a primary fixed at 2.0 with a resource fixed at 3.0 yields 3.0. Returns
# nil when the current aggregate is not `:fixed`. The rev-list and
# per-revision loads are cached per formula.
sig { params(formula: Formula, hit: Hit).returns(T.nilable(String)) }
# a primary fixed at 2.0 with a resource fixed at 3.0 yields 3.0.
#
# Returns:
# - `nil` when the current aggregate is not `:fixed`.
# - `:never_affected` when the walk reaches `:not_applicable` (or the
# start of the formula's history) without ever seeing `:affected`,
# i.e. Homebrew jumped from a version below `introduced` straight past
# `fixed` and never shipped an affected build. The caller drops the
# candidate rather than emitting `{introduced: "0", fixed: <first>}`.
# - a `pkg_version` String when the walk hits `:affected`, or when it
# stops at an unloadable revision (best-effort boundary; the reviewer
# can tighten).
#
# The rev-list and per-revision loads are cached per formula.
sig { params(formula: Formula, hit: Hit).returns(T.nilable(T.any(String, Symbol))) }
def first_fixed_version(formula, hit)
return unless range_status(hit)&.first&.fixed?
@@ -576,16 +587,21 @@ module Homebrew
revs = @formula_rev_lists[formula.name] ||=
[].tap { |a| fv.rev_list("HEAD") { |rev, entry| a << [rev, entry] } }
last_fixed = T.let(formula.pkg_version.to_s, T.nilable(String))
last_fixed = T.let(formula.pkg_version.to_s, String)
revs.each do |rev, entry|
old_fixed = fv.formula_at_revision(rev, entry) do |old|
old.pkg_version.to_s if aggregate_state_at(old, hit) == :fixed
state = fv.formula_at_revision(rev, entry) do |old|
[aggregate_state_at(old, hit), old.pkg_version.to_s]
end
return last_fixed if old_fixed.nil?
# `nil` means the revision failed to load; can't verify further.
return last_fixed if state.nil?
last_fixed = old_fixed
aggregate, pkg_version = state
return :never_affected if aggregate == :not_applicable
return last_fixed if aggregate != :fixed
last_fixed = pkg_version
end
last_fixed
:never_affected
end
sig { params(formula: Formula, hit: Hit).returns(T.nilable(Symbol)) }