gem_guard 0.1.10 → 1.1.2

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: 0370d291f988c0082519f85fdfaffc6fd2acfad9150f91e1c0c66e4d913fd2c0
4
- data.tar.gz: 81378c1f38167cccd7ff0ed7f6fb8399556afe40bb29421d2d50601e5aafed91
3
+ metadata.gz: 2870725c07a4b4c39c53e01c031663a934db6f9e457ad75f509d2ee6e3e35684
4
+ data.tar.gz: 6f630c2ef6939a0c5de8c3b8982781a907065b8c1a3901483f1101910387e244
5
5
  SHA512:
6
- metadata.gz: f07731cbc1d4b3dffe6494a4749dfac41cd31849df39e5e712b3735322135131a59f8dd06aaa790c39104d632637d050e3a25e58a9b92c9441accde55a90126e
7
- data.tar.gz: 5906908cb03aac3227f78b16cc49b5e8a7b78756361b809840c1b8de9a5dfb3bc5d138954a3703f051e2ee62e8eaea11cb047e1073882c5b2bf3f2715a36468e
6
+ metadata.gz: 6b7c598cd6789dfdf5d3b90c82fc9f2ade69c730418f1d2a9f6ba6c22555fad01b7e4bbd438bbb03d6116f429176a407e9b0f13221c47fd2cd0e9e2175563e98
7
+ data.tar.gz: 822e03ed7fa56e17d170abf92db8677fb27f92ff04d0f52ecdc1793c73db9aab3052e37e6cbcb28b7a217f3a30a7fb062a3860169f908067b2c3672964d9f977
@@ -0,0 +1,162 @@
1
+ require "bundler"
2
+ require "fileutils"
3
+
4
+ module GemGuard
5
+ class AutoFixer
6
+ def initialize(lockfile_path = "Gemfile.lock", gemfile_path = "Gemfile")
7
+ @lockfile_path = lockfile_path
8
+ @gemfile_path = gemfile_path
9
+ @backup_created = false
10
+ end
11
+
12
+ def fix_vulnerabilities(vulnerable_dependencies, options = {})
13
+ dry_run = options.fetch(:dry_run, false)
14
+ interactive = options.fetch(:interactive, false)
15
+ create_backup = options.fetch(:backup, true)
16
+
17
+ unless File.exist?(@gemfile_path)
18
+ raise "Gemfile not found at #{@gemfile_path}. Auto-fix requires a Gemfile."
19
+ end
20
+
21
+ unless File.exist?(@lockfile_path)
22
+ raise "Gemfile.lock not found at #{@lockfile_path}. Run 'bundle install' first."
23
+ end
24
+
25
+ fixes = plan_fixes(vulnerable_dependencies)
26
+
27
+ if fixes.empty?
28
+ return {status: :no_fixes_needed, message: "No automatic fixes available."}
29
+ end
30
+
31
+ if dry_run
32
+ return {status: :dry_run, fixes: fixes, message: "Dry run completed. #{fixes.length} fixes planned."}
33
+ end
34
+
35
+ if interactive && !confirm_fixes(fixes)
36
+ return {status: :cancelled, message: "Fix operation cancelled by user."}
37
+ end
38
+
39
+ create_lockfile_backup if create_backup
40
+
41
+ applied_fixes = apply_fixes(fixes)
42
+
43
+ {
44
+ status: :completed,
45
+ fixes: applied_fixes,
46
+ message: "Applied #{applied_fixes.length} fixes successfully."
47
+ }
48
+ end
49
+
50
+ private
51
+
52
+ def plan_fixes(vulnerable_dependencies)
53
+ fixes = []
54
+
55
+ vulnerable_dependencies.each do |vuln_dep|
56
+ dependency = vuln_dep.dependency
57
+ vulnerability = vuln_dep.vulnerability
58
+
59
+ # Extract the recommended version from the fix suggestion
60
+ recommended_version = extract_version_from_fix(vuln_dep.recommended_fix)
61
+
62
+ next unless recommended_version
63
+
64
+ # Check if the recommended version is available and safe
65
+ if version_available_and_safe?(dependency.name, recommended_version)
66
+ fixes << {
67
+ gem_name: dependency.name,
68
+ current_version: dependency.version,
69
+ target_version: recommended_version,
70
+ vulnerability_id: vulnerability.id,
71
+ severity: vulnerability.severity
72
+ }
73
+ end
74
+ end
75
+
76
+ fixes
77
+ end
78
+
79
+ def extract_version_from_fix(fix_command)
80
+ # Extract version from commands like "bundle update nokogiri --to 1.18.9"
81
+ match = fix_command.match(/--to\s+([^\s]+)/)
82
+ match ? match[1] : nil
83
+ end
84
+
85
+ def version_available_and_safe?(gem_name, version)
86
+ # Check if the version exists on RubyGems
87
+ # This is a simplified check - in production, you might want more robust validation
88
+ return false if version.nil? || version.empty?
89
+
90
+ # Basic semantic version validation
91
+ version.match?(/^\d+\.\d+(\.\d+)?/)
92
+ end
93
+
94
+ def confirm_fixes(fixes)
95
+ puts "\nšŸ”§ Planned Fixes:"
96
+ puts "=" * 50
97
+
98
+ fixes.each do |fix|
99
+ severity_emoji = severity_emoji(fix[:severity])
100
+ puts "#{severity_emoji} #{fix[:gem_name]}: #{fix[:current_version]} → #{fix[:target_version]}"
101
+ puts " Fixes: #{fix[:vulnerability_id]}"
102
+ end
103
+
104
+ puts "\nāš ļø This will modify your Gemfile.lock and may require bundle install."
105
+ print "Do you want to proceed? (y/N): "
106
+
107
+ response = $stdin.gets.chomp.downcase
108
+ response == "y" || response == "yes"
109
+ end
110
+
111
+ def severity_emoji(severity)
112
+ case severity.to_s.downcase
113
+ when /critical/
114
+ "šŸ”“"
115
+ when /high/
116
+ "🟠"
117
+ when /medium/
118
+ "🟔"
119
+ else
120
+ "🟢"
121
+ end
122
+ end
123
+
124
+ def create_lockfile_backup
125
+ return if @backup_created
126
+
127
+ backup_path = "#{@lockfile_path}.backup.#{Time.now.strftime("%Y%m%d_%H%M%S")}"
128
+ FileUtils.cp(@lockfile_path, backup_path)
129
+ @backup_created = true
130
+ puts "šŸ“¦ Created backup: #{backup_path}"
131
+ end
132
+
133
+ def apply_fixes(fixes)
134
+ applied_fixes = []
135
+
136
+ fixes.each do |fix|
137
+ if apply_single_fix(fix)
138
+ applied_fixes << fix
139
+ puts "āœ… Updated #{fix[:gem_name]} to #{fix[:target_version]}"
140
+ else
141
+ puts "āŒ Failed to update #{fix[:gem_name]}"
142
+ end
143
+ end
144
+
145
+ # Run bundle install to update the lockfile
146
+ if applied_fixes.any?
147
+ puts "\nšŸ”„ Running bundle install to update lockfile..."
148
+ system("bundle install")
149
+ end
150
+
151
+ applied_fixes
152
+ end
153
+
154
+ def apply_single_fix(fix)
155
+ # Use bundler to update the specific gem
156
+ command = "bundle update #{fix[:gem_name]} --conservative"
157
+
158
+ # Execute the bundle update command
159
+ system(command, out: File::NULL, err: File::NULL)
160
+ end
161
+ end
162
+ end
data/lib/gem_guard/cli.rb CHANGED
@@ -190,6 +190,87 @@ module GemGuard
190
190
  end
191
191
  end
192
192
 
193
+ desc "fix", "Automatically fix vulnerable dependencies"
194
+ option :lockfile, type: :string, desc: "Path to Gemfile.lock"
195
+ option :gemfile, type: :string, desc: "Path to Gemfile"
196
+ option :dry_run, type: :boolean, desc: "Show planned fixes without applying them"
197
+ option :interactive, type: :boolean, desc: "Ask for confirmation before applying fixes"
198
+ option :no_backup, type: :boolean, desc: "Skip creating backup of Gemfile.lock"
199
+ option :config, type: :string, desc: "Config file path"
200
+ def fix
201
+ config = Config.new(options[:config] || ".gemguard.yml")
202
+
203
+ lockfile_path = options[:lockfile] || config.lockfile_path
204
+ gemfile_path = options[:gemfile] || "Gemfile"
205
+ dry_run = options[:dry_run] || false
206
+ interactive = options[:interactive] || false
207
+ create_backup = !options[:no_backup]
208
+
209
+ unless File.exist?(lockfile_path)
210
+ puts "Error: #{lockfile_path} not found"
211
+ exit EXIT_ERROR
212
+ end
213
+
214
+ unless File.exist?(gemfile_path)
215
+ puts "Error: #{gemfile_path} not found. Auto-fix requires a Gemfile."
216
+ exit EXIT_ERROR
217
+ end
218
+
219
+ begin
220
+ # First, scan for vulnerabilities
221
+ dependencies = Parser.new.parse(lockfile_path)
222
+ vulnerabilities = VulnerabilityFetcher.new.fetch_for(dependencies)
223
+ analysis = Analyzer.new.analyze(dependencies, vulnerabilities)
224
+
225
+ if analysis.vulnerable_dependencies.empty?
226
+ puts "āœ… No vulnerabilities found. Nothing to fix!"
227
+ exit EXIT_SUCCESS
228
+ end
229
+
230
+ # Apply fixes
231
+ auto_fixer = AutoFixer.new(lockfile_path, gemfile_path)
232
+ result = auto_fixer.fix_vulnerabilities(
233
+ analysis.vulnerable_dependencies,
234
+ dry_run: dry_run,
235
+ interactive: interactive,
236
+ backup: create_backup
237
+ )
238
+
239
+ case result[:status]
240
+ when :no_fixes_needed
241
+ puts "ā„¹ļø #{result[:message]}"
242
+ exit EXIT_SUCCESS
243
+ when :dry_run
244
+ puts "šŸ” Dry Run Results:"
245
+ puts "=" * 40
246
+ result[:fixes].each do |fix|
247
+ puts "#{fix[:gem_name]}: #{fix[:current_version]} → #{fix[:target_version]}"
248
+ puts " Fixes: #{fix[:vulnerability_id]} (#{fix[:severity]})"
249
+ end
250
+ puts "\n#{result[:message]}"
251
+ puts "Run without --dry-run to apply these fixes."
252
+ exit EXIT_SUCCESS
253
+ when :cancelled
254
+ puts "āŒ #{result[:message]}"
255
+ exit EXIT_SUCCESS
256
+ when :completed
257
+ puts "šŸŽ‰ #{result[:message]}"
258
+ puts "\nšŸ“‹ Applied Fixes:"
259
+ result[:fixes].each do |fix|
260
+ puts "āœ… #{fix[:gem_name]}: #{fix[:current_version]} → #{fix[:target_version]}"
261
+ end
262
+ puts "\nšŸ’” Run 'gem_guard scan' to verify fixes."
263
+ exit EXIT_SUCCESS
264
+ else
265
+ puts "āŒ Unexpected error during fix operation"
266
+ exit EXIT_ERROR
267
+ end
268
+ rescue => e
269
+ puts "Error: #{e.message}"
270
+ exit EXIT_ERROR
271
+ end
272
+ end
273
+
193
274
  desc "version", "Show gem_guard version"
194
275
  def version
195
276
  puts GemGuard::VERSION
@@ -1,3 +1,3 @@
1
1
  module GemGuard
2
- VERSION = "0.1.10"
2
+ VERSION = "1.1.2"
3
3
  end
data/lib/gem_guard.rb CHANGED
@@ -3,10 +3,11 @@ require_relative "gem_guard/parser"
3
3
  require_relative "gem_guard/vulnerability_fetcher"
4
4
  require_relative "gem_guard/analyzer"
5
5
  require_relative "gem_guard/reporter"
6
+ require_relative "gem_guard/cli"
7
+ require_relative "gem_guard/config"
6
8
  require_relative "gem_guard/sbom_generator"
7
9
  require_relative "gem_guard/typosquat_checker"
8
- require_relative "gem_guard/config"
9
- require_relative "gem_guard/cli"
10
+ require_relative "gem_guard/auto_fixer"
10
11
 
11
12
  module GemGuard
12
13
  class Error < StandardError; end
@@ -0,0 +1,13 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ nokogiri (1.18.8)
5
+
6
+ PLATFORMS
7
+ ruby
8
+
9
+ DEPENDENCIES
10
+ nokogiri
11
+
12
+ BUNDLED WITH
13
+ 2.4.10
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gem_guard
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.10
4
+ version: 1.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wilbur Suero
@@ -99,6 +99,7 @@ files:
99
99
  - gem_guard.gemspec
100
100
  - lib/gem_guard.rb
101
101
  - lib/gem_guard/analyzer.rb
102
+ - lib/gem_guard/auto_fixer.rb
102
103
  - lib/gem_guard/cli.rb
103
104
  - lib/gem_guard/config.rb
104
105
  - lib/gem_guard/parser.rb
@@ -112,6 +113,7 @@ files:
112
113
  - templates/github-actions.yml
113
114
  - templates/gitlab-ci.yml
114
115
  - test_nokogiri.lock
116
+ - test_nokogiri.lock.backup.20250810_002252
115
117
  homepage: https://github.com/wilburhimself/gem_guard
116
118
  licenses:
117
119
  - MIT