gem_guard 0.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 +7 -0
- data/CHANGELOG.md +28 -0
- data/LICENSE.txt +21 -0
- data/README.md +96 -0
- data/Rakefile +8 -0
- data/exe/gem_guard +5 -0
- data/gem_guard.gemspec +35 -0
- data/lib/gem_guard/analyzer.rb +83 -0
- data/lib/gem_guard/cli.rb +30 -0
- data/lib/gem_guard/parser.rb +50 -0
- data/lib/gem_guard/reporter.rb +77 -0
- data/lib/gem_guard/version.rb +3 -0
- data/lib/gem_guard/vulnerability_fetcher.rb +123 -0
- data/lib/gem_guard.rb +10 -0
- data/plan.md +167 -0
- metadata +130 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: a7210ff3d709b761cf8aa01e0c5d770f61b2801d9a9603e2608451f782aa23fb
|
4
|
+
data.tar.gz: c59ee85d130fca43c719d3bcff7a136364e65ec37255af0b2721e6736dc2931e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ff65c5924a28cc7193569c7b50ac84234aa897d139d2d177eb3f8538a77ba627492458e40205bfb40c5845e16ecefa84a902912933f131735b1ff74d3171500e
|
7
|
+
data.tar.gz: 58228702c7dc55e0594f515aab361cc63afbf6840fc25657f82cdeda252078e90ab5322a1dd5b56b1861f5352c1d7d527ba5fd01b464ac52ba1b92c3e4ca8033
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# Changelog
|
2
|
+
|
3
|
+
All notable changes to this project will be documented in this file.
|
4
|
+
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
7
|
+
|
8
|
+
## [Unreleased]
|
9
|
+
|
10
|
+
## [0.1.0] - 2025-08-08
|
11
|
+
|
12
|
+
### Added
|
13
|
+
- Initial release of GemGuard
|
14
|
+
- Core vulnerability scanning functionality
|
15
|
+
- Support for OSV.dev vulnerability database
|
16
|
+
- CLI interface with `scan` and `version` commands
|
17
|
+
- Table and JSON output formats
|
18
|
+
- Comprehensive test suite with RSpec
|
19
|
+
- Integration with Bundler for Gemfile.lock parsing
|
20
|
+
- Fix recommendations for vulnerable dependencies
|
21
|
+
|
22
|
+
### Features
|
23
|
+
- Parse Gemfile.lock and build dependency graph
|
24
|
+
- Fetch vulnerabilities from OSV.dev API
|
25
|
+
- Match dependencies against known vulnerabilities
|
26
|
+
- Generate human-readable and JSON reports
|
27
|
+
- Exit with non-zero status when vulnerabilities found
|
28
|
+
- Support for custom lockfile paths
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2025 Wilbur Suero
|
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
|
13
|
+
all 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
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
# GemGuard
|
2
|
+
|
3
|
+
[](https://badge.fury.io/rb/gem_guard)
|
4
|
+
[](https://github.com/wilbursuero/gem_guard/actions)
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
6
|
+
|
7
|
+
Supply chain security and vulnerability management for Ruby gems. GemGuard provides developers with a comprehensive tool to detect, report, and remediate dependency-related security risks.
|
8
|
+
|
9
|
+
## Features
|
10
|
+
|
11
|
+
- 🔍 **Vulnerability Scanning**: Detect known CVEs in your dependencies
|
12
|
+
- 📊 **Multiple Output Formats**: Human-readable tables and JSON output
|
13
|
+
- 🌐 **Multiple Data Sources**: OSV.dev and Ruby Advisory Database
|
14
|
+
- 🔧 **Fix Recommendations**: Suggested commands to remediate vulnerabilities
|
15
|
+
- 🚀 **CI/CD Ready**: Exit codes for pipeline integration
|
16
|
+
|
17
|
+
## Installation
|
18
|
+
|
19
|
+
Add this line to your application's Gemfile:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
gem 'gem_guard'
|
23
|
+
```
|
24
|
+
|
25
|
+
And then execute:
|
26
|
+
|
27
|
+
$ bundle install
|
28
|
+
|
29
|
+
Or install it yourself as:
|
30
|
+
|
31
|
+
$ gem install gem_guard
|
32
|
+
|
33
|
+
## Usage
|
34
|
+
|
35
|
+
### Basic Vulnerability Scan
|
36
|
+
|
37
|
+
Scan your project's dependencies for known vulnerabilities:
|
38
|
+
|
39
|
+
```bash
|
40
|
+
gem_guard scan
|
41
|
+
```
|
42
|
+
|
43
|
+
### Specify Custom Lockfile
|
44
|
+
|
45
|
+
```bash
|
46
|
+
gem_guard scan --lockfile path/to/Gemfile.lock
|
47
|
+
```
|
48
|
+
|
49
|
+
### JSON Output
|
50
|
+
|
51
|
+
```bash
|
52
|
+
gem_guard scan --format json
|
53
|
+
```
|
54
|
+
|
55
|
+
### Example Output
|
56
|
+
|
57
|
+
```
|
58
|
+
🚨 Security Vulnerabilities Found
|
59
|
+
==================================================
|
60
|
+
|
61
|
+
Summary:
|
62
|
+
Total vulnerabilities: 2
|
63
|
+
High/Critical severity: 1
|
64
|
+
|
65
|
+
Details:
|
66
|
+
|
67
|
+
📦 actionpack (6.1.0)
|
68
|
+
🔍 Vulnerability: CVE-2021-22885
|
69
|
+
⚠️ Severity: HIGH
|
70
|
+
📝 Summary: Possible Information Disclosure / Unintended Method Execution in Action Pack
|
71
|
+
🔧 Fix: bundle update actionpack --to 6.1.3.1
|
72
|
+
|
73
|
+
📦 nokogiri (1.10.0)
|
74
|
+
🔍 Vulnerability: CVE-2020-26247
|
75
|
+
⚠️ Severity: MEDIUM
|
76
|
+
📝 Summary: XML External Entity vulnerability in Nokogiri
|
77
|
+
🔧 Fix: bundle update nokogiri --to 1.11.0
|
78
|
+
```
|
79
|
+
|
80
|
+
## Development
|
81
|
+
|
82
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
83
|
+
|
84
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
85
|
+
|
86
|
+
## Contributing
|
87
|
+
|
88
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/wilbursuero/gem_guard.
|
89
|
+
|
90
|
+
## License
|
91
|
+
|
92
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
93
|
+
|
94
|
+
## Security
|
95
|
+
|
96
|
+
If you discover a security vulnerability within GemGuard, please send an email to security@example.com. All security vulnerabilities will be promptly addressed.
|
data/Rakefile
ADDED
data/exe/gem_guard
ADDED
data/gem_guard.gemspec
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require_relative "lib/gem_guard/version"
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "gem_guard"
|
5
|
+
spec.version = GemGuard::VERSION
|
6
|
+
spec.authors = ["Wilbur Suero"]
|
7
|
+
spec.email = ["wilbur@example.com"]
|
8
|
+
|
9
|
+
spec.summary = "Supply chain security and vulnerability management for Ruby gems"
|
10
|
+
spec.description = "A comprehensive tool to detect, report, and remediate dependency-related security risks in Ruby projects. Includes CVE scanning, SBOM generation, and CI/CD integration."
|
11
|
+
spec.homepage = "https://github.com/wilbursuero/gem_guard"
|
12
|
+
spec.license = "MIT"
|
13
|
+
spec.required_ruby_version = ">= 3.0.0"
|
14
|
+
|
15
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
16
|
+
spec.metadata["source_code_uri"] = "https://github.com/wilbursuero/gem_guard"
|
17
|
+
spec.metadata["changelog_uri"] = "https://github.com/wilbursuero/gem_guard/blob/main/CHANGELOG.md"
|
18
|
+
|
19
|
+
spec.files = Dir.chdir(__dir__) do
|
20
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
21
|
+
(File.expand_path(f) == __FILE__) ||
|
22
|
+
f.start_with?(*%w[bin/ test/ spec/ features/ .git .github appveyor Gemfile])
|
23
|
+
end
|
24
|
+
end
|
25
|
+
spec.bindir = "exe"
|
26
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
27
|
+
spec.require_paths = ["lib"]
|
28
|
+
|
29
|
+
spec.add_dependency "bundler", ">= 2.0"
|
30
|
+
spec.add_dependency "thor", "~> 1.0"
|
31
|
+
spec.add_dependency "json", "~> 2.0"
|
32
|
+
|
33
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
34
|
+
spec.add_development_dependency "standard", "~> 1.3"
|
35
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module GemGuard
|
2
|
+
class Analyzer
|
3
|
+
def analyze(dependencies, vulnerabilities)
|
4
|
+
vulnerable_dependencies = []
|
5
|
+
|
6
|
+
dependencies.each do |dependency|
|
7
|
+
matching_vulns = vulnerabilities.select { |vuln| vuln.gem_name == dependency.name }
|
8
|
+
|
9
|
+
next if matching_vulns.empty?
|
10
|
+
|
11
|
+
matching_vulns.each do |vulnerability|
|
12
|
+
if version_affected?(dependency.version, vulnerability)
|
13
|
+
vulnerable_dependencies << VulnerableDependency.new(
|
14
|
+
dependency: dependency,
|
15
|
+
vulnerability: vulnerability,
|
16
|
+
recommended_fix: suggest_fix(dependency, vulnerability)
|
17
|
+
)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
Analysis.new(vulnerable_dependencies)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def version_affected?(version, vulnerability)
|
28
|
+
# Simple version check - in a real implementation, this would be more sophisticated
|
29
|
+
# For now, assume all versions are affected if vulnerability exists
|
30
|
+
true
|
31
|
+
end
|
32
|
+
|
33
|
+
def suggest_fix(dependency, vulnerability)
|
34
|
+
if vulnerability.fixed_versions.any?
|
35
|
+
latest_fix = vulnerability.fixed_versions.last
|
36
|
+
"bundle update #{dependency.name} --to #{latest_fix}"
|
37
|
+
else
|
38
|
+
"bundle update #{dependency.name}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class Analysis
|
44
|
+
attr_reader :vulnerable_dependencies
|
45
|
+
|
46
|
+
def initialize(vulnerable_dependencies)
|
47
|
+
@vulnerable_dependencies = vulnerable_dependencies
|
48
|
+
end
|
49
|
+
|
50
|
+
def has_vulnerabilities?
|
51
|
+
vulnerable_dependencies.any?
|
52
|
+
end
|
53
|
+
|
54
|
+
def vulnerability_count
|
55
|
+
vulnerable_dependencies.length
|
56
|
+
end
|
57
|
+
|
58
|
+
def high_severity_count
|
59
|
+
vulnerable_dependencies.count { |vd| high_severity?(vd.vulnerability.severity) }
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def high_severity?(severity)
|
65
|
+
case severity.to_s.upcase
|
66
|
+
when /HIGH|CRITICAL/
|
67
|
+
true
|
68
|
+
else
|
69
|
+
false
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
class VulnerableDependency
|
75
|
+
attr_reader :dependency, :vulnerability, :recommended_fix
|
76
|
+
|
77
|
+
def initialize(dependency:, vulnerability:, recommended_fix:)
|
78
|
+
@dependency = dependency
|
79
|
+
@vulnerability = vulnerability
|
80
|
+
@recommended_fix = recommended_fix
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require "thor"
|
2
|
+
|
3
|
+
module GemGuard
|
4
|
+
class CLI < Thor
|
5
|
+
desc "scan", "Scan dependencies for known vulnerabilities"
|
6
|
+
option :format, type: :string, default: "table", desc: "Output format (table, json)"
|
7
|
+
option :lockfile, type: :string, default: "Gemfile.lock", desc: "Path to Gemfile.lock"
|
8
|
+
def scan
|
9
|
+
lockfile_path = options[:lockfile]
|
10
|
+
|
11
|
+
unless File.exist?(lockfile_path)
|
12
|
+
puts "Error: #{lockfile_path} not found"
|
13
|
+
exit 1
|
14
|
+
end
|
15
|
+
|
16
|
+
dependencies = Parser.new.parse(lockfile_path)
|
17
|
+
vulnerabilities = VulnerabilityFetcher.new.fetch_for(dependencies)
|
18
|
+
analysis = Analyzer.new.analyze(dependencies, vulnerabilities)
|
19
|
+
|
20
|
+
Reporter.new.report(analysis, format: options[:format])
|
21
|
+
|
22
|
+
exit 1 if analysis.has_vulnerabilities?
|
23
|
+
end
|
24
|
+
|
25
|
+
desc "version", "Show gem_guard version"
|
26
|
+
def version
|
27
|
+
puts GemGuard::VERSION
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require "bundler"
|
2
|
+
|
3
|
+
module GemGuard
|
4
|
+
class Parser
|
5
|
+
def parse(lockfile_path)
|
6
|
+
lockfile = Bundler::LockfileParser.new(File.read(lockfile_path))
|
7
|
+
|
8
|
+
dependencies = []
|
9
|
+
|
10
|
+
lockfile.specs.each do |spec|
|
11
|
+
dependencies << Dependency.new(
|
12
|
+
name: spec.name,
|
13
|
+
version: spec.version.to_s,
|
14
|
+
source: extract_source(spec),
|
15
|
+
dependencies: spec.dependencies.map(&:name)
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
dependencies
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def extract_source(spec)
|
25
|
+
if spec.source.respond_to?(:uri)
|
26
|
+
spec.source.uri.to_s
|
27
|
+
else
|
28
|
+
"https://rubygems.org"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Dependency
|
34
|
+
attr_reader :name, :version, :source, :dependencies
|
35
|
+
|
36
|
+
def initialize(name:, version:, source:, dependencies: [])
|
37
|
+
@name = name
|
38
|
+
@version = version
|
39
|
+
@source = source
|
40
|
+
@dependencies = dependencies
|
41
|
+
end
|
42
|
+
|
43
|
+
def ==(other)
|
44
|
+
other.is_a?(Dependency) &&
|
45
|
+
name == other.name &&
|
46
|
+
version == other.version &&
|
47
|
+
source == other.source
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require "json"
|
2
|
+
|
3
|
+
module GemGuard
|
4
|
+
class Reporter
|
5
|
+
def report(analysis, format: "table")
|
6
|
+
case format.downcase
|
7
|
+
when "json"
|
8
|
+
puts generate_json_report(analysis)
|
9
|
+
when "table"
|
10
|
+
puts generate_table_report(analysis)
|
11
|
+
else
|
12
|
+
puts "Unknown format: #{format}. Supported formats: table, json"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def generate_table_report(analysis)
|
19
|
+
return "✅ No vulnerabilities found!" unless analysis.has_vulnerabilities?
|
20
|
+
|
21
|
+
report = []
|
22
|
+
report << "🚨 Security Vulnerabilities Found"
|
23
|
+
report << "=" * 50
|
24
|
+
report << ""
|
25
|
+
report << "Summary:"
|
26
|
+
report << " Total vulnerabilities: #{analysis.vulnerability_count}"
|
27
|
+
report << " High/Critical severity: #{analysis.high_severity_count}"
|
28
|
+
report << ""
|
29
|
+
report << "Details:"
|
30
|
+
report << ""
|
31
|
+
|
32
|
+
analysis.vulnerable_dependencies.each do |vuln_dep|
|
33
|
+
dep = vuln_dep.dependency
|
34
|
+
vuln = vuln_dep.vulnerability
|
35
|
+
|
36
|
+
report << "📦 #{dep.name} (#{dep.version})"
|
37
|
+
report << " 🔍 Vulnerability: #{vuln.id}"
|
38
|
+
report << " ⚠️ Severity: #{vuln.severity}"
|
39
|
+
report << " 📝 Summary: #{vuln.summary}" unless vuln.summary.empty?
|
40
|
+
report << " 🔧 Fix: #{vuln_dep.recommended_fix}"
|
41
|
+
report << ""
|
42
|
+
end
|
43
|
+
|
44
|
+
report.join("\n")
|
45
|
+
end
|
46
|
+
|
47
|
+
def generate_json_report(analysis)
|
48
|
+
report_data = {
|
49
|
+
summary: {
|
50
|
+
total_vulnerabilities: analysis.vulnerability_count,
|
51
|
+
high_severity_count: analysis.high_severity_count,
|
52
|
+
has_vulnerabilities: analysis.has_vulnerabilities?
|
53
|
+
},
|
54
|
+
vulnerabilities: analysis.vulnerable_dependencies.map do |vuln_dep|
|
55
|
+
{
|
56
|
+
gem: {
|
57
|
+
name: vuln_dep.dependency.name,
|
58
|
+
version: vuln_dep.dependency.version,
|
59
|
+
source: vuln_dep.dependency.source
|
60
|
+
},
|
61
|
+
vulnerability: {
|
62
|
+
id: vuln_dep.vulnerability.id,
|
63
|
+
severity: vuln_dep.vulnerability.severity,
|
64
|
+
summary: vuln_dep.vulnerability.summary,
|
65
|
+
details: vuln_dep.vulnerability.details,
|
66
|
+
affected_versions: vuln_dep.vulnerability.affected_versions,
|
67
|
+
fixed_versions: vuln_dep.vulnerability.fixed_versions
|
68
|
+
},
|
69
|
+
recommended_fix: vuln_dep.recommended_fix
|
70
|
+
}
|
71
|
+
end
|
72
|
+
}
|
73
|
+
|
74
|
+
JSON.pretty_generate(report_data)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
require "net/http"
|
2
|
+
require "json"
|
3
|
+
require "uri"
|
4
|
+
|
5
|
+
module GemGuard
|
6
|
+
class VulnerabilityFetcher
|
7
|
+
OSV_API_URL = "https://api.osv.dev/v1/query"
|
8
|
+
RUBY_ADVISORY_DB_URL = "https://raw.githubusercontent.com/rubysec/ruby-advisory-db/master/gems"
|
9
|
+
|
10
|
+
def fetch_for(dependencies)
|
11
|
+
vulnerabilities = []
|
12
|
+
|
13
|
+
dependencies.each do |dependency|
|
14
|
+
vulnerabilities.concat(fetch_osv_vulnerabilities(dependency))
|
15
|
+
vulnerabilities.concat(fetch_ruby_advisory_vulnerabilities(dependency))
|
16
|
+
end
|
17
|
+
|
18
|
+
vulnerabilities.uniq { |vuln| vuln.id }
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def fetch_osv_vulnerabilities(dependency)
|
24
|
+
query = {
|
25
|
+
package: {
|
26
|
+
name: dependency.name,
|
27
|
+
ecosystem: "RubyGems"
|
28
|
+
},
|
29
|
+
version: dependency.version
|
30
|
+
}
|
31
|
+
|
32
|
+
response = make_http_request(OSV_API_URL, query.to_json)
|
33
|
+
return [] unless response
|
34
|
+
|
35
|
+
data = JSON.parse(response)
|
36
|
+
return [] unless data["vulns"]
|
37
|
+
|
38
|
+
data["vulns"].map do |vuln_data|
|
39
|
+
Vulnerability.new(
|
40
|
+
id: vuln_data["id"],
|
41
|
+
gem_name: dependency.name,
|
42
|
+
affected_versions: extract_affected_versions(vuln_data),
|
43
|
+
fixed_versions: extract_fixed_versions(vuln_data),
|
44
|
+
severity: extract_severity(vuln_data),
|
45
|
+
summary: vuln_data["summary"],
|
46
|
+
details: vuln_data["details"]
|
47
|
+
)
|
48
|
+
end
|
49
|
+
rescue JSON::ParserError
|
50
|
+
[]
|
51
|
+
end
|
52
|
+
|
53
|
+
def fetch_ruby_advisory_vulnerabilities(dependency)
|
54
|
+
# For now, return empty array - will implement Ruby Advisory DB fetching later
|
55
|
+
[]
|
56
|
+
end
|
57
|
+
|
58
|
+
def make_http_request(url, body = nil)
|
59
|
+
uri = URI(url)
|
60
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
61
|
+
http.use_ssl = true if uri.scheme == "https"
|
62
|
+
|
63
|
+
request = if body
|
64
|
+
req = Net::HTTP::Post.new(uri)
|
65
|
+
req["Content-Type"] = "application/json"
|
66
|
+
req.body = body
|
67
|
+
req
|
68
|
+
else
|
69
|
+
Net::HTTP::Get.new(uri)
|
70
|
+
end
|
71
|
+
|
72
|
+
response = http.request(request)
|
73
|
+
response.body if response.code == "200"
|
74
|
+
rescue
|
75
|
+
nil
|
76
|
+
end
|
77
|
+
|
78
|
+
def extract_affected_versions(vuln_data)
|
79
|
+
return [] unless vuln_data["affected"]
|
80
|
+
|
81
|
+
vuln_data["affected"]
|
82
|
+
.select { |affected| affected.dig("package", "ecosystem") == "RubyGems" }
|
83
|
+
.flat_map { |affected| affected["ranges"] || [] }
|
84
|
+
.flat_map { |range| range["events"] || [] }
|
85
|
+
.map { |event| event["introduced"] || event["fixed"] }
|
86
|
+
.compact
|
87
|
+
end
|
88
|
+
|
89
|
+
def extract_fixed_versions(vuln_data)
|
90
|
+
return [] unless vuln_data["affected"]
|
91
|
+
|
92
|
+
vuln_data["affected"]
|
93
|
+
.select { |affected| affected.dig("package", "ecosystem") == "RubyGems" }
|
94
|
+
.flat_map { |affected| affected["ranges"] || [] }
|
95
|
+
.flat_map { |range| range["events"] || [] }
|
96
|
+
.filter_map { |event| event["fixed"] }
|
97
|
+
end
|
98
|
+
|
99
|
+
def extract_severity(vuln_data)
|
100
|
+
return "UNKNOWN" unless vuln_data["severity"]
|
101
|
+
|
102
|
+
vuln_data["severity"].first&.dig("score") || "UNKNOWN"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
class Vulnerability
|
107
|
+
attr_reader :id, :gem_name, :affected_versions, :fixed_versions, :severity, :summary, :details
|
108
|
+
|
109
|
+
def initialize(id:, gem_name:, affected_versions: [], fixed_versions: [], severity: "UNKNOWN", summary: "", details: "")
|
110
|
+
@id = id
|
111
|
+
@gem_name = gem_name
|
112
|
+
@affected_versions = affected_versions
|
113
|
+
@fixed_versions = fixed_versions
|
114
|
+
@severity = severity
|
115
|
+
@summary = summary
|
116
|
+
@details = details
|
117
|
+
end
|
118
|
+
|
119
|
+
def ==(other)
|
120
|
+
other.is_a?(Vulnerability) && id == other.id
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
data/lib/gem_guard.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
require_relative "gem_guard/version"
|
2
|
+
require_relative "gem_guard/cli"
|
3
|
+
require_relative "gem_guard/parser"
|
4
|
+
require_relative "gem_guard/vulnerability_fetcher"
|
5
|
+
require_relative "gem_guard/analyzer"
|
6
|
+
require_relative "gem_guard/reporter"
|
7
|
+
|
8
|
+
module GemGuard
|
9
|
+
class Error < StandardError; end
|
10
|
+
end
|
data/plan.md
ADDED
@@ -0,0 +1,167 @@
|
|
1
|
+
# Supply Chain Security & Vulnerability Management Gem – Plan
|
2
|
+
|
3
|
+
## 1. Overview
|
4
|
+
|
5
|
+
**Working Name:** `gem_guard`
|
6
|
+
**Goal:** Provide Ruby developers with a one-stop tool to detect, report, and remediate dependency-related security risks.
|
7
|
+
**Core Capabilities:**
|
8
|
+
- Scan dependency tree (including transient deps)
|
9
|
+
- Detect known CVEs from public and private vulnerability databases
|
10
|
+
- Suggest safe upgrades and patches
|
11
|
+
- Generate SBOM (Software Bill of Materials) in SPDX/CycloneDX format
|
12
|
+
- Integrate with CI/CD to prevent unsafe deployments
|
13
|
+
|
14
|
+
---
|
15
|
+
|
16
|
+
## 2. Problems Being Solved
|
17
|
+
|
18
|
+
1. **Typosquatting & brand-jacking detection** – accidental installs of malicious gems with similar names.
|
19
|
+
2. **Unpatched dependencies** – gems with known vulnerabilities not updated.
|
20
|
+
3. **Lack of visibility** – no SBOM or complete dependency inventory.
|
21
|
+
4. **CI/CD security gap** – insecure builds proceed unnoticed.
|
22
|
+
|
23
|
+
---
|
24
|
+
|
25
|
+
## 3. Target Users
|
26
|
+
|
27
|
+
- Ruby and Rails developers
|
28
|
+
- DevOps engineers managing Ruby apps in production
|
29
|
+
- Security-conscious teams using Ruby for internal tooling
|
30
|
+
|
31
|
+
---
|
32
|
+
|
33
|
+
## 4. Features & Requirements
|
34
|
+
|
35
|
+
### Phase 1 – Core CLI Scanner
|
36
|
+
- Command: `gem_guard scan`
|
37
|
+
- Parse `Gemfile.lock` and detect:
|
38
|
+
- Direct & transitive dependencies
|
39
|
+
- Gem source URLs
|
40
|
+
- Query vulnerability sources:
|
41
|
+
- [OSV.dev](https://osv.dev)
|
42
|
+
- [Ruby Advisory Database](https://github.com/rubysec/ruby-advisory-db)
|
43
|
+
- Output:
|
44
|
+
- Table of vulnerable gems, CVE IDs, severity, fixed versions
|
45
|
+
- Recommended fix commands (e.g., `bundle update <gem>`)
|
46
|
+
|
47
|
+
### Phase 2 – SBOM Generation
|
48
|
+
- Command: `gem_guard sbom`
|
49
|
+
- Output formats:
|
50
|
+
- SPDX JSON
|
51
|
+
- CycloneDX JSON
|
52
|
+
- Include metadata:
|
53
|
+
- Gem name, version, source URL, license, checksum
|
54
|
+
|
55
|
+
### Phase 3 – CI/CD Integration
|
56
|
+
- Exit with non-zero status if vulnerabilities above a severity threshold are found
|
57
|
+
- Optional GitHub Action and GitLab CI template
|
58
|
+
- Config file `.gem_guard.yml` to set:
|
59
|
+
- Allowed severity levels
|
60
|
+
- Ignored CVEs
|
61
|
+
- Output format
|
62
|
+
|
63
|
+
### Phase 4 – Typosquat Detection
|
64
|
+
- Fuzzy matching gem names against known gems in RubyGems API
|
65
|
+
- Flag suspicious dependencies
|
66
|
+
|
67
|
+
### Phase 5 – Auto-Fix Mode
|
68
|
+
- Command: `gem_guard fix`
|
69
|
+
- Automatically updates vulnerable gems within safe version constraints
|
70
|
+
|
71
|
+
---
|
72
|
+
|
73
|
+
## 5. Architecture
|
74
|
+
|
75
|
+
### Modules
|
76
|
+
1. **Parser**
|
77
|
+
- Reads `Gemfile.lock`
|
78
|
+
- Builds dependency graph
|
79
|
+
2. **VulnerabilityFetcher**
|
80
|
+
- Fetches advisories from APIs or local DB
|
81
|
+
3. **Analyzer**
|
82
|
+
- Matches dependencies with advisories
|
83
|
+
- Assesses severity and suggests fixes
|
84
|
+
4. **Reporter**
|
85
|
+
- Formats output (table, JSON, markdown, SBOM)
|
86
|
+
5. **CIAdapter**
|
87
|
+
- Reads config
|
88
|
+
- Sets exit codes for pipelines
|
89
|
+
6. **TyposquatChecker**
|
90
|
+
- Fuzzy matches gem names
|
91
|
+
7. **Updater**
|
92
|
+
- Runs safe updates for vulnerable gems
|
93
|
+
|
94
|
+
---
|
95
|
+
|
96
|
+
## 6. Implementation Stack
|
97
|
+
|
98
|
+
- **Language:** Ruby (≥ 3.0)
|
99
|
+
- **Key Libraries:**
|
100
|
+
- `bundler` – parsing Gemfile.lock
|
101
|
+
- `json` / `oj` – output formatting
|
102
|
+
- `net/http` or `httpx` – API calls
|
103
|
+
- `thor` – CLI interface
|
104
|
+
- `fuzzy_match` – typosquat detection
|
105
|
+
- **Test Framework:** RSpec
|
106
|
+
- **Static Analysis:** RuboCop
|
107
|
+
|
108
|
+
---
|
109
|
+
|
110
|
+
## 7. Development Roadmap
|
111
|
+
|
112
|
+
### Milestone 1 – MVP Scanner
|
113
|
+
- Parse Gemfile.lock
|
114
|
+
- Fetch & match CVEs
|
115
|
+
- CLI with human-readable output
|
116
|
+
- Tests + RuboCop
|
117
|
+
|
118
|
+
### Milestone 2 – SBOM Output
|
119
|
+
- Generate SPDX and CycloneDX JSON
|
120
|
+
- CLI flags for format selection
|
121
|
+
|
122
|
+
### Milestone 3 – CI/CD Integration
|
123
|
+
- Config file support
|
124
|
+
- Exit codes for severity thresholds
|
125
|
+
- GitHub Action template
|
126
|
+
|
127
|
+
### Milestone 4 – Typosquat Detection
|
128
|
+
- Implement fuzzy match against RubyGems API
|
129
|
+
- Add to scan output
|
130
|
+
|
131
|
+
### Milestone 5 – Auto-Fix Mode
|
132
|
+
- Implement safe dependency update logic
|
133
|
+
|
134
|
+
---
|
135
|
+
|
136
|
+
## 8. Distribution & Adoption
|
137
|
+
|
138
|
+
- Publish to RubyGems.org
|
139
|
+
- Create GitHub repo with:
|
140
|
+
- Badges (Gem Version, Build Status, License)
|
141
|
+
- README with quickstart and examples
|
142
|
+
- Security policy
|
143
|
+
- Write blog post on Ruby security gaps
|
144
|
+
- Submit to Ruby Weekly
|
145
|
+
- Post to dev.to and Hacker News for feedback
|
146
|
+
|
147
|
+
---
|
148
|
+
|
149
|
+
## 9. License
|
150
|
+
|
151
|
+
MIT or Apache 2.0 (lean towards MIT for broad adoption)
|
152
|
+
|
153
|
+
---
|
154
|
+
|
155
|
+
## 10. Risks & Mitigation
|
156
|
+
|
157
|
+
- **API rate limits** – cache advisories locally
|
158
|
+
- **False positives** – allow ignore list in config
|
159
|
+
- **Slow scans** – async fetching with caching
|
160
|
+
|
161
|
+
---
|
162
|
+
|
163
|
+
## 11. Success Criteria
|
164
|
+
|
165
|
+
- MVP used in CI by at least 10 open source projects within 3 months
|
166
|
+
- Detects >95% of known vulnerabilities from Ruby Advisory DB
|
167
|
+
- SBOM passes validation in major tools (e.g., CycloneDX CLI)
|
metadata
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gem_guard
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Wilbur Suero
|
8
|
+
bindir: exe
|
9
|
+
cert_chain: []
|
10
|
+
date: 2025-08-09 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: bundler
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - ">="
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '2.0'
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - ">="
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '2.0'
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: thor
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - "~>"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '1.0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '1.0'
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: json
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '2.0'
|
47
|
+
type: :runtime
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '2.0'
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: rspec
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '3.0'
|
61
|
+
type: :development
|
62
|
+
prerelease: false
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '3.0'
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
name: standard
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '1.3'
|
75
|
+
type: :development
|
76
|
+
prerelease: false
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '1.3'
|
82
|
+
description: A comprehensive tool to detect, report, and remediate dependency-related
|
83
|
+
security risks in Ruby projects. Includes CVE scanning, SBOM generation, and CI/CD
|
84
|
+
integration.
|
85
|
+
email:
|
86
|
+
- wilbur@example.com
|
87
|
+
executables:
|
88
|
+
- gem_guard
|
89
|
+
extensions: []
|
90
|
+
extra_rdoc_files: []
|
91
|
+
files:
|
92
|
+
- CHANGELOG.md
|
93
|
+
- LICENSE.txt
|
94
|
+
- README.md
|
95
|
+
- Rakefile
|
96
|
+
- exe/gem_guard
|
97
|
+
- gem_guard.gemspec
|
98
|
+
- lib/gem_guard.rb
|
99
|
+
- lib/gem_guard/analyzer.rb
|
100
|
+
- lib/gem_guard/cli.rb
|
101
|
+
- lib/gem_guard/parser.rb
|
102
|
+
- lib/gem_guard/reporter.rb
|
103
|
+
- lib/gem_guard/version.rb
|
104
|
+
- lib/gem_guard/vulnerability_fetcher.rb
|
105
|
+
- plan.md
|
106
|
+
homepage: https://github.com/wilbursuero/gem_guard
|
107
|
+
licenses:
|
108
|
+
- MIT
|
109
|
+
metadata:
|
110
|
+
homepage_uri: https://github.com/wilbursuero/gem_guard
|
111
|
+
source_code_uri: https://github.com/wilbursuero/gem_guard
|
112
|
+
changelog_uri: https://github.com/wilbursuero/gem_guard/blob/main/CHANGELOG.md
|
113
|
+
rdoc_options: []
|
114
|
+
require_paths:
|
115
|
+
- lib
|
116
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
117
|
+
requirements:
|
118
|
+
- - ">="
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: 3.0.0
|
121
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - ">="
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
requirements: []
|
127
|
+
rubygems_version: 3.6.2
|
128
|
+
specification_version: 4
|
129
|
+
summary: Supply chain security and vulnerability management for Ruby gems
|
130
|
+
test_files: []
|