Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions lib/browserstack/local.rb
Original file line number Diff line number Diff line change
Expand Up @@ -121,12 +121,27 @@ def stop
@pid = nil
end

# Public accessor used by callers for debugging/logging. Return the command
# with the access key masked so it is never written to logs, CI artifacts or
# error trackers (CWE-312). The real key is still used for execution via
# start_command_args / start_command(false).
def command
start_command
start_command(true)
end

def start_command
cmd = "#{@binary_path} -d start -logFile '#{@logfile}' #{@folder_flag} #{@key} #{@folder_path} #{@force_local_flag}"
# Prevent Ruby's default #inspect from dumping @key when a Local instance is
# logged or included in an exception payload (CWE-312).
def inspect
redacted = instance_variables.map do |var|
value = var == :@key && !@key.to_s.empty? ? "[REDACTED]" : instance_variable_get(var)
"#{var}=#{value.inspect}"
end.join(", ")
"#<#{self.class}:0x#{format('%016x', object_id << 1)} #{redacted}>"
end

def start_command(redact = false)
key = redact && !@key.to_s.empty? ? "[REDACTED]" : @key
cmd = "#{@binary_path} -d start -logFile '#{@logfile}' #{@folder_flag} #{key} #{@folder_path} #{@force_local_flag}"
cmd += " -localIdentifier #{@local_identifier_flag}" if @local_identifier_flag
cmd += " #{@only_flag} #{@only_automate_flag}"
cmd += " -proxyHost #{@proxy_host}" if @proxy_host
Expand Down
21 changes: 21 additions & 0 deletions test/browserstack-local-test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,27 @@ def test_hosts
assert_match /localhost\,8080\,0/, @bs_local.command
end

# Regression for CWE-312: the public #command accessor must NOT expose the
# access key — callers routinely log it to CI output / APM / error trackers.
def test_command_redacts_access_key
bs = BrowserStack::Local.new("MY_SECRET_ACCESS_KEY")
refute_match /MY_SECRET_ACCESS_KEY/, bs.command
assert_match /\[REDACTED\]/, bs.command
end

# The real key must still reach the binary on the execution path.
def test_start_command_keeps_key_for_execution
bs = BrowserStack::Local.new("MY_SECRET_ACCESS_KEY")
assert_match /MY_SECRET_ACCESS_KEY/, bs.start_command
end

# Regression for CWE-312: default object inspection must not dump the key.
def test_inspect_redacts_access_key
bs = BrowserStack::Local.new("MY_SECRET_ACCESS_KEY")
refute_match /MY_SECRET_ACCESS_KEY/, bs.inspect
assert_match /\[REDACTED\]/, bs.inspect
end

def teardown
@bs_local.stop
end
Expand Down
Loading