Skip to content

Commit

Permalink
browser monitoring injection logic hardening
Browse files Browse the repository at this point in the history
These changes introduce hardening to the logic that determines whether
or not to inject the browser agent script tag.

In particular, the following 2 issues have been addressed:

1. If either `#traced_call` or `#should_instrument?` encounter any
   exceptions, these exceptions should be handled and logged without
   impacting the observed web application.
2. Allow a response headers hash to use symbols for values.

Closes #2462
  • Loading branch information
fallwith committed Feb 27, 2024
1 parent 414ddad commit 799d12b
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 4 deletions.
15 changes: 11 additions & 4 deletions lib/new_relic/rack/browser_monitoring.rb
Expand Up @@ -23,8 +23,8 @@ class BrowserMonitoring < AgentMiddleware
CONTENT_TYPE = 'Content-Type'.freeze
CONTENT_DISPOSITION = 'Content-Disposition'.freeze
CONTENT_LENGTH = 'Content-Length'.freeze
ATTACHMENT = 'attachment'.freeze
TEXT_HTML = 'text/html'.freeze
ATTACHMENT = /attachment/.freeze
TEXT_HTML = %r{text/html}.freeze

BODY_START = '<body'.freeze
HEAD_START = '<head'.freeze
Expand Down Expand Up @@ -56,6 +56,9 @@ def traced_call(env)
else
result
end
rescue StandardError => e
NewRelic::Agent.logger.error("RUM instrumentation traced call failed on exception: #{e.class} - #{e.message}")
result
end

def should_instrument?(env, status, headers)
Expand All @@ -65,6 +68,10 @@ def should_instrument?(env, status, headers)
html?(headers) &&
!attachment?(headers) &&
!streaming?(env, headers)
rescue StandardError => e
NewRelic::Agent.logger.error('RUM instrumentation applicability check failed on exception:' \
"#{e.class} - #{e.message}")
false
end

private
Expand Down Expand Up @@ -100,11 +107,11 @@ def modify_source(source, js_to_inject)

def html?(headers)
# needs else branch coverage
headers[CONTENT_TYPE] && headers[CONTENT_TYPE].include?(TEXT_HTML) # rubocop:disable Style/SafeNavigation
headers[CONTENT_TYPE]&.match?(TEXT_HTML)
end

def attachment?(headers)
headers[CONTENT_DISPOSITION]&.include?(ATTACHMENT)
headers[CONTENT_DISPOSITION]&.match?(ATTACHMENT)
end

def streaming?(env, headers)
Expand Down
26 changes: 26 additions & 0 deletions test/new_relic/rack/browser_monitoring_test.rb
Expand Up @@ -222,6 +222,32 @@ def test_content_length_set_when_response_is_nil
assert_equal '0', headers['Content-Length']
end

# introduce an exception in the middle of #traced_call and verify that
# we did not crash but instead carried on with a response
def test_traced_call_method_cannot_crash_the_observed_application
NewRelic::Agent.stub :browser_timing_header, -> { raise 'kaboom' } do
result = get('/')

assert result
end
end

# give #should_instrument? a bogus int hash value guaranteed to raise an
# exception when `#match?` is called on it, and ensure that the error
# is caught and a boolean value still returned
def test_should_instrument_method_cannot_crash_the_observed_application
phony_logger = Minitest::Mock.new
phony_logger.expect :error, nil, [/applicability/]
NewRelic::Agent.stub :logger, phony_logger do
should = app.should_instrument?({}, 200, {'Content-Type' => 1138})

refute(should, 'Expected a #should_instrument? to handle errors and produce a false result')
end
phony_logger.verify
end

private

def headers_from_request(headers, content)
content = Array(content) if content

Expand Down

0 comments on commit 799d12b

Please sign in to comment.