sbom 0.1.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e5779ca3a21e46aa2032845504cda20229928dad1570d5de2a055ffe2a2dae8d
4
- data.tar.gz: d57cc2a38620373ca402ca34322e0cfcb4d30bfa88b8a6791868c92f87b2393d
3
+ metadata.gz: '080bc0e7e22560d9779d7dc7914a6fd6d440dee48508da1db84d1c9d03fd3c5b'
4
+ data.tar.gz: 007f185f1ebc3a95bac42ca6419155e9a269bb0b98e1be5a478b6eab5f43e06c
5
5
  SHA512:
6
- metadata.gz: 221caf4d995e38991fd6c720b00e662fe90ae7709b7032a35d6592848ce669e5245591ca66866b66b29b68326d78afc213ce5292815b4e0f18cce51dcbcf651d
7
- data.tar.gz: 1a4e9340fda31c7fff6dddbfc12b912220a7aee701c4e3df860c41c00e26c6c5a7999727017bb7af201a09d5e505b8fd24a67d5188eef0b3450b1efdfbfc50e7
6
+ metadata.gz: e67dca4dfb55a94d8f55093938773389206f69d5765748323bed4672da6182d76e6d503bf96bda90bde6c721cabac9b21f061714de005fba6c4d4d25e874e4d5
7
+ data.tar.gz: fa270f7dad9214c3a15c1b1bad23d3982955bd262f4bdb332b6bf73f464ac9e846761f8ef01e4fb34058cbbe3253d80ee1f2c04460fa0e385754364acf75e1e9
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.3.0] - 2025-12-23
4
+
5
+ - Add `merge` command to CLI for combining multiple SBOMs into one
6
+ - Add `Sbom.merge` and `Sbom.merge_files` library methods
7
+ - Add `Sbom::Merger` class for merging SBOMs with configurable deduplication
8
+ - Merge deduplicates packages by PURL by default, with option to keep all
9
+ - Supports merging across formats (SPDX + CycloneDX)
10
+
11
+ ## [0.2.0] - 2025-12-14
12
+
13
+ - Add `enrich` command to CLI for enriching SBOMs with data from ecosyste.ms
14
+ - Add `Sbom.enrich` and `Sbom.enrich_file` library methods
15
+ - Add `Sbom::Enricher` class for enriching packages with metadata and security advisories
16
+ - Enrichment adds: description, homepage, download location, license, repository URL, registry URL, documentation URL, supplier info, and security advisories
17
+
3
18
  ## [0.1.0] - 2025-12-14
4
19
 
5
20
  - Initial release
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,49 @@
1
+ # Contributing
2
+
3
+ Bug reports and pull requests are welcome on GitHub at https://github.com/andrew/sbom.
4
+
5
+ ## Getting Started
6
+
7
+ 1. Fork the repository
8
+ 2. Clone your fork and set up the development environment:
9
+
10
+ ```bash
11
+ git clone https://github.com/YOUR_USERNAME/sbom.git
12
+ cd sbom
13
+ bin/setup
14
+ git submodule update --init --recursive
15
+ ```
16
+
17
+ 3. Run the tests to make sure everything works:
18
+
19
+ ```bash
20
+ bundle exec rake test
21
+ ```
22
+
23
+ ## Making Changes
24
+
25
+ 1. Create a feature branch from `main`
26
+ 2. Make your changes
27
+ 3. Add tests for any new functionality
28
+ 4. Ensure all tests pass
29
+ 5. Submit a pull request
30
+
31
+ ## Code Style
32
+
33
+ - Follow existing code conventions
34
+ - Keep commits focused and atomic
35
+ - Write clear commit messages
36
+
37
+ ## Running Tests
38
+
39
+ ```bash
40
+ bundle exec rake test
41
+ ```
42
+
43
+ ## Reporting Bugs
44
+
45
+ When reporting bugs, please include:
46
+ - Ruby version
47
+ - Steps to reproduce
48
+ - Expected vs actual behavior
49
+ - Sample SBOM files if relevant (sanitized of any sensitive data)
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Andrew Nesbitt
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md CHANGED
@@ -69,6 +69,39 @@ end
69
69
  Sbom::Validator.validate_file!("example.cdx.json")
70
70
  ```
71
71
 
72
+ ### Enriching SBOMs
73
+
74
+ Enrich packages with metadata from [ecosyste.ms](https://ecosyste.ms):
75
+
76
+ ```ruby
77
+ # Enrich an entire SBOM
78
+ sbom = Sbom.parse_file("example.cdx.json")
79
+ enriched = Sbom.enrich(sbom)
80
+
81
+ # Or parse and enrich in one step
82
+ enriched = Sbom.enrich_file("example.cdx.json")
83
+
84
+ ```
85
+
86
+ Enrichment adds: description, homepage, download location, license, repository URL, registry URL, documentation URL, supplier info, and security advisories.
87
+
88
+ ### Merging SBOMs
89
+
90
+ Combine multiple SBOMs into one:
91
+
92
+ ```ruby
93
+ # Merge from files (dedupes by PURL by default)
94
+ merged = Sbom.merge_files(["app1.cdx.json", "app2.spdx.json"])
95
+
96
+ # Merge SBOM objects
97
+ merged = Sbom.merge([sbom1, sbom2, sbom3])
98
+
99
+ # Keep all packages without deduplication
100
+ merged = Sbom.merge([sbom1, sbom2], dedupe: :none)
101
+ ```
102
+
103
+ Merging works across formats. Packages are deduplicated by PURL by default. Relationships and licenses are also deduplicated.
104
+
72
105
  ### Building Packages
73
106
 
74
107
  The Package class provides an object interface for building package data:
@@ -118,6 +151,16 @@ sbom document outline example.cdx.json
118
151
  sbom document info example.spdx.json
119
152
  sbom document query example.cdx.json --package rails
120
153
  sbom document query example.cdx.json --license MIT
154
+
155
+ # Enrich SBOM with ecosyste.ms data
156
+ sbom enrich example.cdx.json
157
+ sbom enrich example.cdx.json --output enriched.json
158
+ cat example.cdx.json | sbom enrich -
159
+
160
+ # Merge multiple SBOMs
161
+ sbom merge app1.cdx.json app2.spdx.json --output merged.json
162
+ sbom merge app1.json app2.json --no-dedupe
163
+ sbom merge app1.json app2.json --type cyclonedx
121
164
  ```
122
165
 
123
166
  ## Supported Formats
data/SECURITY.md ADDED
@@ -0,0 +1,21 @@
1
+ # Security Policy
2
+
3
+ ## Supported Versions
4
+
5
+ | Version | Supported |
6
+ | ------- | ------------------ |
7
+ | 0.1.x | :white_check_mark: |
8
+
9
+ ## Reporting a Vulnerability
10
+
11
+ If you discover a security vulnerability in this project, please report it privately by emailing andrewnez@gmail.com.
12
+
13
+ Please include:
14
+ - A description of the vulnerability
15
+ - Steps to reproduce the issue
16
+ - Potential impact
17
+ - Any suggested fixes (optional)
18
+
19
+ You can expect an initial response within 48 hours. We will work with you to understand and address the issue promptly.
20
+
21
+ Please do not open public issues for security vulnerabilities.
data/exe/sbom CHANGED
@@ -25,6 +25,10 @@ module Sbom
25
25
  validate_command
26
26
  when "convert"
27
27
  convert_command
28
+ when "enrich"
29
+ enrich_command
30
+ when "merge"
31
+ merge_command
28
32
  when "document"
29
33
  document_command
30
34
  when "version", "-v", "--version"
@@ -158,6 +162,85 @@ module Sbom
158
162
  abort "Conversion error: #{e.message}"
159
163
  end
160
164
 
165
+ def enrich_command
166
+ parser = OptionParser.new do |opts|
167
+ opts.banner = "Usage: sbom enrich <file> [options]"
168
+ opts.on("-t", "--type TYPE", "SBOM type (spdx, cyclonedx, auto)") { |v| @options[:type] = v.to_sym }
169
+ opts.on("-f", "--format FORMAT", "Output format (json, yaml)") { |v| @options[:format] = v }
170
+ opts.on("-o", "--output FILE", "Output file") { |v| @options[:output] = v }
171
+ opts.on("--advisories", "Include security advisories") { @options[:advisories] = true }
172
+ opts.on("-h", "--help", "Show help") { puts opts; exit }
173
+ end
174
+ parser.parse!(@args)
175
+
176
+ file = @args.shift
177
+ abort "Error: No file specified. Use '-' for stdin." unless file
178
+
179
+ sbom = if file == "-"
180
+ content = $stdin.read
181
+ Sbom::Parser.parse_string(content, sbom_type: @options[:type] || :auto)
182
+ else
183
+ Sbom::Parser.parse_file(file, sbom_type: @options[:type] || :auto)
184
+ end
185
+
186
+ enricher = Sbom::Enricher.new(sbom)
187
+ enriched = enricher.enrich
188
+
189
+ format = @options[:format] || "json"
190
+ output = case format
191
+ when "yaml"
192
+ enriched.to_h.to_yaml
193
+ else
194
+ JSON.pretty_generate(enriched.to_h)
195
+ end
196
+
197
+ if @options[:output]
198
+ File.write(@options[:output], output)
199
+ warn "Enriched SBOM written to #{@options[:output]}"
200
+ warn "Enrichment errors: #{enricher.errors.count}" if enricher.errors.any?
201
+ else
202
+ puts output
203
+ end
204
+
205
+ enricher.errors.each { |e| warn "Warning: #{e[:purl]} - #{e[:error]}" } if enricher.errors.any?
206
+ rescue Sbom::ParserError => e
207
+ abort "Error: #{e.message}"
208
+ end
209
+
210
+ def merge_command
211
+ parser = OptionParser.new do |opts|
212
+ opts.banner = "Usage: sbom merge <file1> <file2> [file3...] [options]"
213
+ opts.on("-t", "--type TYPE", "Output SBOM type (spdx, cyclonedx)") { |v| @options[:type] = v.to_sym }
214
+ opts.on("-f", "--format FORMAT", "Output format (json, yaml, tag)") { |v| @options[:format] = v.to_sym }
215
+ opts.on("-o", "--output FILE", "Output file") { |v| @options[:output] = v }
216
+ opts.on("--no-dedupe", "Keep all packages (don't dedupe by PURL)") { @options[:dedupe] = :none }
217
+ opts.on("-h", "--help", "Show help") { puts opts; exit }
218
+ end
219
+ parser.parse!(@args)
220
+
221
+ abort "Error: At least 2 files required" if @args.size < 2
222
+
223
+ dedupe = @options[:dedupe] || :purl
224
+ merged = Sbom::Merger.merge_files(@args, dedupe: dedupe)
225
+
226
+ output_type = @options[:type] || merged.sbom_type || :spdx
227
+ output_format = @options[:format] || :json
228
+
229
+ generator = Sbom::Generator.new(sbom_type: output_type, format: output_format)
230
+ generator.generate(merged.document&.dig(:name) || "Merged SBOM", merged.to_h)
231
+
232
+ output = generator.output
233
+
234
+ if @options[:output]
235
+ File.write(@options[:output], output)
236
+ puts "Merged SBOM written to #{@options[:output]}"
237
+ else
238
+ puts output
239
+ end
240
+ rescue Sbom::ParserError, Sbom::GeneratorError => e
241
+ abort "Error: #{e.message}"
242
+ end
243
+
161
244
  def document_command
162
245
  subcommand = @args.shift
163
246
 
@@ -329,6 +412,8 @@ module Sbom
329
412
  generate Create a new SBOM
330
413
  validate Validate SBOM against schema
331
414
  convert Convert between SBOM formats
415
+ enrich Enrich SBOM with data from ecosyste.ms
416
+ merge Combine multiple SBOMs into one
332
417
  document Work with SBOM documents
333
418
  version Show version
334
419
 
@@ -0,0 +1,162 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sbom
4
+ class Enricher
5
+ attr_reader :sbom, :errors
6
+
7
+ def initialize(sbom)
8
+ @sbom = sbom
9
+ @errors = []
10
+ end
11
+
12
+ def enrich
13
+ @sbom.packages.each do |package|
14
+ enrich_package(package)
15
+ end
16
+ @sbom
17
+ end
18
+
19
+ def enrich_package(package)
20
+ purl_string = package[:purl] || find_purl_in_external_refs(package)
21
+ return unless purl_string
22
+
23
+ parsed = parse_purl(purl_string)
24
+ return unless parsed
25
+
26
+ lookup_data = fetch_lookup(parsed)
27
+ enrich_from_lookup(package, lookup_data) if lookup_data
28
+
29
+ advisories = fetch_advisories(parsed)
30
+ enrich_from_advisories(package, advisories) if advisories&.any?
31
+ rescue StandardError => e
32
+ @errors << { purl: purl_string, error: e.message }
33
+ end
34
+
35
+ def self.enrich(sbom)
36
+ new(sbom).enrich
37
+ end
38
+
39
+ def self.enrich_package(package)
40
+ purl_string = package[:purl] || find_purl_in_refs(package)
41
+ return package unless purl_string
42
+
43
+ parsed = Purl.parse(purl_string)
44
+ return package unless parsed
45
+
46
+ lookup_data = parsed.lookup
47
+ apply_lookup_enrichment(package, lookup_data) if lookup_data
48
+
49
+ advisories = parsed.advisories
50
+ apply_advisory_enrichment(package, advisories) if advisories&.any?
51
+
52
+ package
53
+ rescue StandardError
54
+ package
55
+ end
56
+
57
+ private
58
+
59
+ def find_purl_in_external_refs(package)
60
+ refs = package[:external_references] || []
61
+ refs.find { |_, type, _| type == "purl" }&.last
62
+ end
63
+
64
+ def self.find_purl_in_refs(package)
65
+ refs = package[:external_references] || []
66
+ refs.find { |_, type, _| type == "purl" }&.last
67
+ end
68
+
69
+ def parse_purl(purl_string)
70
+ Purl.parse(purl_string)
71
+ rescue Purl::InvalidPackageURL
72
+ @errors << { purl: purl_string, error: "Invalid PURL format" }
73
+ nil
74
+ end
75
+
76
+ def fetch_lookup(parsed_purl)
77
+ parsed_purl.lookup
78
+ rescue StandardError => e
79
+ @errors << { purl: parsed_purl.to_s, error: "Lookup failed: #{e.message}" }
80
+ nil
81
+ end
82
+
83
+ def fetch_advisories(parsed_purl)
84
+ parsed_purl.advisories
85
+ rescue StandardError => e
86
+ @errors << { purl: parsed_purl.to_s, error: "Advisories fetch failed: #{e.message}" }
87
+ []
88
+ end
89
+
90
+ def enrich_from_lookup(package, data)
91
+ self.class.apply_lookup_enrichment(package, data)
92
+ end
93
+
94
+ def enrich_from_advisories(package, advisories)
95
+ self.class.apply_advisory_enrichment(package, advisories)
96
+ end
97
+
98
+ def self.apply_lookup_enrichment(package, data)
99
+ pkg_data = data[:package] || {}
100
+ version_data = data[:version] || {}
101
+
102
+ package[:description] ||= pkg_data[:description]
103
+ package[:homepage] ||= pkg_data[:homepage]
104
+ package[:download_location] ||= version_data[:download_url]
105
+
106
+ if pkg_data[:licenses] && !package[:license_concluded]
107
+ package[:license_concluded] = pkg_data[:licenses]
108
+ end
109
+
110
+ package[:repository_url] ||= pkg_data[:repository_url]
111
+ package[:registry_url] ||= pkg_data[:registry_url]
112
+ package[:documentation_url] ||= pkg_data[:documentation_url]
113
+
114
+ if pkg_data[:maintainers]&.any? && !package[:supplier]
115
+ first_maintainer = pkg_data[:maintainers].first
116
+ package[:supplier] = first_maintainer[:login] if first_maintainer
117
+ package[:supplier_type] = "Organization"
118
+ end
119
+
120
+ if pkg_data[:keywords]&.any?
121
+ package[:tags] ||= []
122
+ package[:tags].concat(pkg_data[:keywords]).uniq!
123
+ end
124
+
125
+ package[:properties] ||= []
126
+ if pkg_data[:latest_version]
127
+ package[:properties] << ["ecosystems:latest_version", pkg_data[:latest_version]]
128
+ end
129
+ if pkg_data[:latest_version_published_at]
130
+ package[:properties] << ["ecosystems:latest_version_published_at", pkg_data[:latest_version_published_at]]
131
+ end
132
+ if pkg_data[:versions_count]
133
+ package[:properties] << ["ecosystems:versions_count", pkg_data[:versions_count].to_s]
134
+ end
135
+ if version_data[:published_at]
136
+ package[:properties] << ["ecosystems:version_published_at", version_data[:published_at]]
137
+ end
138
+
139
+ package
140
+ end
141
+
142
+ def self.apply_advisory_enrichment(package, advisories)
143
+ package[:advisories] ||= []
144
+
145
+ advisories.each do |advisory|
146
+ package[:advisories] << {
147
+ id: advisory[:id],
148
+ title: advisory[:title],
149
+ description: advisory[:description],
150
+ severity: advisory[:severity],
151
+ cvss_score: advisory[:cvss_score],
152
+ url: advisory[:url],
153
+ published_at: advisory[:published_at],
154
+ source: advisory[:source_kind],
155
+ references: advisory[:references]
156
+ }
157
+ end
158
+
159
+ package
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sbom
4
+ class Merger
5
+ attr_reader :sboms, :options, :result
6
+
7
+ def initialize(sboms, dedupe: :purl)
8
+ @sboms = sboms
9
+ @options = { dedupe: dedupe }
10
+ @result = nil
11
+ end
12
+
13
+ def merge
14
+ @result = Data::Sbom.new(sbom_type: determine_sbom_type)
15
+ @result.version = determine_version
16
+
17
+ build_document
18
+ merge_packages
19
+ merge_files
20
+ merge_relationships
21
+ merge_licenses
22
+ merge_annotations
23
+
24
+ @result
25
+ end
26
+
27
+ def determine_sbom_type
28
+ types = @sboms.map(&:sbom_type).uniq
29
+ return types.first if types.size == 1
30
+
31
+ :spdx
32
+ end
33
+
34
+ def determine_version
35
+ versions = @sboms.map(&:version).compact.uniq
36
+ versions.first
37
+ end
38
+
39
+ def build_document
40
+ names = @sboms.map { |s| s.document&.dig(:name) }.compact
41
+ merged_name = names.any? ? "Merged: #{names.join(', ')}" : "Merged SBOM"
42
+
43
+ @result.document = {
44
+ name: merged_name,
45
+ id: "SPDXRef-DOCUMENT",
46
+ created: Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
47
+ }
48
+ end
49
+
50
+ def merge_packages
51
+ seen_purls = {}
52
+
53
+ @sboms.each do |sbom|
54
+ sbom.packages.each do |pkg|
55
+ if @options[:dedupe] == :purl && pkg[:purl]
56
+ next if seen_purls[pkg[:purl]]
57
+
58
+ seen_purls[pkg[:purl]] = true
59
+ end
60
+
61
+ @result.add_package(pkg)
62
+ end
63
+ end
64
+ end
65
+
66
+ def merge_files
67
+ @sboms.each do |sbom|
68
+ sbom.files.each do |file|
69
+ @result.add_file(file)
70
+ end
71
+ end
72
+ end
73
+
74
+ def merge_relationships
75
+ seen = Set.new
76
+
77
+ @sboms.each do |sbom|
78
+ sbom.relationships.each do |rel|
79
+ key = [rel[:source], rel[:type], rel[:target]]
80
+ next if seen.include?(key)
81
+
82
+ seen.add(key)
83
+ @result.add_relationship(rel)
84
+ end
85
+ end
86
+ end
87
+
88
+ def merge_licenses
89
+ seen = Set.new
90
+
91
+ @sboms.each do |sbom|
92
+ sbom.licenses.each do |lic|
93
+ next if seen.include?(lic)
94
+
95
+ seen.add(lic)
96
+ @result.add_license(lic)
97
+ end
98
+ end
99
+ end
100
+
101
+ def merge_annotations
102
+ @sboms.each do |sbom|
103
+ sbom.annotations.each do |ann|
104
+ @result.add_annotation(ann)
105
+ end
106
+ end
107
+ end
108
+
109
+ class << self
110
+ def merge(sboms, dedupe: :purl)
111
+ new(sboms, dedupe: dedupe).merge
112
+ end
113
+
114
+ def merge_files(filenames, dedupe: :purl)
115
+ sboms = filenames.map { |f| Parser.parse_file(f) }
116
+ merge(sboms, dedupe: dedupe)
117
+ end
118
+ end
119
+ end
120
+ end
data/lib/sbom/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sbom
4
- VERSION = "0.1.0"
4
+ VERSION = "0.3.0"
5
5
  end
data/lib/sbom.rb CHANGED
@@ -32,6 +32,8 @@ require_relative "sbom/generator"
32
32
  require_relative "sbom/validation_result"
33
33
  require_relative "sbom/validator"
34
34
  require_relative "sbom/output"
35
+ require_relative "sbom/enricher"
36
+ require_relative "sbom/merger"
35
37
 
36
38
  module Sbom
37
39
  class << self
@@ -50,5 +52,22 @@ module Sbom
50
52
  def validate_file(filename, sbom_type: :auto)
51
53
  Validator.validate_file(filename, sbom_type: sbom_type)
52
54
  end
55
+
56
+ def enrich(sbom)
57
+ Enricher.enrich(sbom)
58
+ end
59
+
60
+ def enrich_file(filename, sbom_type: :auto)
61
+ sbom = parse_file(filename, sbom_type: sbom_type)
62
+ Enricher.enrich(sbom)
63
+ end
64
+
65
+ def merge(sboms, dedupe: :purl)
66
+ Merger.merge(sboms, dedupe: dedupe)
67
+ end
68
+
69
+ def merge_files(filenames, dedupe: :purl)
70
+ Merger.merge_files(filenames, dedupe: dedupe)
71
+ end
53
72
  end
54
73
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sbom
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Nesbitt
@@ -63,8 +63,11 @@ files:
63
63
  - ".gitmodules"
64
64
  - CHANGELOG.md
65
65
  - CODE_OF_CONDUCT.md
66
+ - CONTRIBUTING.md
67
+ - LICENSE
66
68
  - README.md
67
69
  - Rakefile
70
+ - SECURITY.md
68
71
  - exe/sbom
69
72
  - lib/sbom.rb
70
73
  - lib/sbom/cyclonedx/generator.rb
@@ -74,10 +77,12 @@ files:
74
77
  - lib/sbom/data/package.rb
75
78
  - lib/sbom/data/relationship.rb
76
79
  - lib/sbom/data/sbom.rb
80
+ - lib/sbom/enricher.rb
77
81
  - lib/sbom/error.rb
78
82
  - lib/sbom/generator.rb
79
83
  - lib/sbom/license/data/spdx_licenses.json
80
84
  - lib/sbom/license/scanner.rb
85
+ - lib/sbom/merger.rb
81
86
  - lib/sbom/output.rb
82
87
  - lib/sbom/parser.rb
83
88
  - lib/sbom/spdx/generator.rb