still_active 1.4.2 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,9 +3,14 @@
3
3
  require_relative "deps_dev_client"
4
4
  require_relative "gitlab_client"
5
5
  require_relative "repository"
6
+ require_relative "../helpers/activity_helper"
7
+ require_relative "../helpers/alternatives_helper"
8
+ require_relative "../helpers/catalog_index"
6
9
  require_relative "../helpers/libyear_helper"
10
+ require_relative "../helpers/ruby_advisory_db"
7
11
  require_relative "../helpers/ruby_helper"
8
12
  require_relative "../helpers/version_helper"
13
+ require_relative "../helpers/vulnerability_helper"
9
14
  require "async"
10
15
  require "async/barrier"
11
16
  require "async/semaphore"
@@ -17,6 +22,10 @@ module StillActive
17
22
 
18
23
  def call(&on_progress)
19
24
  task = Async do
25
+ # Load the optional ruby-advisory-db once, before the fan-out, so the
26
+ # read-only Database is shared across fibers rather than reloaded per gem.
27
+ advisory_db = RubyAdvisoryDb.load
28
+ catalog = StillActive.config.alternatives ? CatalogIndex.load : nil
20
29
  barrier = Async::Barrier.new
21
30
  semaphore = Async::Semaphore.new(StillActive.config.parallelism, parent: barrier)
22
31
  result_object = {}
@@ -30,6 +39,8 @@ module StillActive
30
39
  gem_version: gem[:version],
31
40
  source_type: gem[:source_type] || :rubygems,
32
41
  source_uri: gem[:source_uri],
42
+ advisory_db: advisory_db,
43
+ catalog: catalog,
33
44
  )
34
45
  rescue Octokit::TooManyRequests
35
46
  $stderr.print("\r\e[K") if on_progress
@@ -54,24 +65,27 @@ module StillActive
54
65
 
55
66
  private
56
67
 
57
- def gem_info(gem_name:, result_object:, gem_version: nil, source_type: :rubygems, source_uri: nil)
68
+ def gem_info(gem_name:, result_object:, gem_version: nil, source_type: :rubygems, source_uri: nil, advisory_db: nil, catalog: nil)
58
69
  result_object[gem_name] = { source_type: source_type }
59
70
  result_object[gem_name][:version_used] = gem_version if gem_version
60
71
 
61
72
  case source_type
62
73
  when :path, :git
63
- gem_info_non_rubygems(gem_name: gem_name, gem_version: gem_version, result_object: result_object, source_uri: source_uri)
74
+ gem_info_non_rubygems(gem_name: gem_name, gem_version: gem_version, result_object: result_object, source_uri: source_uri, advisory_db: advisory_db)
64
75
  else
65
76
  gem_info_rubygems(
66
77
  gem_name: gem_name,
67
78
  gem_version: gem_version,
68
79
  result_object: result_object,
69
80
  source_uri: source_uri,
81
+ advisory_db: advisory_db,
70
82
  )
71
83
  end
84
+
85
+ attach_alternatives(gem_name: gem_name, result_object: result_object, catalog: catalog)
72
86
  end
73
87
 
74
- def gem_info_rubygems(gem_name:, gem_version:, result_object:, source_uri:)
88
+ def gem_info_rubygems(gem_name:, gem_version:, result_object:, source_uri:, advisory_db: nil)
75
89
  vs = versions(gem_name: gem_name, source_uri: source_uri)
76
90
  repo_info = repository_info(gem_name: gem_name, versions: vs)
77
91
  commit_date = last_commit_date(
@@ -89,6 +103,7 @@ module StillActive
89
103
  deps_dev = fetch_deps_dev_info(
90
104
  gem_name: gem_name,
91
105
  version: gem_version || VersionHelper.gem_version(version_hash: last_release),
106
+ advisory_db: advisory_db,
92
107
  )
93
108
  result_object[gem_name].merge!({
94
109
  latest_version: VersionHelper.gem_version(version_hash: last_release),
@@ -118,6 +133,7 @@ module StillActive
118
133
 
119
134
  version_used_release_date: VersionHelper.release_date(version_hash: version_used),
120
135
  version_yanked: !vs.empty? && version_used.nil?,
136
+ license: VersionHelper.license(version_hash: version_used),
121
137
  libyear: LibyearHelper.gem_libyear(
122
138
  version_used_release_date: VersionHelper.release_date(version_hash: version_used),
123
139
  latest_version_release_date: VersionHelper.release_date(version_hash: last_release),
@@ -126,10 +142,10 @@ module StillActive
126
142
  end
127
143
  end
128
144
 
129
- def gem_info_non_rubygems(gem_name:, gem_version:, result_object:, source_uri: nil)
145
+ def gem_info_non_rubygems(gem_name:, gem_version:, result_object:, source_uri: nil, advisory_db: nil)
130
146
  repo_info = repository_info_for_non_rubygems(gem_name: gem_name, source_uri: source_uri)
131
147
  source, owner, name = repo_info.values_at(:source, :owner, :name)
132
- deps_dev = gem_version ? fetch_deps_dev_info(gem_name: gem_name, version: gem_version) : {}
148
+ deps_dev = gem_version ? fetch_deps_dev_info(gem_name: gem_name, version: gem_version, advisory_db: advisory_db) : {}
133
149
 
134
150
  # Fall back to repo-derived project_id for scorecard when deps.dev doesn't have the version
135
151
  deps_dev[:scorecard_score] ||= DepsDevClient.project_scorecard(project_id: repo_info[:project_id])&.dig(:score)
@@ -142,11 +158,23 @@ module StillActive
142
158
  })
143
159
  end
144
160
 
145
- def fetch_deps_dev_info(gem_name:, version:)
161
+ def attach_alternatives(gem_name:, result_object:, catalog:)
162
+ return if catalog.nil?
163
+ return unless [:archived, :critical].include?(ActivityHelper.activity_level(result_object[gem_name]))
164
+
165
+ leads = AlternativesHelper.leads_for(gem_name: gem_name, index: catalog)
166
+ result_object[gem_name][:alternatives] = leads unless leads.empty?
167
+ rescue StandardError
168
+ nil # cosmetic best-effort: lead-fetching must never break the core audit
169
+ end
170
+
171
+ def fetch_deps_dev_info(gem_name:, version:, advisory_db: nil)
146
172
  info = DepsDevClient.version_info(gem_name: gem_name, version: version)
147
173
  scorecard = DepsDevClient.project_scorecard(project_id: info&.dig(:project_id))
148
174
  advisory_keys = info&.dig(:advisory_keys) || []
149
- vulnerabilities = advisory_keys.filter_map { |id| DepsDevClient.advisory_detail(advisory_id: id) }
175
+ deps_dev_vulns = advisory_keys.filter_map { |id| DepsDevClient.advisory_detail(advisory_id: id) }
176
+ radb_vulns = RubyAdvisoryDb.advisories_for(database: advisory_db, gem_name: gem_name, version: version)
177
+ vulnerabilities = VulnerabilityHelper.merge_advisories(deps_dev: deps_dev_vulns, ruby_advisory_db: radb_vulns)
150
178
  {
151
179
  scorecard_score: scorecard&.dig(:score),
152
180
  vulnerability_count: vulnerabilities.length,
data/still_active.gemspec CHANGED
@@ -36,6 +36,7 @@ Gem::Specification.new do |spec|
36
36
  spec.executables = spec.files.grep(%r{\Abin/still_active}) { |f| File.basename(f) }
37
37
  spec.require_paths = ["lib"]
38
38
 
39
+ spec.add_development_dependency("bundler-audit")
39
40
  spec.add_development_dependency("debug")
40
41
  spec.add_development_dependency("faker")
41
42
  spec.add_development_dependency("json_schemer")
@@ -44,7 +45,11 @@ Gem::Specification.new do |spec|
44
45
  spec.add_development_dependency("rubocop-rspec")
45
46
  spec.add_development_dependency("rubocop-shopify")
46
47
 
47
- spec.add_runtime_dependency("async")
48
+ # 2.0/2.1 ship a scheduler that breaks our fan-out (io_read); 2.2 is the
49
+ # verified floor (checked against Ruby 3.3 in Docker). octokit/faraday-retry/
50
+ # gems work down to ancient versions, so they stay unpinned rather than
51
+ # carry an artificial floor.
52
+ spec.add_runtime_dependency("async", ">= 2.2")
48
53
  spec.add_runtime_dependency("bundler", ">= 2.0")
49
54
  spec.add_runtime_dependency("faraday-retry")
50
55
  spec.add_runtime_dependency("gems")
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: still_active
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.2
4
+ version: 1.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sean Floyd
@@ -9,6 +9,20 @@ bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: bundler-audit
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :development
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
12
26
  - !ruby/object:Gem::Dependency
13
27
  name: debug
14
28
  requirement: !ruby/object:Gem::Requirement
@@ -113,14 +127,14 @@ dependencies:
113
127
  requirements:
114
128
  - - ">="
115
129
  - !ruby/object:Gem::Version
116
- version: '0'
130
+ version: '2.2'
117
131
  type: :runtime
118
132
  prerelease: false
119
133
  version_requirements: !ruby/object:Gem::Requirement
120
134
  requirements:
121
135
  - - ">="
122
136
  - !ruby/object:Gem::Version
123
- version: '0'
137
+ version: '2.2'
124
138
  - !ruby/object:Gem::Dependency
125
139
  name: bundler
126
140
  requirement: !ruby/object:Gem::Requirement
@@ -197,14 +211,19 @@ files:
197
211
  - README.md
198
212
  - bin/still_active
199
213
  - lib/helpers/activity_helper.rb
214
+ - lib/helpers/alternatives_helper.rb
200
215
  - lib/helpers/ansi_helper.rb
216
+ - lib/helpers/bot_context.rb
201
217
  - lib/helpers/bundler_helper.rb
218
+ - lib/helpers/catalog_index.rb
219
+ - lib/helpers/cyclonedx_helper.rb
202
220
  - lib/helpers/diff_markdown_helper.rb
203
221
  - lib/helpers/emoji_helper.rb
204
222
  - lib/helpers/http_helper.rb
205
223
  - lib/helpers/libyear_helper.rb
206
224
  - lib/helpers/lockfile_indexer.rb
207
225
  - lib/helpers/markdown_helper.rb
226
+ - lib/helpers/ruby_advisory_db.rb
208
227
  - lib/helpers/ruby_helper.rb
209
228
  - lib/helpers/sarif_helper.rb
210
229
  - lib/helpers/terminal_helper.rb