still_active 3.0.0.rc6 → 3.1.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +46 -2
- data/README.md +28 -3
- data/lib/still_active/artifactory_client.rb +1 -1
- data/lib/still_active/cli.rb +91 -29
- data/lib/still_active/compact_index_client.rb +1 -1
- data/lib/still_active/config.rb +2 -0
- data/lib/still_active/config_file.rb +3 -2
- data/lib/still_active/deps_dev_client.rb +24 -3
- data/lib/still_active/ecosystem_lens.rb +19 -7
- data/lib/still_active/ecosystems_client.rb +1 -1
- data/lib/still_active/forgejo_client.rb +1 -1
- data/lib/still_active/gitlab_client.rb +1 -1
- data/lib/{helpers → still_active/helpers}/activity_helper.rb +1 -1
- data/lib/still_active/helpers/cvss_helper.rb +29 -0
- data/lib/{helpers → still_active/helpers}/cyclonedx_helper.rb +85 -7
- data/lib/{helpers → still_active/helpers}/libyear_helper.rb +1 -1
- data/lib/{helpers → still_active/helpers}/markdown_helper.rb +19 -0
- data/lib/{helpers → still_active/helpers}/sarif_helper.rb +17 -2
- data/lib/still_active/helpers/sbom_graph.rb +166 -0
- data/lib/{helpers → still_active/helpers}/status_helper.rb +23 -5
- data/lib/{helpers → still_active/helpers}/terminal_helper.rb +15 -0
- data/lib/{helpers → still_active/helpers}/version_helper.rb +14 -2
- data/lib/{helpers → still_active/helpers}/vulnerability_helper.rb +4 -5
- data/lib/still_active/options.rb +6 -3
- data/lib/still_active/osv_client.rb +2 -2
- data/lib/still_active/poison_security_correlator.rb +3 -3
- data/lib/still_active/pypi_client.rb +1 -1
- data/lib/still_active/sarif/rules.rb +15 -0
- data/lib/still_active/sbom_reader.rb +117 -2
- data/lib/still_active/sbom_workflow.rb +4 -4
- data/lib/still_active/suppressions.rb +5 -3
- data/lib/still_active/version.rb +1 -1
- data/lib/still_active/workflow.rb +17 -11
- data/still_active.gemspec +6 -6
- metadata +48 -33
- data/lib/helpers/cvss_helper.rb +0 -63
- /data/lib/{helpers → still_active/helpers}/alternatives_helper.rb +0 -0
- /data/lib/{helpers → still_active/helpers}/ansi_helper.rb +0 -0
- /data/lib/{helpers → still_active/helpers}/bot_context.rb +0 -0
- /data/lib/{helpers → still_active/helpers}/bundler_helper.rb +0 -0
- /data/lib/{helpers → still_active/helpers}/catalog_index.rb +0 -0
- /data/lib/{helpers → still_active/helpers}/constraint_helper.rb +0 -0
- /data/lib/{helpers → still_active/helpers}/dependency_helper.rb +0 -0
- /data/lib/{helpers → still_active/helpers}/diff_markdown_helper.rb +0 -0
- /data/lib/{helpers → still_active/helpers}/dotnet_helper.rb +0 -0
- /data/lib/{helpers → still_active/helpers}/emoji_helper.rb +0 -0
- /data/lib/{helpers → still_active/helpers}/endoflife_helper.rb +0 -0
- /data/lib/{helpers → still_active/helpers}/http_helper.rb +0 -0
- /data/lib/{helpers → still_active/helpers}/lockfile_dependency_parser.rb +0 -0
- /data/lib/{helpers → still_active/helpers}/lockfile_indexer.rb +0 -0
- /data/lib/{helpers → still_active/helpers}/markdown_escape.rb +0 -0
- /data/lib/{helpers → still_active/helpers}/pep440_helper.rb +0 -0
- /data/lib/{helpers → still_active/helpers}/python_helper.rb +0 -0
- /data/lib/{helpers → still_active/helpers}/ruby_advisory_db.rb +0 -0
- /data/lib/{helpers → still_active/helpers}/ruby_helper.rb +0 -0
- /data/lib/{helpers → still_active/helpers}/runtime_ceiling_helper.rb +0 -0
- /data/lib/{helpers → still_active/helpers}/semver_satisfaction.rb +0 -0
- /data/lib/{helpers → still_active/helpers}/summary_helper.rb +0 -0
|
@@ -4,6 +4,8 @@ require "json"
|
|
|
4
4
|
require "uri"
|
|
5
5
|
require "package_url"
|
|
6
6
|
|
|
7
|
+
require_relative "helpers/sbom_graph"
|
|
8
|
+
|
|
7
9
|
module StillActive
|
|
8
10
|
# Reads a CycloneDX SBOM (e.g. produced by Syft) into a clean, normalized
|
|
9
11
|
# dependency set -- the breadth input for non-Ruby ecosystems, where the
|
|
@@ -63,12 +65,19 @@ module StillActive
|
|
|
63
65
|
|
|
64
66
|
deps = []
|
|
65
67
|
unassessable = []
|
|
68
|
+
entries_by_ref = {}
|
|
66
69
|
components.each do |component|
|
|
67
70
|
kind, entry = classify(component)
|
|
68
71
|
entry[:production] = !dev_signal?(component) if kind == :dependency && marks_dev
|
|
69
|
-
|
|
72
|
+
if kind == :dependency
|
|
73
|
+
ref = component["bom-ref"]
|
|
74
|
+
entries_by_ref[ref] = entry if ref.is_a?(String)
|
|
75
|
+
deps << entry
|
|
76
|
+
end
|
|
70
77
|
unassessable << entry if kind == :unassessable
|
|
71
78
|
end
|
|
79
|
+
drop_project_components(doc, deps, entries_by_ref)
|
|
80
|
+
attach_dependency_graph(doc, deps, entries_by_ref)
|
|
72
81
|
# uniq collapses a package that a generator lists more than once (e.g. Syft's
|
|
73
82
|
# per-location entries); `production` is derived from each component's own
|
|
74
83
|
# signal, so duplicates of the same name+version dedup cleanly unless a
|
|
@@ -80,6 +89,92 @@ module StillActive
|
|
|
80
89
|
|
|
81
90
|
private
|
|
82
91
|
|
|
92
|
+
# A Syft SBOM lists the scanned project as an ordinary library component with a
|
|
93
|
+
# purl, so it was classified as one of its own dependencies and assessed: no
|
|
94
|
+
# registry entry, therefore no releases, therefore reported as critically stale.
|
|
95
|
+
# That is a confident false verdict about the user's own code, on the workflow
|
|
96
|
+
# the README recommends, and it trips --fail-if-critical.
|
|
97
|
+
#
|
|
98
|
+
# The project is dropped rather than surfaced as unassessable: it is not a
|
|
99
|
+
# dependency we failed to assess, it is not a dependency, which is the same
|
|
100
|
+
# treatment CI actions and opaque binaries already get. Nothing changes for an
|
|
101
|
+
# SBOM with no dependency graph, and the rule needs the project to actually
|
|
102
|
+
# depend on something, so a dependency with a missing parent edge is never
|
|
103
|
+
# dropped for looking parentless.
|
|
104
|
+
def drop_project_components(doc, deps, entries_by_ref)
|
|
105
|
+
return if entries_by_ref.empty?
|
|
106
|
+
|
|
107
|
+
roots = SbomGraph.project_refs(dependencies: doc["dependencies"], library_refs: entries_by_ref.keys.to_set)
|
|
108
|
+
return if roots.empty?
|
|
109
|
+
|
|
110
|
+
# Compared by identity, not value: two components can classify to equal
|
|
111
|
+
# hashes, and only the one the graph named as a root should be dropped.
|
|
112
|
+
dropped = roots.filter_map { |ref| entries_by_ref.delete(ref)&.object_id }.to_set
|
|
113
|
+
deps.reject! { |entry| dropped.include?(entry.object_id) }
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Place each package in the CycloneDX dependency graph, so a cross-ecosystem
|
|
117
|
+
# audit can say which packages you actually declared and, for the rest, the
|
|
118
|
+
# declared package that pulls each one in. Same contract as the native path:
|
|
119
|
+
# `direct` is a boolean, and `dependency_path` is present only for a transitive
|
|
120
|
+
# package, head-first so it names the dependency a maintainer can act on.
|
|
121
|
+
#
|
|
122
|
+
# Both fields are attached ONLY to packages the graph actually places. A
|
|
123
|
+
# generator can emit a near-empty graph (a Syft directory scan of a Ruby project
|
|
124
|
+
# produced one edge across 789 components), and stamping `direct: false` on
|
|
125
|
+
# everything it failed to mention would be the positive claim "none of these are
|
|
126
|
+
# yours" about a document that never said. Absent means unknown, which the
|
|
127
|
+
# renderers already treat correctly: they test `direct == false`, not falsiness.
|
|
128
|
+
def attach_dependency_graph(doc, deps, entries_by_ref)
|
|
129
|
+
return if entries_by_ref.empty?
|
|
130
|
+
|
|
131
|
+
placements = SbomGraph.resolve(
|
|
132
|
+
dependencies: doc["dependencies"],
|
|
133
|
+
root_ref: doc.dig("metadata", "component", "bom-ref"),
|
|
134
|
+
library_refs: entries_by_ref.keys.to_set
|
|
135
|
+
)
|
|
136
|
+
return if placements.nil?
|
|
137
|
+
|
|
138
|
+
identities = entries_by_ref.transform_values { |entry| "#{entry[:ecosystem]}/#{entry[:name]}" }
|
|
139
|
+
# Fold per package identity first. A generator may list one package under
|
|
140
|
+
# several bom-refs (Syft emits a component per location), and those copies are
|
|
141
|
+
# collapsed by `uniq` below; if they carried different placements they would
|
|
142
|
+
# stop being equal and the package would appear twice in the audit.
|
|
143
|
+
best = {}
|
|
144
|
+
entries_by_ref.each do |ref, entry|
|
|
145
|
+
placement = placements[ref]
|
|
146
|
+
next if placement.nil?
|
|
147
|
+
|
|
148
|
+
key = identity_key(entry)
|
|
149
|
+
best[key] = better_placement(best[key], placement)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
deps.each do |entry|
|
|
153
|
+
placement = best[identity_key(entry)]
|
|
154
|
+
next if placement.nil?
|
|
155
|
+
|
|
156
|
+
entry[:direct] = placement[:direct]
|
|
157
|
+
next if placement[:direct]
|
|
158
|
+
|
|
159
|
+
path = Array(placement[:path]).filter_map { |ref| identities[ref] }
|
|
160
|
+
entry[:dependency_path] = path unless path.empty?
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def identity_key(entry)
|
|
165
|
+
[entry[:ecosystem], entry[:name], entry[:version]]
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Declared beats pulled-in (you can act on it directly), and among transitive
|
|
169
|
+
# placements the shorter path wins, matching the native path's shortest-path BFS.
|
|
170
|
+
def better_placement(current, candidate)
|
|
171
|
+
return candidate if current.nil?
|
|
172
|
+
return current if current[:direct]
|
|
173
|
+
return candidate if candidate[:direct]
|
|
174
|
+
|
|
175
|
+
(Array(candidate[:path]).length < Array(current[:path]).length) ? candidate : current
|
|
176
|
+
end
|
|
177
|
+
|
|
83
178
|
# Whether a component is marked dev/test-only. CycloneDX `scope` (excluded =
|
|
84
179
|
# not part of the product, optional = not needed at runtime) is the standard;
|
|
85
180
|
# tools that don't set scope may instead emit a property (cyclonedx-npm's
|
|
@@ -146,7 +241,12 @@ module StillActive
|
|
|
146
241
|
return [:unassessable, {ecosystem: ecosystem, name: full_name, version: parsed.version, reason: :private_registry, repository_url: repository_url}]
|
|
147
242
|
end
|
|
148
243
|
|
|
149
|
-
|
|
244
|
+
# The input's own PURL, kept verbatim rather than parsed-and-rebuilt. An
|
|
245
|
+
# enriched SBOM re-emits it unchanged, so whatever a consumer matched on
|
|
246
|
+
# the input it matches on our output, and no per-ecosystem PURL
|
|
247
|
+
# reconstruction (npm scopes, maven group:artifact, go module paths) can
|
|
248
|
+
# get it subtly wrong.
|
|
249
|
+
[:dependency, {ecosystem: ecosystem, name: full_name, version: parsed.version, purl: preserved_purl(purl)}]
|
|
150
250
|
elsif NOISE_TYPES.include?(type)
|
|
151
251
|
nil # CI actions / opaque binaries: not a package dependency
|
|
152
252
|
else
|
|
@@ -161,6 +261,21 @@ module StillActive
|
|
|
161
261
|
[:unassessable, {ecosystem: nil, name: name, reason: :malformed_purl}]
|
|
162
262
|
end
|
|
163
263
|
|
|
264
|
+
# The input's PURL, kept for re-emission in an enriched SBOM, with Syft's
|
|
265
|
+
# `package-id` qualifier removed. That qualifier is Syft's internal per-location
|
|
266
|
+
# bookkeeping rather than part of the package's identity, and it is why the same
|
|
267
|
+
# package can appear twice under two different PURLs; dropping it is what lets
|
|
268
|
+
# those copies still collapse. Everything else is left byte-identical rather than
|
|
269
|
+
# re-serialized through PackageURL, so a consumer that matched the input PURL
|
|
270
|
+
# matches ours, with no chance of a normalization changing the encoding.
|
|
271
|
+
def preserved_purl(purl)
|
|
272
|
+
base, separator, query = purl.partition("?")
|
|
273
|
+
return purl if separator.empty?
|
|
274
|
+
|
|
275
|
+
kept = query.split("&").reject { |pair| pair.start_with?("package-id=") }
|
|
276
|
+
kept.empty? ? base : "#{base}?#{kept.join("&")}"
|
|
277
|
+
end
|
|
278
|
+
|
|
164
279
|
# Whether a repository_url points at a known public registry. We only read the
|
|
165
280
|
# host; we never connect to it (a lockfile/SBOM-derived URL is untrusted). An
|
|
166
281
|
# unparseable or non-public host reads as private, failing safe.
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
require_relative "ecosystem_lens"
|
|
4
4
|
require_relative "ceiling_reconciler"
|
|
5
5
|
require_relative "poison_security_correlator"
|
|
6
|
-
require_relative "
|
|
7
|
-
require_relative "
|
|
6
|
+
require_relative "helpers/python_helper"
|
|
7
|
+
require_relative "helpers/dotnet_helper"
|
|
8
8
|
require "async"
|
|
9
9
|
require "async/barrier"
|
|
10
10
|
require "async/semaphore"
|
|
@@ -62,7 +62,7 @@ module StillActive
|
|
|
62
62
|
# otherwise): absent stays "unknown", never a false that reads as test-only.
|
|
63
63
|
result["#{dep[:ecosystem]}/#{dep[:name]}@#{dep[:version]}"] =
|
|
64
64
|
EcosystemLens.assess(ecosystem: dep[:ecosystem], name: dep[:name], version: dep[:version], constraint_cache: constraint_cache, runtime_ranges: runtime_ranges)
|
|
65
|
-
.merge(dep.slice(:production))
|
|
65
|
+
.merge(dep.slice(:production, :direct, :dependency_path, :purl))
|
|
66
66
|
rescue => e
|
|
67
67
|
# One dependency's failure must not abort the audit, but it must not
|
|
68
68
|
# disappear either: record it as an unassessable entry (same shape as
|
|
@@ -70,7 +70,7 @@ module StillActive
|
|
|
70
70
|
# production rides along (when known, via slice) so a failed prod dep
|
|
71
71
|
# stays distinguishable from a failed dev one.
|
|
72
72
|
failures << {ecosystem: dep[:ecosystem], name: dep[:name], version: dep[:version], reason: :assessment_error, error: "#{e.class}: #{e.message}"}
|
|
73
|
-
.merge(dep.slice(:production))
|
|
73
|
+
.merge(dep.slice(:production, :direct, :dependency_path, :purl))
|
|
74
74
|
warn("error assessing #{dep[:ecosystem]}/#{dep[:name]}@#{dep[:version]}: #{e.class}\n\t#{e.message}")
|
|
75
75
|
ensure
|
|
76
76
|
completed += 1
|
|
@@ -16,7 +16,7 @@ module StillActive
|
|
|
16
16
|
# name an explicit advisory id, so a newly disclosed CVE on the same gem is
|
|
17
17
|
# never pre-silenced.
|
|
18
18
|
class Suppressions
|
|
19
|
-
GATEABLE_SIGNALS = [:activity, :vulnerability, :libyear, :poison, :language_ceiling].freeze
|
|
19
|
+
GATEABLE_SIGNALS = [:activity, :vulnerability, :libyear, :poison, :language_ceiling, :deprecated].freeze
|
|
20
20
|
|
|
21
21
|
Entry = Struct.new(:gem, :advisory, :signal, :reason, :expires, keyword_init: true) do
|
|
22
22
|
def whole_gem?
|
|
@@ -76,14 +76,16 @@ module StillActive
|
|
|
76
76
|
return false
|
|
77
77
|
end
|
|
78
78
|
if signal && !GATEABLE_SIGNALS.include?(signal)
|
|
79
|
-
|
|
79
|
+
# Listed from the constant rather than spelled out: the hand-written
|
|
80
|
+
# version had already drifted (it never mentioned language_ceiling).
|
|
81
|
+
warnings << "ignoring suppression for #{label}: unknown signal #{signal.inspect} (expected #{GATEABLE_SIGNALS.join(", ")})"
|
|
80
82
|
return false
|
|
81
83
|
end
|
|
82
84
|
if signal == :vulnerability && advisory.nil?
|
|
83
85
|
warnings << "ignoring vulnerability suppression for #{label}: must name an advisory id so newly disclosed advisories still surface"
|
|
84
86
|
return false
|
|
85
87
|
end
|
|
86
|
-
if [:activity, :libyear, :poison].include?(signal) && gem.nil?
|
|
88
|
+
if [:activity, :libyear, :poison, :deprecated].include?(signal) && gem.nil?
|
|
87
89
|
warnings << "ignoring #{signal} suppression: must name a gem"
|
|
88
90
|
return false
|
|
89
91
|
end
|
data/lib/still_active/version.rb
CHANGED
|
@@ -12,17 +12,17 @@ require_relative "forgejo_client"
|
|
|
12
12
|
require_relative "github_client"
|
|
13
13
|
require_relative "gitlab_client"
|
|
14
14
|
require_relative "repository"
|
|
15
|
-
require_relative "
|
|
16
|
-
require_relative "
|
|
17
|
-
require_relative "
|
|
18
|
-
require_relative "
|
|
19
|
-
require_relative "
|
|
20
|
-
require_relative "
|
|
21
|
-
require_relative "
|
|
22
|
-
require_relative "
|
|
23
|
-
require_relative "
|
|
24
|
-
require_relative "
|
|
25
|
-
require_relative "
|
|
15
|
+
require_relative "helpers/activity_helper"
|
|
16
|
+
require_relative "helpers/alternatives_helper"
|
|
17
|
+
require_relative "helpers/catalog_index"
|
|
18
|
+
require_relative "helpers/constraint_helper"
|
|
19
|
+
require_relative "helpers/http_helper"
|
|
20
|
+
require_relative "helpers/libyear_helper"
|
|
21
|
+
require_relative "helpers/ruby_advisory_db"
|
|
22
|
+
require_relative "helpers/ruby_helper"
|
|
23
|
+
require_relative "helpers/runtime_ceiling_helper"
|
|
24
|
+
require_relative "helpers/version_helper"
|
|
25
|
+
require_relative "helpers/vulnerability_helper"
|
|
26
26
|
require "async"
|
|
27
27
|
require "async/barrier"
|
|
28
28
|
require "async/semaphore"
|
|
@@ -390,6 +390,12 @@ module StillActive
|
|
|
390
390
|
{
|
|
391
391
|
scorecard_score: scorecard&.dig(:score),
|
|
392
392
|
scorecard_maintained: scorecard&.dig(:maintained),
|
|
393
|
+
# The maintainer's own "stop using this" declaration, from the same
|
|
394
|
+
# response as the advisories. RubyGems has no deprecation mechanism, so
|
|
395
|
+
# this is effectively always false for gems today; it is threaded anyway
|
|
396
|
+
# so the field means the same thing on both paths.
|
|
397
|
+
deprecated: info&.dig(:deprecated) == true,
|
|
398
|
+
deprecation_reason: info&.dig(:deprecation_reason),
|
|
393
399
|
vulnerability_count: vulnerabilities.length,
|
|
394
400
|
vulnerabilities: vulnerabilities
|
|
395
401
|
}
|
data/still_active.gemspec
CHANGED
|
@@ -55,12 +55,12 @@ Gem::Specification.new do |spec|
|
|
|
55
55
|
# carry an artificial floor.
|
|
56
56
|
spec.add_runtime_dependency("async", ">= 2.2")
|
|
57
57
|
spec.add_runtime_dependency("bundler", ">= 2.0")
|
|
58
|
-
#
|
|
59
|
-
#
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
#
|
|
63
|
-
|
|
58
|
+
# CVSS base score from an OSV vector, for the CVSS-4-only advisory deps.dev can't
|
|
59
|
+
# score. 5.0 is the floor because it is the first release without the poison-pill
|
|
60
|
+
# constraints (a Ruby 2.6 floor, a bigdecimal cap, an exact bundler pin) that kept
|
|
61
|
+
# it optional through 3.0; 5.0 also adds base_score on CVSS 4.0, which is the API
|
|
62
|
+
# CvssHelper reads.
|
|
63
|
+
spec.add_runtime_dependency("cvss-suite", ">= 5.0")
|
|
64
64
|
spec.add_runtime_dependency("faraday-retry")
|
|
65
65
|
spec.add_runtime_dependency("gems")
|
|
66
66
|
spec.add_runtime_dependency("octokit")
|
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: 3.
|
|
4
|
+
version: 3.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sean Floyd
|
|
@@ -37,6 +37,20 @@ dependencies:
|
|
|
37
37
|
- - ">="
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
39
|
version: '2.0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: cvss-suite
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '5.0'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '5.0'
|
|
40
54
|
- !ruby/object:Gem::Dependency
|
|
41
55
|
name: faraday-retry
|
|
42
56
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -133,38 +147,6 @@ files:
|
|
|
133
147
|
- LICENSE.txt
|
|
134
148
|
- README.md
|
|
135
149
|
- bin/still_active
|
|
136
|
-
- lib/helpers/activity_helper.rb
|
|
137
|
-
- lib/helpers/alternatives_helper.rb
|
|
138
|
-
- lib/helpers/ansi_helper.rb
|
|
139
|
-
- lib/helpers/bot_context.rb
|
|
140
|
-
- lib/helpers/bundler_helper.rb
|
|
141
|
-
- lib/helpers/catalog_index.rb
|
|
142
|
-
- lib/helpers/constraint_helper.rb
|
|
143
|
-
- lib/helpers/cvss_helper.rb
|
|
144
|
-
- lib/helpers/cyclonedx_helper.rb
|
|
145
|
-
- lib/helpers/dependency_helper.rb
|
|
146
|
-
- lib/helpers/diff_markdown_helper.rb
|
|
147
|
-
- lib/helpers/dotnet_helper.rb
|
|
148
|
-
- lib/helpers/emoji_helper.rb
|
|
149
|
-
- lib/helpers/endoflife_helper.rb
|
|
150
|
-
- lib/helpers/http_helper.rb
|
|
151
|
-
- lib/helpers/libyear_helper.rb
|
|
152
|
-
- lib/helpers/lockfile_dependency_parser.rb
|
|
153
|
-
- lib/helpers/lockfile_indexer.rb
|
|
154
|
-
- lib/helpers/markdown_escape.rb
|
|
155
|
-
- lib/helpers/markdown_helper.rb
|
|
156
|
-
- lib/helpers/pep440_helper.rb
|
|
157
|
-
- lib/helpers/python_helper.rb
|
|
158
|
-
- lib/helpers/ruby_advisory_db.rb
|
|
159
|
-
- lib/helpers/ruby_helper.rb
|
|
160
|
-
- lib/helpers/runtime_ceiling_helper.rb
|
|
161
|
-
- lib/helpers/sarif_helper.rb
|
|
162
|
-
- lib/helpers/semver_satisfaction.rb
|
|
163
|
-
- lib/helpers/status_helper.rb
|
|
164
|
-
- lib/helpers/summary_helper.rb
|
|
165
|
-
- lib/helpers/terminal_helper.rb
|
|
166
|
-
- lib/helpers/version_helper.rb
|
|
167
|
-
- lib/helpers/vulnerability_helper.rb
|
|
168
150
|
- lib/still_active.rb
|
|
169
151
|
- lib/still_active/artifactory_client.rb
|
|
170
152
|
- lib/still_active/ceiling_reconciler.rb
|
|
@@ -181,6 +163,39 @@ files:
|
|
|
181
163
|
- lib/still_active/forgejo_client.rb
|
|
182
164
|
- lib/still_active/github_client.rb
|
|
183
165
|
- lib/still_active/gitlab_client.rb
|
|
166
|
+
- lib/still_active/helpers/activity_helper.rb
|
|
167
|
+
- lib/still_active/helpers/alternatives_helper.rb
|
|
168
|
+
- lib/still_active/helpers/ansi_helper.rb
|
|
169
|
+
- lib/still_active/helpers/bot_context.rb
|
|
170
|
+
- lib/still_active/helpers/bundler_helper.rb
|
|
171
|
+
- lib/still_active/helpers/catalog_index.rb
|
|
172
|
+
- lib/still_active/helpers/constraint_helper.rb
|
|
173
|
+
- lib/still_active/helpers/cvss_helper.rb
|
|
174
|
+
- lib/still_active/helpers/cyclonedx_helper.rb
|
|
175
|
+
- lib/still_active/helpers/dependency_helper.rb
|
|
176
|
+
- lib/still_active/helpers/diff_markdown_helper.rb
|
|
177
|
+
- lib/still_active/helpers/dotnet_helper.rb
|
|
178
|
+
- lib/still_active/helpers/emoji_helper.rb
|
|
179
|
+
- lib/still_active/helpers/endoflife_helper.rb
|
|
180
|
+
- lib/still_active/helpers/http_helper.rb
|
|
181
|
+
- lib/still_active/helpers/libyear_helper.rb
|
|
182
|
+
- lib/still_active/helpers/lockfile_dependency_parser.rb
|
|
183
|
+
- lib/still_active/helpers/lockfile_indexer.rb
|
|
184
|
+
- lib/still_active/helpers/markdown_escape.rb
|
|
185
|
+
- lib/still_active/helpers/markdown_helper.rb
|
|
186
|
+
- lib/still_active/helpers/pep440_helper.rb
|
|
187
|
+
- lib/still_active/helpers/python_helper.rb
|
|
188
|
+
- lib/still_active/helpers/ruby_advisory_db.rb
|
|
189
|
+
- lib/still_active/helpers/ruby_helper.rb
|
|
190
|
+
- lib/still_active/helpers/runtime_ceiling_helper.rb
|
|
191
|
+
- lib/still_active/helpers/sarif_helper.rb
|
|
192
|
+
- lib/still_active/helpers/sbom_graph.rb
|
|
193
|
+
- lib/still_active/helpers/semver_satisfaction.rb
|
|
194
|
+
- lib/still_active/helpers/status_helper.rb
|
|
195
|
+
- lib/still_active/helpers/summary_helper.rb
|
|
196
|
+
- lib/still_active/helpers/terminal_helper.rb
|
|
197
|
+
- lib/still_active/helpers/version_helper.rb
|
|
198
|
+
- lib/still_active/helpers/vulnerability_helper.rb
|
|
184
199
|
- lib/still_active/options.rb
|
|
185
200
|
- lib/still_active/osv_client.rb
|
|
186
201
|
- lib/still_active/poison_security_correlator.rb
|
data/lib/helpers/cvss_helper.rb
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module StillActive
|
|
4
|
-
# Computes a CVSS score from a vector string, across v2/v3/v4. deps.dev stores only
|
|
5
|
-
# CVSS 3.x, so a CVSS-4-only advisory (the flagship protobuf case) arrives with no
|
|
6
|
-
# numeric score; OSV carries the v4 vector, and this turns it into the number the
|
|
7
|
-
# SARIF security-severity and CycloneDX rating want.
|
|
8
|
-
#
|
|
9
|
-
# cvss-suite is an OPTIONAL, undeclared dependency: it over-constrains its own deps
|
|
10
|
-
# (an exact `bundler` pin, a `bigdecimal ~> 3.1` cap), the poison-pill pattern
|
|
11
|
-
# still_active itself flags, so hard-requiring it would force that liability on every
|
|
12
|
-
# user and trip our own audit. CvssHelper soft-requires it; install cvss-suite (or run
|
|
13
|
-
# the distribution that bundles it) to light it up.
|
|
14
|
-
#
|
|
15
|
-
# Returns cvss-suite's `overall_score`: for the base-only vectors OSV publishes that
|
|
16
|
-
# IS the base score, but a vector carrying threat/environmental metrics would fold
|
|
17
|
-
# those in -- which is why the severity band floors at the authoritative GHSA label
|
|
18
|
-
# (VulnerabilityHelper.advisory_severity) and this number never lowers it: the label
|
|
19
|
-
# drives gating and SARIF level, the number only sharpens display. Fails safe: an
|
|
20
|
-
# absent gem or an absent/unparseable vector yields nil, never a crash.
|
|
21
|
-
module CvssHelper
|
|
22
|
-
extend self
|
|
23
|
-
|
|
24
|
-
def score(vector)
|
|
25
|
-
return unless available?
|
|
26
|
-
return if vector.nil? || vector.to_s.empty?
|
|
27
|
-
|
|
28
|
-
cvss = CvssSuite.new(vector.to_s)
|
|
29
|
-
cvss.valid? ? cvss.overall_score : nil
|
|
30
|
-
rescue => e
|
|
31
|
-
# We only reach here with cvss-suite loaded (available?), so a raise is the
|
|
32
|
-
# installed gem failing this call site -- an incompatible version, a renamed
|
|
33
|
-
# `overall_score` -- not a bad vector, which cvss-suite reports via valid?.
|
|
34
|
-
# Fail safe to nil (the number is display-only, never gates), but don't
|
|
35
|
-
# swallow it: an opted-in-but-broken scorer that emitted nothing would read
|
|
36
|
-
# as "no advisories are scored". Warn once per run (not per advisory),
|
|
37
|
-
# matching OsvClient.enrich rather than staying silent.
|
|
38
|
-
warn_scorer_failed(e)
|
|
39
|
-
nil
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
# Memoized soft-require: true once cvss-suite loads, false when it isn't installed.
|
|
43
|
-
def available?
|
|
44
|
-
return @available unless @available.nil?
|
|
45
|
-
|
|
46
|
-
@available = begin
|
|
47
|
-
require "cvss_suite"
|
|
48
|
-
true
|
|
49
|
-
rescue LoadError
|
|
50
|
-
false
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
private
|
|
55
|
-
|
|
56
|
-
def warn_scorer_failed(error)
|
|
57
|
-
return if @warned
|
|
58
|
-
|
|
59
|
-
@warned = true
|
|
60
|
-
warn("warning: cvss-suite scoring failed: #{error.class} (#{error.message}); CVSS numbers unavailable this run, check your cvss-suite version")
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
end
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|