Last active
April 16, 2020 18:03
-
-
Save 3zcurdia/5c2e8e2eff8649e16113af4c753d4666 to your computer and use it in GitHub Desktop.
Gemfile.lock auditor
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
require 'HTTparty' | |
require 'nokogiri' | |
require 'colorize' | |
require 'action_view' | |
class Analyzer | |
def initialize(gem) | |
@gem = gem | |
end | |
attr_accessor :gem | |
def url | |
raise "Not yet implemented" | |
end | |
def page | |
@page ||= Nokogiri::HTML(HTTParty.get(url)) | |
end | |
end | |
class RubyToolbox < Analyzer | |
def url | |
"https://www.ruby-toolbox.com/projects/#{gem}" | |
end | |
def score | |
@score ||= page.xpath("/html/body/section/div/section/div/div[1]/div[2]/div[1]/span[2]")&.text&.to_f | |
end | |
def description | |
@description ||= page.xpath("/html/body/section/div/section/div/div[4]/div")&.text | |
end | |
end | |
class RubyVersions < Analyzer | |
def url | |
"https://rubygems.org/gems/#{gem}/versions" | |
end | |
def latest | |
list.first | |
end | |
def [](version) | |
hash[version] | |
end | |
def list | |
@list ||= page.css(".t-list__items").css("li.gem__version-wrap").map do |node| | |
version = node.css(".t-list__item")&.text | |
date = node.css("small.gem__version__date")&.text&.sub("- ","") | |
[version, date] | |
end | |
end | |
def hash | |
list.to_h | |
end | |
def dependencies | |
@dependencies ||= pages.css(".dependencies").css(".t-list__items").map do |node| | |
item = node.css(".t-list__item") | |
gem = item&.css("strong")&.text | |
version = item&.text.sub(gem.to_s, '') | |
[gem, version] | |
end | |
end | |
def rails_dependencies | |
dependencies | |
end | |
end | |
class RubyDependencies < Analyzer | |
def url | |
"https://rubygems.org/gems/#{gem}" | |
end | |
def [](dependency) | |
hash[dependency] | |
end | |
def list | |
@list ||= page.css(".dependencies").css(".t-list__items").map do |node| | |
item = node.css(".t-list__item") | |
dependency = item&.css("strong")&.text | |
version = item&.text.sub(dependency.to_s, '') | |
[dependency.strip, version.strip.gsub("\n", '')] | |
end | |
end | |
def hash | |
list.to_h | |
end | |
RAILS_GEMS = %w[actionpack actionview actioncable actionmailer activerecord | |
active_model_serializers activejob activesupport activemodel | |
activestorage activeadmin].freeze | |
def rails_dependencies | |
hash.select { |dependency, _| RAILS_GEMS.include?(dependency) } | |
end | |
end | |
class VulnerabilityGems | |
VULNERABILITES = URI.open('https://blog.reversinglabs.com/hubfs/Blog/ruby_malicious_gems.txt') do |io| | |
io.read | |
end.lines.map{ |line| line.split }.to_h | |
def initialize(gem) | |
@gem = gem | |
end | |
def vulnerable? | |
VULNERABILITES.key?(gem) | |
end | |
end | |
class GemfileAuditor | |
include ActionView::Helpers::DateHelper | |
def initialize(file_path) | |
@file_path = file_path | |
puts "Analizing: #{@file_path}" | |
end | |
def audit | |
parse.each do |gem, version| | |
analyze(gem, version) | |
end | |
end | |
def parse | |
out = {} | |
File.open(@file_path).each do |line| | |
out[$1.tr(" ", "")] = $2 if line =~ /(.*)\s\(([\d\.]*)\)/ | |
end | |
out | |
end | |
private | |
def analyze(gem, version = nil) | |
puts "\n## #{gem}" | |
analyze_score(gem) | |
analyze_dependencies(gem) | |
analyze_version(gem, version) | |
analyze_vulnerability(gem) | |
end | |
def analyze_score(gem) | |
toolbox = RubyToolbox.new(gem) | |
puts toolbox.description | |
report = "\n\t- RubyToolbox score: #{toolbox.score}" | |
case toolbox.score | |
when (20.0..) | |
puts report.green | |
when (10.0...20.0) | |
puts report.blue | |
when (1.0...10.0) | |
puts report.yellow | |
when (0.0...1.0) | |
puts report.red | |
else | |
puts report | |
end | |
end | |
def analyze_version(gem, version = nil) | |
versions = RubyVersions.new(gem) | |
puts "\t- Latest Version: #{versions.latest&.join(" - ")}" | |
analyze_current_version(versions, version) | |
end | |
def analyze_dependencies(gem) | |
dependencies = RubyDependencies.new(gem) | |
return if dependencies.rails_dependencies.empty? | |
puts "\t- Rails dependencies" | |
dependencies.rails_dependencies.each do |rails_gem, version| | |
puts "\t * #{rails_gem} #{version}" | |
end | |
end | |
def analyze_vulnerability(gem) | |
vulnerable_gems = VulnerabilityGems.new(gem) | |
return unless vulnerable_gems.vulnerable? | |
puts "☢️ #{gem} is vulnerable" | |
end | |
private | |
def analyze_current_version(versions, current) | |
return unless current | |
relased = versions[current] | |
return unless relased | |
report = "\t- Current Version : #{current} - #{relased}" | |
report += "\n\t- Released #{distance_of_time_in_words(DateTime.now, DateTime.parse(relased))}" | |
if current == versions.latest.first | |
puts "#{report}, Updated ✅".green | |
else | |
puts "#{report}, Outdated ⚠️".yellow | |
end | |
end | |
end | |
if __FILE__ == $0 | |
if ARGV.length < 1 | |
puts "Gemfile.lock must be given" | |
exit(1) | |
end | |
GemfileAuditor.new(ARGV[0]).audit | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment