rubion 0.3.1 → 0.3.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 +4 -4
- data/README.md +3 -1
- data/lib/rubion/reporter.rb +113 -30
- data/lib/rubion/scanner.rb +2 -2
- data/lib/rubion/version.rb +1 -1
- data/lib/rubion.rb +20 -5
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a34f7102afe1491a751b94e0d11e22fe854f20bb1c1f5a322473447282bd665a
|
|
4
|
+
data.tar.gz: a7dc977d51a43a5657445ee42afc730be67dd2fe6006e35b5153f47ae13fa30d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 13e0b27d669216a58580c02dc8f2e5d26a35a0102e9ac7537bb6d6aa6caea7d42e115b4c12ad6cecc4cdd61add4829a0e549b01578c23d150ce90da4ae859001
|
|
7
|
+
data.tar.gz: f09035851f40e23cdac6f3c594f3f99f4579438ea6421c9007295e1cde0707d689b95675e7435c8da17878defe51bec2ad8427b1ec09fc476727cb026ace9859
|
data/README.md
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Rubion
|
|
2
2
|
|
|
3
3
|
**Rubion** is a security and version scanner for Ruby and JavaScript projects. It helps you identify vulnerabilities and outdated dependencies in your Ruby gems and NPM/JavaScript packages.
|
|
4
4
|
|
|
5
|
+
<img width="1237" height="671" alt="Screenshot 2025-11-14 at 10 48 12 am" src="https://github.com/user-attachments/assets/a3d93452-c442-416a-9697-de59746e16ad" />
|
|
6
|
+
|
|
5
7
|
## Features
|
|
6
8
|
|
|
7
9
|
- 📛 **Gem Vulnerabilities**: Scans for known security vulnerabilities in Ruby gems using `bundle-audit`
|
data/lib/rubion/reporter.rb
CHANGED
|
@@ -4,8 +4,9 @@ require 'terminal-table'
|
|
|
4
4
|
|
|
5
5
|
module Rubion
|
|
6
6
|
class Reporter
|
|
7
|
-
def initialize(scan_result)
|
|
7
|
+
def initialize(scan_result, sort_by: nil)
|
|
8
8
|
@result = scan_result
|
|
9
|
+
@sort_by = sort_by
|
|
9
10
|
end
|
|
10
11
|
|
|
11
12
|
def report
|
|
@@ -36,31 +37,6 @@ module Rubion
|
|
|
36
37
|
|
|
37
38
|
private
|
|
38
39
|
|
|
39
|
-
def _print_gem_vulnerabilities
|
|
40
|
-
puts "Gem Vulnerabilities:\n\n"
|
|
41
|
-
|
|
42
|
-
if @result.gem_vulnerabilities.empty?
|
|
43
|
-
puts " ✅ No vulnerabilities found!\n\n"
|
|
44
|
-
return
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
table = Terminal::Table.new do |t|
|
|
48
|
-
t.headings = %w[Level Name Version Vulnerability]
|
|
49
|
-
|
|
50
|
-
@result.gem_vulnerabilities.each do |vuln|
|
|
51
|
-
t.add_row [
|
|
52
|
-
severity_with_icon(vuln[:severity]),
|
|
53
|
-
vuln[:gem],
|
|
54
|
-
vuln[:version],
|
|
55
|
-
truncate(vuln[:title], 50)
|
|
56
|
-
]
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
puts table
|
|
61
|
-
puts "\n"
|
|
62
|
-
end
|
|
63
|
-
|
|
64
40
|
def print_header
|
|
65
41
|
# Simplified header
|
|
66
42
|
puts "\n"
|
|
@@ -97,10 +73,14 @@ module Rubion
|
|
|
97
73
|
return
|
|
98
74
|
end
|
|
99
75
|
|
|
76
|
+
# Sort if sort_by is specified
|
|
77
|
+
versions = @result.gem_versions.dup
|
|
78
|
+
versions = sort_versions(versions, :gem) if @sort_by
|
|
79
|
+
|
|
100
80
|
table = Terminal::Table.new do |t|
|
|
101
81
|
t.headings = ['Name', 'Current', 'Date', 'Latest', 'Date', 'Behind By(Time)', 'Behind By(Versions)']
|
|
102
82
|
|
|
103
|
-
|
|
83
|
+
versions.each do |gem|
|
|
104
84
|
t.add_row [
|
|
105
85
|
gem[:gem],
|
|
106
86
|
gem[:current],
|
|
@@ -150,10 +130,14 @@ module Rubion
|
|
|
150
130
|
return
|
|
151
131
|
end
|
|
152
132
|
|
|
133
|
+
# Sort if sort_by is specified
|
|
134
|
+
versions = @result.package_versions.dup
|
|
135
|
+
versions = sort_versions(versions, :package) if @sort_by
|
|
136
|
+
|
|
153
137
|
table = Terminal::Table.new do |t|
|
|
154
|
-
t.headings = ['Name', 'Current', 'Date', 'Latest', 'Date', 'Behind By', 'Versions']
|
|
138
|
+
t.headings = ['Name', 'Current', 'Date', 'Latest', 'Date', 'Behind By(Time)', 'Behind By(Versions)']
|
|
155
139
|
|
|
156
|
-
|
|
140
|
+
versions.each do |pkg|
|
|
157
141
|
t.add_row [
|
|
158
142
|
pkg[:package],
|
|
159
143
|
pkg[:current],
|
|
@@ -218,7 +202,7 @@ module Rubion
|
|
|
218
202
|
def truncate(text, length = 50)
|
|
219
203
|
return text if text.length <= length
|
|
220
204
|
|
|
221
|
-
"#{text[0..length - 3]}..."
|
|
205
|
+
"#{text[0..(length - 3)]}..."
|
|
222
206
|
end
|
|
223
207
|
|
|
224
208
|
def version_difference(current, latest)
|
|
@@ -242,5 +226,104 @@ module Rubion
|
|
|
242
226
|
rescue StandardError
|
|
243
227
|
'unknown'
|
|
244
228
|
end
|
|
229
|
+
|
|
230
|
+
# Sort versions array based on the specified column
|
|
231
|
+
def sort_versions(versions, name_key)
|
|
232
|
+
return versions unless @sort_by
|
|
233
|
+
|
|
234
|
+
column = @sort_by.strip.downcase
|
|
235
|
+
name_key_sym = name_key # :gem or :package
|
|
236
|
+
|
|
237
|
+
# Normalize column name - default to 'name' if not recognized
|
|
238
|
+
normalized_column = case column
|
|
239
|
+
when 'name', 'current', 'date', 'latest',
|
|
240
|
+
'behind by(time)', 'behind by time', 'time',
|
|
241
|
+
'behind by(versions)', 'behind by versions', 'versions'
|
|
242
|
+
column
|
|
243
|
+
else
|
|
244
|
+
'name' # Default to name sorting
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
versions.sort_by do |item|
|
|
248
|
+
case normalized_column
|
|
249
|
+
when 'name'
|
|
250
|
+
item[name_key_sym].to_s.downcase
|
|
251
|
+
when 'current'
|
|
252
|
+
parse_version_for_sort(item[:current])
|
|
253
|
+
when 'date'
|
|
254
|
+
# Sort by current_date (first Date column)
|
|
255
|
+
parse_date_for_sort(item[:current_date])
|
|
256
|
+
when 'latest'
|
|
257
|
+
parse_version_for_sort(item[:latest])
|
|
258
|
+
when 'behind by(time)', 'behind by time', 'time'
|
|
259
|
+
parse_time_for_sort(item[:time_diff])
|
|
260
|
+
when 'behind by(versions)', 'behind by versions', 'versions'
|
|
261
|
+
parse_version_count_for_sort(item[:version_count])
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# Parse version string for sorting (handles semantic versions)
|
|
267
|
+
def parse_version_for_sort(version_str)
|
|
268
|
+
return [0, 0, 0, ''] if version_str.nil? || version_str == 'N/A' || version_str == 'unknown'
|
|
269
|
+
|
|
270
|
+
# Handle version strings like "1.2.3", "1.2.3.4", "1.2.3-beta", etc.
|
|
271
|
+
parts = version_str.to_s.split(/[.-]/)
|
|
272
|
+
major = parts[0].to_i
|
|
273
|
+
minor = parts[1].to_i
|
|
274
|
+
patch = parts[2].to_i
|
|
275
|
+
suffix = parts[3..-1].join('') if parts.length > 3
|
|
276
|
+
|
|
277
|
+
[major, minor, patch, suffix || '']
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
# Parse date string for sorting (handles M/D/YYYY format)
|
|
281
|
+
def parse_date_for_sort(date_str)
|
|
282
|
+
return [0, 0, 0] if date_str.nil? || date_str == 'N/A'
|
|
283
|
+
|
|
284
|
+
begin
|
|
285
|
+
parts = date_str.split('/').map(&:to_i)
|
|
286
|
+
return [parts[2] || 0, parts[0] || 0, parts[1] || 0] if parts.length == 3
|
|
287
|
+
rescue StandardError
|
|
288
|
+
# If parsing fails, return a date that sorts last
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
[0, 0, 0]
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
# Parse time difference string for sorting (e.g., "1 year", "3 months", "5 days")
|
|
295
|
+
def parse_time_for_sort(time_str)
|
|
296
|
+
return [0, 0] if time_str.nil? || time_str == 'N/A'
|
|
297
|
+
|
|
298
|
+
time_str = time_str.to_s.downcase.strip
|
|
299
|
+
|
|
300
|
+
# Extract number and unit
|
|
301
|
+
match = time_str.match(/(\d+(?:\.\d+)?)\s*(year|month|day|years|months|days)/)
|
|
302
|
+
return [0, 0] unless match
|
|
303
|
+
|
|
304
|
+
value = match[1].to_f
|
|
305
|
+
unit = match[2].to_s.downcase
|
|
306
|
+
|
|
307
|
+
# Convert to days for comparison
|
|
308
|
+
days = case unit
|
|
309
|
+
when /year/
|
|
310
|
+
value * 365
|
|
311
|
+
when /month/
|
|
312
|
+
value * 30
|
|
313
|
+
when /day/
|
|
314
|
+
value
|
|
315
|
+
else
|
|
316
|
+
0
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
[days.to_i, value]
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
# Parse version count for sorting
|
|
323
|
+
def parse_version_count_for_sort(count)
|
|
324
|
+
return 0 if count.nil? || count == 'N/A' || count == 'unknown'
|
|
325
|
+
|
|
326
|
+
count.to_i
|
|
327
|
+
end
|
|
245
328
|
end
|
|
246
329
|
end
|
data/lib/rubion/scanner.rb
CHANGED
|
@@ -37,7 +37,7 @@ module Rubion
|
|
|
37
37
|
@result
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
-
def scan_incremental(options = { gems: true, packages: true })
|
|
40
|
+
def scan_incremental(options = { gems: true, packages: true, sort_by: nil })
|
|
41
41
|
puts "🔍 Scanning project at: #{@project_path}\n\n"
|
|
42
42
|
|
|
43
43
|
# Scan and display Ruby gems first (if enabled)
|
|
@@ -46,7 +46,7 @@ module Rubion
|
|
|
46
46
|
|
|
47
47
|
# Print gem results immediately
|
|
48
48
|
puts "\n"
|
|
49
|
-
reporter = Reporter.new(@result)
|
|
49
|
+
reporter = Reporter.new(@result, sort_by: options[:sort_by])
|
|
50
50
|
reporter.print_gem_vulnerabilities
|
|
51
51
|
reporter.print_gem_versions
|
|
52
52
|
end
|
data/lib/rubion/version.rb
CHANGED
data/lib/rubion.rb
CHANGED
|
@@ -28,7 +28,7 @@ module Rubion
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
def self.parse_scan_options(args)
|
|
31
|
-
options = { gems: true, packages: true }
|
|
31
|
+
options = { gems: true, packages: true, sort_by: nil }
|
|
32
32
|
|
|
33
33
|
# Check for --gems-only or --packages-only flags
|
|
34
34
|
if args.include?('--gems-only') || args.include?('-g')
|
|
@@ -43,20 +43,28 @@ module Rubion
|
|
|
43
43
|
options[:packages] = args.include?('--packages')
|
|
44
44
|
end
|
|
45
45
|
|
|
46
|
+
# Parse --sort-by or -s option
|
|
47
|
+
sort_index = args.index('--sort-by') || args.index('-s')
|
|
48
|
+
if sort_index && args[sort_index + 1]
|
|
49
|
+
options[:sort_by] = args[sort_index + 1]
|
|
50
|
+
end
|
|
51
|
+
|
|
46
52
|
options
|
|
47
53
|
end
|
|
48
54
|
|
|
49
|
-
def self.scan(options = { gems: true, packages: true })
|
|
55
|
+
def self.scan(options = { gems: true, packages: true, sort_by: nil })
|
|
50
56
|
project_path = Dir.pwd
|
|
51
57
|
|
|
52
58
|
scanner = Scanner.new(project_path: project_path)
|
|
53
59
|
result = scanner.scan_incremental(options)
|
|
54
60
|
|
|
55
61
|
# Results are already printed incrementally based on options
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
#
|
|
62
|
+
# Package results are printed in scan_incremental, but we need to ensure
|
|
63
|
+
# they use the same reporter instance with sort_by
|
|
64
|
+
# Actually, scan_incremental handles gem printing, but package printing
|
|
65
|
+
# happens here, so we need a reporter for packages
|
|
59
66
|
if options[:packages]
|
|
67
|
+
reporter = Reporter.new(result, sort_by: options[:sort_by])
|
|
60
68
|
reporter.print_package_vulnerabilities
|
|
61
69
|
reporter.print_package_versions
|
|
62
70
|
end
|
|
@@ -76,6 +84,7 @@ module Rubion
|
|
|
76
84
|
--gems, --gem, -g Scan only Ruby gems (skip NPM packages)
|
|
77
85
|
--packages, --npm, -p Scan only NPM packages (skip Ruby gems)
|
|
78
86
|
--all, -a Scan both gems and packages (default)
|
|
87
|
+
--sort-by COLUMN, -s COLUMN Sort results by column (Name, Current, Date, Latest, Behind By(Time), Behind By(Versions))
|
|
79
88
|
|
|
80
89
|
DESCRIPTION:
|
|
81
90
|
Rubion scans your project for:
|
|
@@ -101,6 +110,12 @@ module Rubion
|
|
|
101
110
|
# Scan only NPM packages
|
|
102
111
|
rubion scan --packages
|
|
103
112
|
|
|
113
|
+
# Sort by name
|
|
114
|
+
rubion scan --sort-by Name
|
|
115
|
+
|
|
116
|
+
# Sort by versions behind
|
|
117
|
+
rubion scan -s "Behind By(Versions)"
|
|
118
|
+
|
|
104
119
|
# Get help
|
|
105
120
|
rubion help
|
|
106
121
|
|