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 / libyear) 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].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.



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

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

Instance Attribute Details

#warningsObject (readonly)

Returns the value of attribute warnings.



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

def warnings
  @warnings
end

Class Method Details

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



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

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.



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

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.



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

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)


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

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