rubion 0.3.1 → 0.3.3
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 +117 -30
- data/lib/rubion/scanner.rb +2 -2
- data/lib/rubion/version.rb +1 -1
- data/lib/rubion.rb +32 -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: 72ddffc4eae85bd07e59db90bb20e6890ec3bc3b2da6d725dc9ffea8d7f3e7a5
|
|
4
|
+
data.tar.gz: 4d3fc54759167ab0b568489b61dacacdb7898b0716fb8fde245de186ea1e4995
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b3c7dffd83662d797e033288bc326645a14bb77847f16bb1472ab219d75081b4ee97769854a9eac641548c6f601b864cff7bc524d6a64a5605f0bda166f7def1
|
|
7
|
+
data.tar.gz: 253bf224334af3d725c074a7c5f3867ab7cede744663bf46e36a16f74232691648c95210ffc64db673569cb96c1f036152880d7dcb7dac512774824c011874d7
|
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,10 @@ 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: 'Behind By(Time)', sort_desc: true)
|
|
8
8
|
@result = scan_result
|
|
9
|
+
@sort_by = sort_by
|
|
10
|
+
@sort_desc = sort_desc
|
|
9
11
|
end
|
|
10
12
|
|
|
11
13
|
def report
|
|
@@ -36,31 +38,6 @@ module Rubion
|
|
|
36
38
|
|
|
37
39
|
private
|
|
38
40
|
|
|
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
41
|
def print_header
|
|
65
42
|
# Simplified header
|
|
66
43
|
puts "\n"
|
|
@@ -97,10 +74,14 @@ module Rubion
|
|
|
97
74
|
return
|
|
98
75
|
end
|
|
99
76
|
|
|
77
|
+
# Sort if sort_by is specified
|
|
78
|
+
versions = @result.gem_versions.dup
|
|
79
|
+
versions = sort_versions(versions, :gem) if @sort_by
|
|
80
|
+
|
|
100
81
|
table = Terminal::Table.new do |t|
|
|
101
82
|
t.headings = ['Name', 'Current', 'Date', 'Latest', 'Date', 'Behind By(Time)', 'Behind By(Versions)']
|
|
102
83
|
|
|
103
|
-
|
|
84
|
+
versions.each do |gem|
|
|
104
85
|
t.add_row [
|
|
105
86
|
gem[:gem],
|
|
106
87
|
gem[:current],
|
|
@@ -150,10 +131,14 @@ module Rubion
|
|
|
150
131
|
return
|
|
151
132
|
end
|
|
152
133
|
|
|
134
|
+
# Sort if sort_by is specified
|
|
135
|
+
versions = @result.package_versions.dup
|
|
136
|
+
versions = sort_versions(versions, :package) if @sort_by
|
|
137
|
+
|
|
153
138
|
table = Terminal::Table.new do |t|
|
|
154
|
-
t.headings = ['Name', 'Current', 'Date', 'Latest', 'Date', 'Behind By', 'Versions']
|
|
139
|
+
t.headings = ['Name', 'Current', 'Date', 'Latest', 'Date', 'Behind By(Time)', 'Behind By(Versions)']
|
|
155
140
|
|
|
156
|
-
|
|
141
|
+
versions.each do |pkg|
|
|
157
142
|
t.add_row [
|
|
158
143
|
pkg[:package],
|
|
159
144
|
pkg[:current],
|
|
@@ -218,7 +203,7 @@ module Rubion
|
|
|
218
203
|
def truncate(text, length = 50)
|
|
219
204
|
return text if text.length <= length
|
|
220
205
|
|
|
221
|
-
"#{text[0..length - 3]}..."
|
|
206
|
+
"#{text[0..(length - 3)]}..."
|
|
222
207
|
end
|
|
223
208
|
|
|
224
209
|
def version_difference(current, latest)
|
|
@@ -242,5 +227,107 @@ module Rubion
|
|
|
242
227
|
rescue StandardError
|
|
243
228
|
'unknown'
|
|
244
229
|
end
|
|
230
|
+
|
|
231
|
+
# Sort versions array based on the specified column
|
|
232
|
+
def sort_versions(versions, name_key)
|
|
233
|
+
return versions unless @sort_by
|
|
234
|
+
|
|
235
|
+
column = @sort_by.strip.downcase
|
|
236
|
+
name_key_sym = name_key # :gem or :package
|
|
237
|
+
|
|
238
|
+
# Normalize column name - default to 'name' if not recognized
|
|
239
|
+
normalized_column = case column
|
|
240
|
+
when 'name', 'current', 'date', 'latest',
|
|
241
|
+
'behind by(time)', 'behind by time', 'time',
|
|
242
|
+
'behind by(versions)', 'behind by versions', 'versions'
|
|
243
|
+
column
|
|
244
|
+
else
|
|
245
|
+
'name' # Default to name sorting
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
sorted = versions.sort_by do |item|
|
|
249
|
+
case normalized_column
|
|
250
|
+
when 'name'
|
|
251
|
+
item[name_key_sym].to_s.downcase
|
|
252
|
+
when 'current'
|
|
253
|
+
parse_version_for_sort(item[:current])
|
|
254
|
+
when 'date'
|
|
255
|
+
# Sort by current_date (first Date column)
|
|
256
|
+
parse_date_for_sort(item[:current_date])
|
|
257
|
+
when 'latest'
|
|
258
|
+
parse_version_for_sort(item[:latest])
|
|
259
|
+
when 'behind by(time)', 'behind by time', 'time'
|
|
260
|
+
parse_time_for_sort(item[:time_diff])
|
|
261
|
+
when 'behind by(versions)', 'behind by versions', 'versions'
|
|
262
|
+
parse_version_count_for_sort(item[:version_count])
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# Reverse if descending order requested
|
|
267
|
+
@sort_desc ? sorted.reverse : sorted
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Parse version string for sorting (handles semantic versions)
|
|
271
|
+
def parse_version_for_sort(version_str)
|
|
272
|
+
return [0, 0, 0, ''] if version_str.nil? || version_str == 'N/A' || version_str == 'unknown'
|
|
273
|
+
|
|
274
|
+
# Handle version strings like "1.2.3", "1.2.3.4", "1.2.3-beta", etc.
|
|
275
|
+
parts = version_str.to_s.split(/[.-]/)
|
|
276
|
+
major = parts[0].to_i
|
|
277
|
+
minor = parts[1].to_i
|
|
278
|
+
patch = parts[2].to_i
|
|
279
|
+
suffix = parts[3..-1].join('') if parts.length > 3
|
|
280
|
+
|
|
281
|
+
[major, minor, patch, suffix || '']
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# Parse date string for sorting (handles M/D/YYYY format)
|
|
285
|
+
def parse_date_for_sort(date_str)
|
|
286
|
+
return [0, 0, 0] if date_str.nil? || date_str == 'N/A'
|
|
287
|
+
|
|
288
|
+
begin
|
|
289
|
+
parts = date_str.split('/').map(&:to_i)
|
|
290
|
+
return [parts[2] || 0, parts[0] || 0, parts[1] || 0] if parts.length == 3
|
|
291
|
+
rescue StandardError
|
|
292
|
+
# If parsing fails, return a date that sorts last
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
[0, 0, 0]
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# Parse time difference string for sorting (e.g., "1 year", "3 months", "5 days")
|
|
299
|
+
def parse_time_for_sort(time_str)
|
|
300
|
+
return [0, 0] if time_str.nil? || time_str == 'N/A'
|
|
301
|
+
|
|
302
|
+
time_str = time_str.to_s.downcase.strip
|
|
303
|
+
|
|
304
|
+
# Extract number and unit
|
|
305
|
+
match = time_str.match(/(\d+(?:\.\d+)?)\s*(year|month|day|years|months|days)/)
|
|
306
|
+
return [0, 0] unless match
|
|
307
|
+
|
|
308
|
+
value = match[1].to_f
|
|
309
|
+
unit = match[2].to_s.downcase
|
|
310
|
+
|
|
311
|
+
# Convert to days for comparison
|
|
312
|
+
days = case unit
|
|
313
|
+
when /year/
|
|
314
|
+
value * 365
|
|
315
|
+
when /month/
|
|
316
|
+
value * 30
|
|
317
|
+
when /day/
|
|
318
|
+
value
|
|
319
|
+
else
|
|
320
|
+
0
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
[days.to_i, value]
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
# Parse version count for sorting
|
|
327
|
+
def parse_version_count_for_sort(count)
|
|
328
|
+
return 0 if count.nil? || count == 'N/A' || count == 'unknown'
|
|
329
|
+
|
|
330
|
+
count.to_i
|
|
331
|
+
end
|
|
245
332
|
end
|
|
246
333
|
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: "Behind By(Time)", sort_desc: true })
|
|
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], sort_desc: options[:sort_desc])
|
|
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,8 @@ module Rubion
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
def self.parse_scan_options(args)
|
|
31
|
-
|
|
31
|
+
# Default to sorting by "Behind By(Time)" in descending order
|
|
32
|
+
options = { gems: true, packages: true, sort_by: "Behind By(Time)", sort_desc: true }
|
|
32
33
|
|
|
33
34
|
# Check for --gems-only or --packages-only flags
|
|
34
35
|
if args.include?('--gems-only') || args.include?('-g')
|
|
@@ -43,20 +44,31 @@ module Rubion
|
|
|
43
44
|
options[:packages] = args.include?('--packages')
|
|
44
45
|
end
|
|
45
46
|
|
|
47
|
+
# Parse --sort-by or -s option
|
|
48
|
+
sort_index = args.index('--sort-by') || args.index('-s')
|
|
49
|
+
if sort_index && args[sort_index + 1]
|
|
50
|
+
options[:sort_by] = args[sort_index + 1]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Parse --asc or --ascending for ascending order (descending is default)
|
|
54
|
+
options[:sort_desc] = false if args.include?('--asc') || args.include?('--ascending')
|
|
55
|
+
|
|
46
56
|
options
|
|
47
57
|
end
|
|
48
58
|
|
|
49
|
-
def self.scan(options = { gems: true, packages: true })
|
|
59
|
+
def self.scan(options = { gems: true, packages: true, sort_by: "Behind By(Time)", sort_desc: true })
|
|
50
60
|
project_path = Dir.pwd
|
|
51
61
|
|
|
52
62
|
scanner = Scanner.new(project_path: project_path)
|
|
53
63
|
result = scanner.scan_incremental(options)
|
|
54
64
|
|
|
55
65
|
# Results are already printed incrementally based on options
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
#
|
|
66
|
+
# Package results are printed in scan_incremental, but we need to ensure
|
|
67
|
+
# they use the same reporter instance with sort_by
|
|
68
|
+
# Actually, scan_incremental handles gem printing, but package printing
|
|
69
|
+
# happens here, so we need a reporter for packages
|
|
59
70
|
if options[:packages]
|
|
71
|
+
reporter = Reporter.new(result, sort_by: options[:sort_by], sort_desc: options[:sort_desc])
|
|
60
72
|
reporter.print_package_vulnerabilities
|
|
61
73
|
reporter.print_package_versions
|
|
62
74
|
end
|
|
@@ -76,6 +88,9 @@ module Rubion
|
|
|
76
88
|
--gems, --gem, -g Scan only Ruby gems (skip NPM packages)
|
|
77
89
|
--packages, --npm, -p Scan only NPM packages (skip Ruby gems)
|
|
78
90
|
--all, -a Scan both gems and packages (default)
|
|
91
|
+
--sort-by COLUMN, -s COLUMN Sort results by column (Name, Current, Date, Latest, Behind By(Time), Behind By(Versions))
|
|
92
|
+
(default: "Behind By(Time)" in descending order)
|
|
93
|
+
--asc, --ascending Sort in ascending order (use with --sort-by)
|
|
79
94
|
|
|
80
95
|
DESCRIPTION:
|
|
81
96
|
Rubion scans your project for:
|
|
@@ -101,6 +116,18 @@ module Rubion
|
|
|
101
116
|
# Scan only NPM packages
|
|
102
117
|
rubion scan --packages
|
|
103
118
|
|
|
119
|
+
# Sort by name
|
|
120
|
+
rubion scan --sort-by Name
|
|
121
|
+
|
|
122
|
+
# Sort by versions behind
|
|
123
|
+
rubion scan -s "Behind By(Versions)"
|
|
124
|
+
|
|
125
|
+
# Sort by name in descending order (default)
|
|
126
|
+
rubion scan --sort-by Name
|
|
127
|
+
|
|
128
|
+
# Sort by name in ascending order
|
|
129
|
+
rubion scan --sort-by Name --asc
|
|
130
|
+
|
|
104
131
|
# Get help
|
|
105
132
|
rubion help
|
|
106
133
|
|