Class: StillActive::Suppressions

Inherits:
Object
  • Object
show all
Defined in:
lib/still_active/suppressions.rb

Overview

Granular, auditable suppression of individual findings, loaded from the ignore: block of a committed .still_active.yml. Each entry silences one signal (activity / vulnerability / libyear / poison) or one advisory for one gem, optionally until an expiry date, replacing the all-or-nothing whole-gem --ignore. A bare gem name (or a gem-only mapping) keeps the old whole-gem behaviour so --ignore can union into the same list.

Two guardrails keep suppression from hiding live risk: a lapsed entry stops applying so the finding re-surfaces, and a vulnerability suppression must name an explicit advisory id, so a newly disclosed CVE on the same gem is never pre-silenced.

Defined Under Namespace

Classes: Entry

Constant Summary collapse

GATEABLE_SIGNALS =
[:activity, :vulnerability, :libyear, :poison, :language_ceiling, :deprecated].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(entries, warnings, today) ⇒ Suppressions

Returns a new instance of Suppressions.



109
110
111
112
113
# File 'lib/still_active/suppressions.rb', line 109

def initialize(entries, warnings, today)
  @entries = entries
  @warnings = warnings
  @today = today
end

Instance Attribute Details

#warningsObject (readonly)

Returns the value of attribute warnings.



107
108
109
# File 'lib/still_active/suppressions.rb', line 107

def warnings
  @warnings
end

Class Method Details

.from(raw, today: Date.today) ⇒ Object



42
43
44
45
46
# File 'lib/still_active/suppressions.rb', line 42

def from(raw, today: Date.today)
  warnings = []
  entries = Array(raw).filter_map { |item| parse_entry(item, warnings) }
  new(entries, warnings, today)
end

Instance Method Details

#match(gem:, signal:, advisory: nil, aliases: []) ⇒ Object

The first live entry covering this finding, or nil. Used by SARIF to carry the suppression's reason as the native suppressions justification.



136
137
138
139
140
141
142
143
# File 'lib/still_active/suppressions.rb', line 136

def match(gem:, signal:, advisory: nil, aliases: [])
  @entries.find do |entry|
    next false if entry.expired?(@today)
    next false unless entry.gem.nil? || entry.gem == gem

    entry.covers?(signal:, advisory:, aliases:)
  end
end

#stale_gem_warnings(present_gems) ⇒ Object

Warnings for live entries that name a gem absent from the audited set: they can never match, so they are dead config (a typo, or a gem removed since the suppression was written). This is the presence axis of suppression rot; expired? already covers the time axis, so an expired entry isn't re-reported here, and a gem-agnostic advisory entry (gem nil) is skipped since it applies across the whole graph.



125
126
127
128
129
130
131
132
# File 'lib/still_active/suppressions.rb', line 125

def stale_gem_warnings(present_gems)
  @entries.filter_map do |entry|
    next if entry.expired?(@today)
    next unless entry.gem && !present_gems.include?(entry.gem)

    "suppression for #{entry.gem} never applies: it is not in the audited dependencies (typo, or removed since it was suppressed?)"
  end
end

#suppressed?(gem:, signal:, advisory: nil, aliases: []) ⇒ Boolean

Returns:

  • (Boolean)


115
116
117
# File 'lib/still_active/suppressions.rb', line 115

def suppressed?(gem:, signal:, advisory: nil, aliases: [])
  !match(gem:, signal:, advisory:, aliases:).nil?
end