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].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.



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

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

Instance Attribute Details

#warningsObject (readonly)

Returns the value of attribute warnings.



105
106
107
# File 'lib/still_active/suppressions.rb', line 105

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.



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

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.



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

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)


113
114
115
# File 'lib/still_active/suppressions.rb', line 113

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