rubion 0.3.7 → 0.3.9
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/lib/rubion/scanner.rb +127 -3
- data/lib/rubion/version.rb +1 -1
- data/lib/rubion.rb +47 -40
- 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: cbfa8d01b6cef9604c0b32e7075db67ed38d96141419fad0dd02b44e8930609a
|
|
4
|
+
data.tar.gz: 2aaa821116bdd6cf9a02268f523db47573b4accfdeaae6277a8fe97207b24a3f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f00a408ee14d7615a42de4d091a6bc10fb8d1f91d255fd2425126f241acfad3466e49064f4925eace4df986276d9052df8dca31974c70c52ca5127e91cf1b56e
|
|
7
|
+
data.tar.gz: 17cb903ed78b6a6cc11622fe706f2c48f98bd47daeb487abc8ccd90d356d15f8468b9747a9aa077823a083740910fe0a95647b0f417108f2ee6b272f070062fd
|
data/lib/rubion/scanner.rb
CHANGED
|
@@ -148,17 +148,43 @@ module Rubion
|
|
|
148
148
|
def check_npm_versions
|
|
149
149
|
return unless @package_manager
|
|
150
150
|
|
|
151
|
-
|
|
151
|
+
# Yarn v1 doesn't support --json flag, so handle it differently
|
|
152
|
+
if @package_manager == 'yarn'
|
|
153
|
+
check_yarn_outdated
|
|
154
|
+
else
|
|
155
|
+
check_npm_outdated
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def check_npm_outdated
|
|
160
|
+
command = 'npm outdated --json 2>&1'
|
|
152
161
|
stdout, stderr, status = Open3.capture3(command, chdir: @project_path)
|
|
153
162
|
|
|
154
163
|
begin
|
|
155
164
|
data = JSON.parse(stdout) unless stdout.empty?
|
|
156
165
|
parse_npm_outdated_output(data || {})
|
|
157
|
-
rescue JSON::ParserError
|
|
166
|
+
rescue JSON::ParserError => e
|
|
167
|
+
puts " ⚠️ Error parsing npm outdated JSON output: #{e.message}"
|
|
168
|
+
@result.package_versions = []
|
|
169
|
+
end
|
|
170
|
+
rescue StandardError => e
|
|
171
|
+
puts " ⚠️ Could not run npm outdated (#{e.message}). Skipping package version check."
|
|
172
|
+
@result.package_versions = []
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def check_yarn_outdated
|
|
176
|
+
# Yarn v1 doesn't support --json, so parse text output
|
|
177
|
+
command = 'yarn outdated 2>&1'
|
|
178
|
+
stdout, stderr, status = Open3.capture3(command, chdir: @project_path)
|
|
179
|
+
|
|
180
|
+
begin
|
|
181
|
+
parse_yarn_outdated_output(stdout)
|
|
182
|
+
rescue StandardError => e
|
|
183
|
+
puts " ⚠️ Could not parse yarn outdated output (#{e.message}). Skipping package version check."
|
|
158
184
|
@result.package_versions = []
|
|
159
185
|
end
|
|
160
186
|
rescue StandardError => e
|
|
161
|
-
puts " ⚠️ Could not run
|
|
187
|
+
puts " ⚠️ Could not run yarn outdated (#{e.message}). Skipping package version check."
|
|
162
188
|
@result.package_versions = []
|
|
163
189
|
end
|
|
164
190
|
|
|
@@ -395,6 +421,104 @@ module Rubion
|
|
|
395
421
|
@result.package_versions = []
|
|
396
422
|
end
|
|
397
423
|
|
|
424
|
+
def parse_yarn_outdated_output(output)
|
|
425
|
+
versions = []
|
|
426
|
+
packages_to_process = []
|
|
427
|
+
|
|
428
|
+
# Yarn v1 outdated output format:
|
|
429
|
+
# Package Name Current Wanted Latest
|
|
430
|
+
# package-name 1.0.0 1.0.0 2.0.0
|
|
431
|
+
# Skip header lines and parse package info
|
|
432
|
+
output.each_line do |line|
|
|
433
|
+
line = line.strip
|
|
434
|
+
next if line.empty?
|
|
435
|
+
next if line.start_with?('Package') || line.start_with?('yarn') || line.start_with?('Done')
|
|
436
|
+
next if line.include?('─') # Skip separator lines
|
|
437
|
+
|
|
438
|
+
# Parse format: package-name current wanted latest location
|
|
439
|
+
# Or: package-name current wanted latest
|
|
440
|
+
parts = line.split(/\s+/)
|
|
441
|
+
next if parts.length < 4
|
|
442
|
+
|
|
443
|
+
package_name = parts[0]
|
|
444
|
+
current_version = parts[1]
|
|
445
|
+
latest_version = parts[3] # Skip wanted (parts[2]), use latest
|
|
446
|
+
|
|
447
|
+
# Skip if versions are the same (not outdated)
|
|
448
|
+
next if current_version == latest_version
|
|
449
|
+
|
|
450
|
+
packages_to_process << {
|
|
451
|
+
name: package_name,
|
|
452
|
+
current_version: current_version,
|
|
453
|
+
latest_version: latest_version
|
|
454
|
+
}
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
total = packages_to_process.size
|
|
458
|
+
|
|
459
|
+
return if total == 0
|
|
460
|
+
|
|
461
|
+
# Process in parallel with threads (limit to 10 concurrent requests)
|
|
462
|
+
mutex = Mutex.new
|
|
463
|
+
thread_pool = []
|
|
464
|
+
max_threads = 10
|
|
465
|
+
|
|
466
|
+
packages_to_process.each_with_index do |pkg_data, index|
|
|
467
|
+
# Wait if we have too many threads
|
|
468
|
+
thread_pool.shift.join if thread_pool.size >= max_threads
|
|
469
|
+
|
|
470
|
+
thread = Thread.new do
|
|
471
|
+
# Fetch all version info once per package (includes dates and version list)
|
|
472
|
+
pkg_data_full = fetch_npm_all_versions(pkg_data[:name])
|
|
473
|
+
|
|
474
|
+
# Extract dates for current and latest versions
|
|
475
|
+
current_date = pkg_data_full[:versions][pkg_data[:current_version]] || 'N/A'
|
|
476
|
+
latest_date = pkg_data_full[:versions][pkg_data[:latest_version]] || 'N/A'
|
|
477
|
+
|
|
478
|
+
# Calculate time difference
|
|
479
|
+
time_diff = calculate_time_difference(current_date, latest_date)
|
|
480
|
+
|
|
481
|
+
# Count versions between current and latest
|
|
482
|
+
version_count = count_versions_from_list(pkg_data_full[:version_list], pkg_data[:current_version],
|
|
483
|
+
pkg_data[:latest_version])
|
|
484
|
+
|
|
485
|
+
# Check if this is a direct dependency
|
|
486
|
+
direct_dependency = is_direct_package?(pkg_data[:name])
|
|
487
|
+
|
|
488
|
+
result = {
|
|
489
|
+
package: pkg_data[:name],
|
|
490
|
+
current: pkg_data[:current_version],
|
|
491
|
+
current_date: current_date,
|
|
492
|
+
latest: pkg_data[:latest_version],
|
|
493
|
+
latest_date: latest_date,
|
|
494
|
+
time_diff: time_diff,
|
|
495
|
+
version_count: version_count,
|
|
496
|
+
direct: direct_dependency,
|
|
497
|
+
index: index
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
mutex.synchronize do
|
|
501
|
+
versions << result
|
|
502
|
+
print "\r📦 Checking NPM packages... #{versions.size}/#{total}"
|
|
503
|
+
$stdout.flush
|
|
504
|
+
end
|
|
505
|
+
end
|
|
506
|
+
|
|
507
|
+
thread_pool << thread
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
# Wait for all threads to complete
|
|
511
|
+
thread_pool.each(&:join)
|
|
512
|
+
|
|
513
|
+
# Sort by original index to maintain order
|
|
514
|
+
versions.sort_by! { |v| v[:index] }
|
|
515
|
+
versions.each { |v| v.delete(:index) }
|
|
516
|
+
|
|
517
|
+
puts "\r📦 Checking NPM packages... #{total}/#{total} ✓" if total > 0
|
|
518
|
+
|
|
519
|
+
@result.package_versions = versions
|
|
520
|
+
end
|
|
521
|
+
|
|
398
522
|
# Dummy data for demonstration (commented out - only show real data)
|
|
399
523
|
# Uncomment these methods if you need dummy data for testing
|
|
400
524
|
|
data/lib/rubion/version.rb
CHANGED
data/lib/rubion.rb
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative
|
|
4
|
-
require_relative
|
|
5
|
-
require_relative
|
|
3
|
+
require_relative 'rubion/version'
|
|
4
|
+
require_relative 'rubion/scanner'
|
|
5
|
+
require_relative 'rubion/reporter'
|
|
6
6
|
|
|
7
7
|
module Rubion
|
|
8
8
|
class Error < StandardError; end
|
|
@@ -10,7 +10,7 @@ module Rubion
|
|
|
10
10
|
class CLI
|
|
11
11
|
def self.start(args)
|
|
12
12
|
command = args[0]
|
|
13
|
-
|
|
13
|
+
|
|
14
14
|
case command
|
|
15
15
|
when 'scan'
|
|
16
16
|
# Parse options
|
|
@@ -29,8 +29,8 @@ module Rubion
|
|
|
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:
|
|
33
|
-
|
|
32
|
+
options = { gems: true, packages: true, sort_by: 'Behind By(Time)', sort_desc: true, exclude_dependencies: false }
|
|
33
|
+
|
|
34
34
|
# Check for --gems-only or --packages-only flags
|
|
35
35
|
if args.include?('--gems-only') || args.include?('-g')
|
|
36
36
|
options[:gems] = true
|
|
@@ -43,50 +43,54 @@ module Rubion
|
|
|
43
43
|
options[:gems] = args.include?('--gems')
|
|
44
44
|
options[:packages] = args.include?('--packages')
|
|
45
45
|
end
|
|
46
|
-
|
|
46
|
+
|
|
47
47
|
# Parse --sort-by or -s option
|
|
48
48
|
sort_index = args.index('--sort-by') || args.index('-s')
|
|
49
|
-
if sort_index && args[sort_index + 1]
|
|
50
|
-
|
|
49
|
+
options[:sort_by] = args[sort_index + 1] if sort_index && args[sort_index + 1]
|
|
50
|
+
|
|
51
|
+
# Parse --asc/--ascending or --desc/--descending for sort order
|
|
52
|
+
if args.include?('--asc') || args.include?('--ascending')
|
|
53
|
+
options[:sort_desc] = false
|
|
54
|
+
elsif args.include?('--desc') || args.include?('--descending')
|
|
55
|
+
options[:sort_desc] = true
|
|
51
56
|
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
|
-
|
|
57
|
+
|
|
56
58
|
# Parse --exclude-dependencies flag
|
|
57
59
|
options[:exclude_dependencies] = true if args.include?('--exclude-dependencies')
|
|
58
|
-
|
|
60
|
+
|
|
59
61
|
options
|
|
60
62
|
end
|
|
61
63
|
|
|
62
|
-
def self.scan(options = { gems: true, packages: true, sort_by:
|
|
64
|
+
def self.scan(options = { gems: true, packages: true, sort_by: 'Behind By(Time)', sort_desc: true,
|
|
65
|
+
exclude_dependencies: false })
|
|
63
66
|
project_path = Dir.pwd
|
|
64
|
-
|
|
67
|
+
|
|
65
68
|
scanner = Scanner.new(project_path: project_path)
|
|
66
69
|
result = scanner.scan_incremental(options)
|
|
67
|
-
|
|
70
|
+
|
|
68
71
|
# Results are already printed incrementally based on options
|
|
69
72
|
# Package results are printed in scan_incremental, but we need to ensure
|
|
70
73
|
# they use the same reporter instance with sort_by
|
|
71
74
|
# Actually, scan_incremental handles gem printing, but package printing
|
|
72
75
|
# happens here, so we need a reporter for packages
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
return unless options[:packages]
|
|
77
|
+
|
|
78
|
+
reporter = Reporter.new(result, sort_by: options[:sort_by], sort_desc: options[:sort_desc],
|
|
79
|
+
exclude_dependencies: options[:exclude_dependencies])
|
|
80
|
+
reporter.print_package_vulnerabilities
|
|
81
|
+
reporter.print_package_versions
|
|
78
82
|
end
|
|
79
83
|
|
|
80
84
|
def self.print_help
|
|
81
85
|
puts <<~HELP
|
|
82
|
-
|
|
86
|
+
|
|
83
87
|
🔒 Rubion - Security & Version Scanner for Ruby and JavaScript projects
|
|
84
|
-
|
|
88
|
+
|
|
85
89
|
USAGE:
|
|
86
90
|
rubion scan [OPTIONS] Scan current project for vulnerabilities and outdated versions
|
|
87
91
|
rubion version Display Rubion version
|
|
88
92
|
rubion help Display this help message
|
|
89
|
-
|
|
93
|
+
|
|
90
94
|
SCAN OPTIONS:
|
|
91
95
|
--gems, --gem, -g Scan only Ruby gems (skip NPM packages)
|
|
92
96
|
--packages, --npm, -p Scan only NPM packages (skip Ruby gems)
|
|
@@ -94,61 +98,64 @@ module Rubion
|
|
|
94
98
|
--sort-by COLUMN, -s COLUMN Sort results by column (Name, Current, Date, Latest, Behind By(Time), Behind By(Versions))
|
|
95
99
|
(default: "Behind By(Time)" in descending order)
|
|
96
100
|
--asc, --ascending Sort in ascending order (use with --sort-by)
|
|
101
|
+
--desc, --descending Sort in descending order (use with --sort-by, default)
|
|
97
102
|
--exclude-dependencies Show only direct dependencies (from Gemfile/package.json)
|
|
98
|
-
|
|
103
|
+
|
|
99
104
|
DESCRIPTION:
|
|
100
105
|
Rubion scans your project for:
|
|
101
106
|
- Ruby gem vulnerabilities (using bundler-audit)
|
|
102
107
|
- Outdated Ruby gems (using bundle outdated)
|
|
103
108
|
- NPM/JavaScript package vulnerabilities (using npm audit or yarn audit)
|
|
104
109
|
- Outdated NPM/JavaScript packages (using npm outdated or yarn outdated)
|
|
105
|
-
|
|
110
|
+
|
|
106
111
|
OUTPUT:
|
|
107
112
|
Results are displayed in organized tables with:
|
|
108
113
|
📛 Vulnerabilities with severity icons (🔴 Critical, 🟠 High, 🟡 Medium, 🟢 Low)
|
|
109
114
|
📦 Version information with release dates
|
|
110
115
|
⏱️ Time difference ("Behind By" column)
|
|
111
116
|
🔢 Version count between current and latest
|
|
112
|
-
|
|
117
|
+
|
|
113
118
|
EXAMPLES:
|
|
114
119
|
# Scan both gems and packages (default)
|
|
115
120
|
rubion scan
|
|
116
|
-
|
|
121
|
+
#{' '}
|
|
117
122
|
# Scan only Ruby gems
|
|
118
123
|
rubion scan --gems
|
|
119
|
-
|
|
124
|
+
#{' '}
|
|
120
125
|
# Scan only NPM packages
|
|
121
126
|
rubion scan --packages
|
|
122
|
-
|
|
127
|
+
#{' '}
|
|
123
128
|
# Sort by name
|
|
124
129
|
rubion scan --sort-by Name
|
|
125
|
-
|
|
130
|
+
#{' '}
|
|
126
131
|
# Sort by versions behind
|
|
127
132
|
rubion scan -s "Behind By(Versions)"
|
|
128
|
-
|
|
133
|
+
#{' '}
|
|
129
134
|
# Sort by name in descending order (default)
|
|
130
135
|
rubion scan --sort-by Name
|
|
131
|
-
|
|
136
|
+
#{' '}
|
|
132
137
|
# Sort by name in ascending order
|
|
133
138
|
rubion scan --sort-by Name --asc
|
|
134
|
-
|
|
139
|
+
#{' '}
|
|
140
|
+
# Sort by name in descending order
|
|
141
|
+
rubion scan --sort-by Name --desc
|
|
142
|
+
#{' '}
|
|
135
143
|
# Show only direct dependencies
|
|
136
144
|
rubion scan --exclude-dependencies
|
|
137
|
-
|
|
145
|
+
#{' '}
|
|
138
146
|
# Get help
|
|
139
147
|
rubion help
|
|
140
|
-
|
|
148
|
+
|
|
141
149
|
REQUIREMENTS:
|
|
142
150
|
- Ruby 2.6+
|
|
143
151
|
- Bundler (for gem scanning)
|
|
144
152
|
- NPM or Yarn (for package scanning, optional)
|
|
145
153
|
- bundler-audit (optional, install with: gem install bundler-audit)
|
|
146
|
-
|
|
154
|
+
#{' '}
|
|
147
155
|
NOTE:
|
|
148
156
|
If both npm and yarn are available, you will be prompted to choose which one to use.
|
|
149
|
-
|
|
157
|
+
|
|
150
158
|
HELP
|
|
151
159
|
end
|
|
152
160
|
end
|
|
153
161
|
end
|
|
154
|
-
|