Skip to content

Instantly share code, notes, and snippets.

@3zcurdia
Last active April 16, 2020 18:03
Show Gist options
  • Save 3zcurdia/5c2e8e2eff8649e16113af4c753d4666 to your computer and use it in GitHub Desktop.
Save 3zcurdia/5c2e8e2eff8649e16113af4c753d4666 to your computer and use it in GitHub Desktop.
Gemfile.lock auditor
#!/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