Skip to content

Commit ad37065

Browse files
authored
Merge pull request rails#53350 from jhawthorn/cve_announcement
Add tools/cve_announcement.rb
2 parents 9ffd264 + 3179531 commit ad37065

File tree

1 file changed

+93
-0
lines changed

1 file changed

+93
-0
lines changed

tools/cve_announcement.rb

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# frozen_string_literal: true
2+
3+
require "net/http"
4+
require "json"
5+
require "uri"
6+
7+
def versions_section(advisory)
8+
desc = +""
9+
advisory[:vulnerabilities].each do |vuln|
10+
package = vuln[:package][:name]
11+
bad_versions = vuln[:vulnerable_version_range]
12+
patched_versions = vuln[:patched_versions]
13+
desc << "* #{package} #{bad_versions}"
14+
desc << " (patched in #{patched_versions})" unless patched_versions.empty?
15+
desc << "\n"
16+
end
17+
["Versions affected", desc]
18+
end
19+
20+
def patches_section(advisory)
21+
desc = +""
22+
advisory[:vulnerabilities].each do |vuln|
23+
patched_versions = vuln[:patched_versions]
24+
commit = IO.popen(%W[git log --format=format:%H --grep=#{advisory[:cve_id]} v#{patched_versions}], &:read)
25+
raise "git log failed" unless $?.success?
26+
branch = patched_versions[/^\d+\.\d+/]
27+
desc << "* #{branch} - https://github.com/rails/rails/commit/#{commit}.patch\n"
28+
end
29+
["Patches", desc]
30+
end
31+
32+
def format_advisory(advisory)
33+
text = advisory[:description].dup
34+
text.gsub!("\r\n", "\n") # yuck
35+
36+
sections = text.split(/(?=\n[A-Z].+\n---+\n)/)
37+
header = sections.shift.strip
38+
header = <<EOS
39+
#{header}
40+
41+
* #{advisory[:cve_id]}
42+
* #{advisory[:ghsa_id]}
43+
44+
EOS
45+
46+
sections.map! do |section|
47+
section.split(/^---+$/, 2).map(&:strip)
48+
end
49+
50+
sections.unshift(versions_section(advisory))
51+
sections.push(patches_section(advisory))
52+
53+
([header.strip] + sections.map do |section|
54+
title, body = section
55+
"#{title}\n#{"-" * title.size}\n#{body.strip}"
56+
end).join("\n\n")
57+
end
58+
59+
uri = URI("https://api.github.com/repos/rails/rails/security-advisories")
60+
json = Net::HTTP.get(uri)
61+
advisories = JSON.parse(json, symbolize_names: true)
62+
63+
should_open = ARGV.delete("--open")
64+
cves = ARGV
65+
unless cves.any? && cves.all? { |s| s.match?(/\ACVE-\d\d\d\d-\d+\z/) }
66+
puts "Usage: #$0 CVE-YYYY-XXXXX..."
67+
puts
68+
puts "recent CVEs:"
69+
advisories[0, 10].each do |advisory|
70+
puts " #{advisory[:cve_id]} - #{advisory[:summary]}"
71+
end
72+
exit 1
73+
end
74+
75+
cves.map do |cve|
76+
advisory = advisories.detect { |x| x[:cve_id] == cve }
77+
raise "Can't find #{cve}" unless advisory
78+
if should_open
79+
format = format_advisory(advisory)
80+
query = {
81+
title: "[#{advisory[:cve_id]}] #{advisory[:summary]}",
82+
body: format,
83+
category: "security-announcements",
84+
tags: "announcement,security"
85+
}
86+
url = "https://discuss.rubyonrails.org/new-topic?#{URI.encode_www_form(query)}"
87+
system(ENV.fetch("BROWSER", "open"), url)
88+
else
89+
puts "# #{advisory[:summary]}"
90+
puts
91+
puts format_advisory(advisory)
92+
end
93+
end

0 commit comments

Comments
 (0)