Module: StillActive::SbomReader
Overview
Reads a CycloneDX SBOM (e.g. produced by Syft) into a clean, normalized dependency set -- the breadth input for non-Ruby ecosystems, where the maintenance lens runs over deps.dev/ecosyste.ms.
parse returns both the assessable dependencies AND the components it could
NOT assess (a real dependency in an unsupported ecosystem, or a library with
no PURL -- typically a git/path/local source). Surfacing those is the point:
silently dropping them would let an audit report "all clear" while ignoring
the deps that are often the riskiest. Genuine non-package noise (GitHub
Actions pkg:github, opaque binaries pkg:generic, and file/application
components) is excluded, not surfaced.
read keeps the simple "just the dependencies" shape. Never raises -- a
missing/malformed SBOM or a bad PURL degrades to empty/skip.
Defined Under Namespace
Classes: Result
Constant Summary collapse
- ECOSYSTEMS =
PURL type -> still_active ecosystem (also the deps.dev
system, lowercased). { "gem" => :rubygems, "npm" => :npm, "pypi" => :pypi, "cargo" => :cargo, "maven" => :maven, "golang" => :go, "nuget" => :nuget }.freeze
- NOISE_TYPES =
PURL types that are not package dependencies: CI actions and opaque binaries. Excluded from both lists (not "unassessed deps").
["github", "generic"].freeze
Instance Method Summary collapse
Instance Method Details
#parse(path) ⇒ Object
47 48 49 50 51 |
# File 'lib/still_active/sbom_reader.rb', line 47 def parse(path) parse_string(File.read(path)) rescue SystemCallError, IOError Result.new(dependencies: [], unassessable: []) end |
#parse_string(body) ⇒ Object
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/still_active/sbom_reader.rb', line 53 def parse_string(body) doc = JSON.parse(body) components = doc.is_a?(Hash) ? doc["components"] : nil return Result.new(dependencies: [], unassessable: []) unless components.is_a?(Array) # Only trust the prod/dev split when the generator actually marks it # somewhere (scope, or a dev property). syft-style SBOMs mark nothing, so an # unmarked component is genuinely unknown -- never assume it's production. # Scoped to library components: scope/properties are legal on any component # type (application/file/...), and a dev signal on one of those is unrelated # to whether the dependency library components are marked. marks_dev = components.any? { |c| c.is_a?(Hash) && c["type"] == "library" && dev_signal?(c) } deps = [] unassessable = [] entries_by_ref = {} components.each do |component| kind, entry = classify(component) entry[:production] = !dev_signal?(component) if kind == :dependency && marks_dev if kind == :dependency ref = component["bom-ref"] entries_by_ref[ref] = entry if ref.is_a?(String) deps << entry end unassessable << entry if kind == :unassessable end drop_project_components(doc, deps, entries_by_ref) attach_dependency_graph(doc, deps, entries_by_ref) # uniq collapses a package that a generator lists more than once (e.g. Syft's # per-location entries); `production` is derived from each component's own # signal, so duplicates of the same name+version dedup cleanly unless a # generator marks the copies inconsistently (malformed; not observed). Result.new(dependencies: deps.uniq, unassessable: unassessable.uniq) rescue JSON::ParserError Result.new(dependencies: [], unassessable: []) end |
#read(path) ⇒ Object
44 |
# File 'lib/still_active/sbom_reader.rb', line 44 def read(path) = parse(path).dependencies |
#read_string(body) ⇒ Object
45 |
# File 'lib/still_active/sbom_reader.rb', line 45 def read_string(body) = parse_string(body).dependencies |