diff --git a/lib/brakeman.rb b/lib/brakeman.rb index e128ab76be..9bd77fc972 100644 --- a/lib/brakeman.rb +++ b/lib/brakeman.rb @@ -250,6 +250,8 @@ def self.get_formats_from_output_format output_format [:to_sarif] when :sonar, :to_sonar [:to_sonar] + when :github, :to_github + [:to_github] else [:to_text] end @@ -283,6 +285,8 @@ def self.get_formats_from_output_files output_files :to_sarif when /\.sonar$/i :to_sonar + when /\.github$/i + :to_github else :to_text end diff --git a/lib/brakeman/options.rb b/lib/brakeman/options.rb index f6c6599112..7706292732 100644 --- a/lib/brakeman/options.rb +++ b/lib/brakeman/options.rb @@ -233,7 +233,7 @@ def create_option_parser options opts.on "-f", "--format TYPE", - [:pdf, :text, :html, :csv, :tabs, :json, :markdown, :codeclimate, :cc, :plain, :table, :junit, :sarif, :sonar], + [:pdf, :text, :html, :csv, :tabs, :json, :markdown, :codeclimate, :cc, :plain, :table, :junit, :sarif, :sonar, :github], "Specify output formats. Default is text" do |type| type = "s" if type == :text diff --git a/lib/brakeman/report.rb b/lib/brakeman/report.rb index fdb5501a21..8104f2ba65 100644 --- a/lib/brakeman/report.rb +++ b/lib/brakeman/report.rb @@ -6,7 +6,7 @@ class Brakeman::Report attr_reader :tracker - VALID_FORMATS = [:to_html, :to_pdf, :to_csv, :to_json, :to_tabs, :to_hash, :to_s, :to_markdown, :to_codeclimate, :to_plain, :to_text, :to_junit] + VALID_FORMATS = [:to_html, :to_pdf, :to_csv, :to_json, :to_tabs, :to_hash, :to_s, :to_markdown, :to_codeclimate, :to_plain, :to_text, :to_junit, :to_github] def initialize tracker @app_tree = tracker.app_tree @@ -48,6 +48,9 @@ def format format when :to_sonar require_report 'sonar' Brakeman::Report::Sonar + when :to_github + require_report 'github' + Brakeman::Report::Github else raise "Invalid format: #{format}. Should be one of #{VALID_FORMATS.inspect}" end diff --git a/lib/brakeman/report/report_github.rb b/lib/brakeman/report/report_github.rb new file mode 100644 index 0000000000..3f31f5bf65 --- /dev/null +++ b/lib/brakeman/report/report_github.rb @@ -0,0 +1,31 @@ +# Github Actions Formatter +# Formats warnings as workflow commands to create annotations in GitHub UI +class Brakeman::Report::Github < Brakeman::Report::Base + def generate_report + # @see https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-a-warning-message + errors.concat(warnings).join("\n") + end + + def warnings + all_warnings + .map { |warning| "::warning file=#{warning_file(warning)},line=#{warning.line}::#{warning.message}" } + end + + def errors + tracker.errors.map do |error| + if error[:exception].is_a?(Racc::ParseError) + # app/services/balance.rb:4 :: parse error on value "..." (tDOT3) + file, line = error[:exception].message.split(':').map(&:strip)[0,2] + "::error file=#{file},line=#{line}::#{clean_message(error[:error])}" + else + "::error ::#{clean_message(error[:error])}" + end + end + end + + private + + def clean_message(msg) + msg.gsub('::','').squeeze(' ') + end +end diff --git a/test/tests/brakeman.rb b/test/tests/brakeman.rb index abe4eeff80..64654d9cb5 100644 --- a/test/tests/brakeman.rb +++ b/test/tests/brakeman.rb @@ -287,6 +287,8 @@ def test_output_format output_format_tester({:output_format => :to_markdown}, [:to_markdown]) output_format_tester({:output_format => :text}, [:to_text]) output_format_tester({:output_format => :to_text}, [:to_text]) + output_format_tester({:output_format => :github}, [:to_github]) + output_format_tester({:output_format => :to_github}, [:to_github]) output_format_tester({:output_format => :others}, [:to_text]) output_format_tester({:output_files => ['xx.html', 'xx.pdf']}, [:to_html, :to_pdf]) @@ -298,7 +300,7 @@ def test_output_format output_format_tester({:output_files => ['xx.xx', 'xx.xx']}, [:to_text, :to_text]) output_format_tester({:output_files => ['xx.cc', 'xx.table']}, [:to_codeclimate, :to_table]) output_format_tester({:output_files => ['xx.plain', 'xx.text']}, [:to_text, :to_text]) - output_format_tester({:output_files => ['xx.html', 'xx.pdf', 'xx.csv', 'xx.tabs', 'xx.json', 'xx.md']}, [:to_html, :to_pdf, :to_csv, :to_tabs, :to_json, :to_markdown]) + output_format_tester({:output_files => ['xx.html', 'xx.pdf', 'xx.csv', 'xx.tabs', 'xx.json', 'xx.md', 'xx.github']}, [:to_html, :to_pdf, :to_csv, :to_tabs, :to_json, :to_markdown, :to_github]) end def test_output_format_errors_raised diff --git a/test/tests/github_output.rb b/test/tests/github_output.rb new file mode 100644 index 0000000000..635fa11920 --- /dev/null +++ b/test/tests/github_output.rb @@ -0,0 +1,29 @@ +require_relative '../test' + +class TestGithubOutput < Minitest::Test + def setup + @@report ||= github_report + end + + def test_report_format + assert_equal 33, @@report.lines.count + @@report.lines.each do |line| + assert line.start_with?('::'), 'Every line must start with `::`' + assert_equal 2, line.scan('::').count, 'Every line must have exactly 2 `::`' + end + end + + def test_for_errors + assert_equal 2, @@report.lines.count {|line| line.start_with?('::error') } + assert_includes @@report, 'file=app/services/balance.rb,line=4' + end + + private + + def github_report + tracker = Brakeman.run("#{TEST_PATH}/apps/rails6") + tracker.error Racc::ParseError.new('app/services/balance.rb:4 :: parse error on value "..." (tDOT3)') + tracker.error StandardError.new('Something went wrong') + tracker.report.to_github + end +end diff --git a/test/tests/report_generation.rb b/test/tests/report_generation.rb index 6d554f36bd..5d8327f03d 100644 --- a/test/tests/report_generation.rb +++ b/test/tests/report_generation.rb @@ -142,6 +142,13 @@ def test_markdown_debug_sanity @@tracker.options[:debug] = false end + def test_github_sanity + report = @@report.to_github + + assert report.is_a? String + assert report.include? "::warning" + end + def test_bad_format_type assert_raises RuntimeError do @@report.format(:to_something_else)