From 9232ca4508184505f140b3810823872299b480df Mon Sep 17 00:00:00 2001 From: Cheng XU Date: Fri, 28 Jun 2019 14:50:38 +0800 Subject: [PATCH] system_command: allow redacting secrets in the log Add a new argument `secrets` to specify secret tokens, so we can redact them in the log. --- Library/Homebrew/exceptions.rb | 6 ++++-- Library/Homebrew/system_command.rb | 9 ++++----- Library/Homebrew/test/system_command_spec.rb | 12 ++++++++++++ Library/Homebrew/utils.rb | 4 ++++ 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/Library/Homebrew/exceptions.rb b/Library/Homebrew/exceptions.rb index 5d6ca67cda..51ad3898fa 100644 --- a/Library/Homebrew/exceptions.rb +++ b/Library/Homebrew/exceptions.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require "shellwords" +require "utils" class UsageError < RuntimeError attr_reader :reason @@ -520,7 +521,7 @@ class ErrorDuringExecution < RuntimeError attr_reader :status attr_reader :output - def initialize(cmd, status:, output: nil) + def initialize(cmd, status:, output: nil, secrets: []) @cmd = cmd @status = status @output = output @@ -531,7 +532,8 @@ class ErrorDuringExecution < RuntimeError status end - s = +"Failure while executing; `#{cmd.shelljoin.gsub(/\\=/, "=")}` exited with #{exitstatus}." + redacted_cmd = redact_secrets(cmd.shelljoin.gsub('\=', "="), secrets) + s = +"Failure while executing; `#{redacted_cmd}` exited with #{exitstatus}." unless [*output].empty? format_output_line = lambda do |type_line| diff --git a/Library/Homebrew/system_command.rb b/Library/Homebrew/system_command.rb index e365ec1ab2..838661732f 100644 --- a/Library/Homebrew/system_command.rb +++ b/Library/Homebrew/system_command.rb @@ -34,7 +34,7 @@ class SystemCommand end def run! - puts command.shelljoin.gsub(/\\=/, "=") if verbose? || ARGV.debug? + puts redact_secrets(command.shelljoin.gsub('\=', "="), @secrets) if verbose? || ARGV.debug? @output = [] @@ -54,7 +54,7 @@ class SystemCommand end def initialize(executable, args: [], sudo: false, env: {}, input: [], must_succeed: false, - print_stdout: false, print_stderr: true, verbose: false, **options) + print_stdout: false, print_stderr: true, verbose: false, secrets: [], **options) @executable = executable @args = args @@ -63,6 +63,7 @@ class SystemCommand @print_stdout = print_stdout @print_stderr = print_stderr @verbose = verbose + @secrets = Array(secrets) @must_succeed = must_succeed options.assert_valid_keys!(:chdir) @options = options @@ -106,9 +107,7 @@ class SystemCommand def assert_success return if @status.success? - raise ErrorDuringExecution.new(command, - status: @status, - output: @output) + raise ErrorDuringExecution.new(command, status: @status, output: @output, secrets: @secrets) end def expanded_args diff --git a/Library/Homebrew/test/system_command_spec.rb b/Library/Homebrew/test/system_command_spec.rb index 56241ac33f..d1d5a79eed 100644 --- a/Library/Homebrew/test/system_command_spec.rb +++ b/Library/Homebrew/test/system_command_spec.rb @@ -252,5 +252,17 @@ describe SystemCommand do expect(system_command(executable)).to be_a_success end end + + context "when given arguments with secrets" do + it "does not leak the secrets" do + redacted_msg = /#{Regexp.escape("username:******")}/ + expect do + described_class.run! "curl", + args: %w[--user username:hunter2], + verbose: true, + secrets: %w[hunter2] + end.to raise_error.with_message(redacted_msg).and output(redacted_msg).to_stdout + end + end end end diff --git a/Library/Homebrew/utils.rb b/Library/Homebrew/utils.rb index be96044804..57499f2e3b 100644 --- a/Library/Homebrew/utils.rb +++ b/Library/Homebrew/utils.rb @@ -500,3 +500,7 @@ end def command_help_lines(path) path.read.lines.grep(/^#:/).map { |line| line.slice(2..-1) } end + +def redact_secrets(input, secrets) + secrets.reduce(input) { |str, secret| str.gsub secret, "******" }.freeze +end