newrelic_security 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/pr_ci.yml +2 -2
  3. data/.github/workflows/release.yml +1 -1
  4. data/.github/workflows/rubocop.yml +1 -1
  5. data/CHANGELOG.md +28 -0
  6. data/Gemfile_test +3 -0
  7. data/README.md +1 -0
  8. data/lib/newrelic_security/agent/agent.rb +3 -1
  9. data/lib/newrelic_security/agent/configuration/manager.rb +16 -2
  10. data/lib/newrelic_security/agent/control/application_runtime_error.rb +1 -1
  11. data/lib/newrelic_security/agent/control/collector.rb +7 -1
  12. data/lib/newrelic_security/agent/control/control_command.rb +2 -1
  13. data/lib/newrelic_security/agent/control/error_reporting.rb +8 -6
  14. data/lib/newrelic_security/agent/control/event.rb +1 -0
  15. data/lib/newrelic_security/agent/control/event_processor.rb +20 -14
  16. data/lib/newrelic_security/agent/control/event_subscriber.rb +5 -1
  17. data/lib/newrelic_security/agent/control/health_check.rb +1 -0
  18. data/lib/newrelic_security/agent/control/http_context.rb +2 -1
  19. data/lib/newrelic_security/agent/control/iast_client.rb +1 -1
  20. data/lib/newrelic_security/agent/control/reflected_xss.rb +3 -4
  21. data/lib/newrelic_security/agent/control/websocket_client.rb +53 -16
  22. data/lib/newrelic_security/agent/utils/agent_utils.rb +14 -10
  23. data/lib/newrelic_security/instrumentation-security/graphql/chain.rb +26 -0
  24. data/lib/newrelic_security/instrumentation-security/graphql/instrumentation.rb +28 -0
  25. data/lib/newrelic_security/instrumentation-security/graphql/prepend.rb +18 -0
  26. data/lib/newrelic_security/instrumentation-security/io/chain.rb +2 -2
  27. data/lib/newrelic_security/instrumentation-security/io/prepend.rb +1 -1
  28. data/lib/newrelic_security/instrumentation-security/rack/chain.rb +24 -0
  29. data/lib/newrelic_security/instrumentation-security/rack/instrumentation.rb +44 -0
  30. data/lib/newrelic_security/instrumentation-security/rack/prepend.rb +18 -0
  31. data/lib/newrelic_security/version.rb +1 -1
  32. data/lib/newrelic_security/websocket-client-simple/client.rb +5 -1
  33. metadata +11 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 82cddb6a77c7bac2c46e31b5e0577dd346aa0e78b919fc8fb3c2765a7192c0a6
4
- data.tar.gz: cb8f47120816909cb30ae0d16f9c6c4da3f684a8497747ccab3bbf12aa36e501
3
+ metadata.gz: 90213be8f4f16d3ac2402f570f734466927d341581a73e3382f8d843d0a4486e
4
+ data.tar.gz: a329513f2be86620ecccd38f2811b842b897028c06027e47930b3dea4e8b7b4c
5
5
  SHA512:
6
- metadata.gz: 96abdbedd151b564c30d2ccafc1e98b46185130d6028634877ef0a42c1dc8ff775cf5affbcb7fdd7eee305bbb5ca33a266dfd82ebd5b7f0d2f6e396687af7897
7
- data.tar.gz: a69f0fcaf8a427a4ec169ed7dd33952ee88e723a89281a2e9b46841029898fdea6a21748290e2058e861629e368da84b0b6d317181248b33f88a3a33bc5049e2
6
+ metadata.gz: 565fbe75ccb52096f5e1e1549f5dfdea51f96435a2ee0a8819723e1c996c731f6090a6edb3bb1673318a9273288b031a3645e7d64f81a56cd0212181d64647e4
7
+ data.tar.gz: d176f0228f7f448e2fd08abd28906f59c4e0cac88abcd41bd7a2c02b30c427a78c990cc08a41d3626d5e4ec1cb62809cc2d029bcce7b8f88aa20736c883373c6
@@ -22,7 +22,7 @@ jobs:
22
22
  run: sudo apt-get update; sudo apt-get install -y --no-install-recommends libcurl4-nss-dev libxslt1-dev libc6-dev openjdk-11-jdk
23
23
 
24
24
  - name: Install Ruby ${{ matrix.ruby-version }}
25
- uses: ruby/setup-ruby@cacc9f1c0b3f4eb8a16a6bb0ed10897b43b9de49 # tag v1.176.0
25
+ uses: ruby/setup-ruby@086ffb1a2090c870a3f881cc91ea83aa4243d408 # v1.195.0
26
26
  with:
27
27
  ruby-version: ${{ matrix.ruby-version }}
28
28
 
@@ -53,7 +53,7 @@ jobs:
53
53
  - name: Configure git
54
54
  run: 'git config --global init.defaultBranch main'
55
55
  - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # tag v4.1.2
56
- - uses: ruby/setup-ruby@cacc9f1c0b3f4eb8a16a6bb0ed10897b43b9de49 # tag v1.176.0
56
+ - uses: ruby/setup-ruby@086ffb1a2090c870a3f881cc91ea83aa4243d408 # v1.195.0
57
57
  with:
58
58
  ruby-version: '3.1'
59
59
  - run: bundle
@@ -16,7 +16,7 @@ jobs:
16
16
  with:
17
17
  fetch-depth: 0
18
18
 
19
- - uses: ruby/setup-ruby@cacc9f1c0b3f4eb8a16a6bb0ed10897b43b9de49 # tag v1.176.0
19
+ - uses: ruby/setup-ruby@086ffb1a2090c870a3f881cc91ea83aa4243d408 # v1.195.0
20
20
  with:
21
21
  ruby-version: 3.2
22
22
 
@@ -10,7 +10,7 @@ jobs:
10
10
  - name: Configure git
11
11
  run: 'git config --global init.defaultBranch main'
12
12
  - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # tag v4.1.2
13
- - uses: ruby/setup-ruby@cacc9f1c0b3f4eb8a16a6bb0ed10897b43b9de49 # tag v1.176.0
13
+ - uses: ruby/setup-ruby@086ffb1a2090c870a3f881cc91ea83aa4243d408 # v1.195.0
14
14
  with:
15
15
  ruby-version: '3.3'
16
16
  - run: bundle
data/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # New Relic Ruby Security Agent Release Notes
2
2
 
3
+ ## v0.4.0
4
+
5
+ Version 0.4.0 introuduces Rack framework support, GraphQL support & CI/CD integration as part of security. Updated json_version: **1.2.9**
6
+
7
+ - Feature: Rack framework support [PR#149](https://github.com/newrelic/csec-ruby-agent/pull/149)
8
+
9
+ - Feature: GraphQL support [PR#133](https://github.com/newrelic/csec-ruby-agent/pull/133)
10
+
11
+ - Feature: CI/CD integration support [PR#146](https://github.com/newrelic/csec-ruby-agent/pull/146)
12
+
13
+ - Bugfix: Fix for Agent is sending 404 http error in runtime error json [PR#147](https://github.com/newrelic/csec-ruby-agent/pull/147)
14
+
15
+ - Bugfix: Fix for Exception occurred in generating unhandled exception in Sinatra jruby app [PR#150](https://github.com/newrelic/csec-ruby-agent/pull/150)
16
+
17
+ - Bugfix: Fix for Do not send empty url endpoint json & Reconnect in random interval between 5-15 secs [PR#151](https://github.com/newrelic/csec-ruby-agent/pull/151)
18
+
19
+ - Bugfix: Fix for Wrong formated critical error message [PR#148](https://github.com/newrelic/csec-ruby-agent/pull/148)
20
+
21
+ - BugFix: Fix for Vulnerability is missed in text/json because of small stackTrace [PR#155](https://github.com/newrelic/csec-ruby-agent/pull/155)
22
+
23
+ - BugFix: Fix for API endpoints not detected in grape framework in JRuby environment [PR#158](https://github.com/newrelic/csec-ruby-agent/pull/158)
24
+
25
+ - BugFix: Fix for XML content type vulnerability not detected [PR#159](https://github.com/newrelic/csec-ruby-agent/pull/159)
26
+
27
+ - BugFix: Fix for Concurrency issue observed in JRuby environment, BufferOverflowException observed in writing events to websocket [PR#161](https://github.com/newrelic/csec-ruby-agent/pull/161)
28
+
29
+ - Memory usage optimisations [PR#153](https://github.com/newrelic/csec-ruby-agent/pull/153)
30
+
3
31
  ## v0.3.0
4
32
 
5
33
  Version 0.3.0 introduces more control on IAST scanning through new configs(exclude_from_iast_scan, scan_schedule & scan_controllers) and
data/Gemfile_test CHANGED
@@ -13,7 +13,9 @@ gem 'rubocop'
13
13
  gem 'rubocop-minitest'
14
14
  gem 'rubocop-rake'
15
15
  gem 'simplecov'
16
+ gem 'concurrent-ruby', '1.3.4'
16
17
  gem 'railties'
18
+ gem 'ffi', '1.15.5' if RUBY_VERSION < '3.1.0'
17
19
  if RUBY_VERSION >= '3.0.0'
18
20
  gem 'rails', '~>6.0.0'
19
21
  elsif RUBY_VERSION < '2.5.0'
@@ -23,6 +25,7 @@ else
23
25
  end
24
26
  gem 'loofah', '~> 2.19.0'
25
27
  gem 'sinatra'
28
+ gem 'tilt', '~> 2.4.0' if RUBY_VERSION < '2.5.0'
26
29
  gem 'padrino'
27
30
  gem 'grape'
28
31
  gem 'roda'
data/README.md CHANGED
@@ -46,6 +46,7 @@ The newrelic_security must be explicitly enabled in order to perform IAST analys
46
46
  - Padrino: 0.15 or higher
47
47
  - Grape: 1.2 or higher
48
48
  - Roda: 3.19 or higher
49
+ - Rack: 1.6 or higher
49
50
  - gRPC: 1 or higher
50
51
  ### Web servers
51
52
  - Puma: 3 or higher
@@ -62,6 +62,8 @@ module NewRelic::Security
62
62
  end
63
63
 
64
64
  def shutdown_security_agent
65
+ NewRelic::Security::Agent.logger.info "Flushing eventQ (#{NewRelic::Security::Agent.agent.event_processor.eventQ.size} events) and closing websocket connection"
66
+ NewRelic::Security::Agent.agent.event_processor&.eventQ&.clear
65
67
  @iast_client&.fuzzQ&.clear
66
68
  @iast_client&.completed_requests&.clear
67
69
  @iast_client&.pending_request_ids&.clear
@@ -80,7 +82,7 @@ module NewRelic::Security
80
82
  end
81
83
 
82
84
  def start_event_processor
83
- @event_processor&.event_dequeue_thread&.kill
85
+ @event_processor&.event_dequeue_threads&.each { |t| t&.kill }
84
86
  @event_processor&.healthcheck_thread&.kill
85
87
  @event_processor = nil
86
88
  @event_processor = NewRelic::Security::Agent::Control::EventProcessor.new
@@ -37,14 +37,17 @@ module NewRelic::Security
37
37
  @cache[:'security.detection.deserialization.enabled'] = ::NewRelic::Agent.config[:'security.detection.deserialization.enabled'].nil? ? true : ::NewRelic::Agent.config[:'security.detection.deserialization.enabled']
38
38
  @cache[:'security.scan_controllers.iast_scan_request_rate_limit'] = ::NewRelic::Agent.config[:'security.scan_controllers.iast_scan_request_rate_limit'].to_i
39
39
  @cache[:framework] = detect_framework
40
+ @cache[:app_class] = detect_app_class if @cache[:framework] == :rack
40
41
  @cache[:'security.application_info.port'] = ::NewRelic::Agent.config[:'security.application_info.port'].to_i
41
42
  @cache[:listen_port] = nil
42
43
  @cache[:process_start_time] = current_time_millis # TODO: Ruby doesn't provide process start time in pure ruby implementation using agent loading time for now.
43
44
  @cache[:traffic_start_time] = nil
44
45
  @cache[:scan_start_time] = nil
46
+ @cache[:'security.scan_controllers.scan_instance_count'] = ::NewRelic::Agent.config[:'security.scan_controllers.scan_instance_count']
47
+ @cache[:'security.iast_test_identifier'] = ::NewRelic::Agent.config[:'security.iast_test_identifier']
45
48
  @cache[:app_root] = NewRelic::Security::Agent::Utils.app_root
46
49
  @cache[:jruby_objectspace_enabled] = false
47
- @cache[:json_version] = :'1.2.8'
50
+ @cache[:json_version] = :'1.2.9'
48
51
  @cache[:'security.exclude_from_iast_scan.api'] = convert_to_regexp_list(::NewRelic::Agent.config[:'security.exclude_from_iast_scan.api'])
49
52
  @cache[:'security.exclude_from_iast_scan.http_request_parameters.header'] = ::NewRelic::Agent.config[:'security.exclude_from_iast_scan.http_request_parameters.header']
50
53
  @cache[:'security.exclude_from_iast_scan.http_request_parameters.query'] = ::NewRelic::Agent.config[:'security.exclude_from_iast_scan.http_request_parameters.query']
@@ -153,6 +156,16 @@ module NewRelic::Security
153
156
  return :sinatra if defined?(::Sinatra)
154
157
  return :roda if defined?(::Roda)
155
158
  return :grape if defined?(::Grape)
159
+ return :rack if defined?(::Rack) && defined?(Rack::Builder)
160
+ end
161
+
162
+ def detect_app_class
163
+ target_class = nil
164
+ ObjectSpace.each_object(::Rack::Builder) do |z| target_class = z.instance_variable_get(:@run).target end
165
+ target_class
166
+ rescue StandardError => exception
167
+ NewRelic::Security::Agent.logger.error "Exception in detect_app_class : #{exception.inspect} #{exception.backtrace}"
168
+ nil
156
169
  end
157
170
 
158
171
  def generate_uuid
@@ -167,7 +180,8 @@ module NewRelic::Security
167
180
  end
168
181
  ::SecureRandom.uuid
169
182
  rescue Exception => exception
170
- NewRelic::Security::Agent.logger.error "Exception in generate_uuid : #{exception.inspect} #{exception.backtrace}"
183
+ NewRelic::Security::Agent.logger.warn "Error in generate_uuid, generating it through default approach : #{exception.inspect} #{exception.backtrace}"
184
+ ::SecureRandom.uuid
171
185
  end
172
186
 
173
187
  def create_uuid
@@ -81,7 +81,7 @@ module NewRelic::Security
81
81
 
82
82
  def generate_trace_id(ctxt, category)
83
83
  @exception[:stackTrace]
84
- method, route = ctxt.route.split(AT_THE_RATE) if ctxt.route
84
+ method, route = ctxt.route.split(AT_THE_RATE) if ctxt&.route
85
85
  if @exception[:stackTrace]
86
86
  ::Digest::SHA256.hexdigest("#{@exception[:stackTrace].join(PIPE)}#{category}#{route}#{method}").to_s
87
87
  else
@@ -45,6 +45,7 @@ module NewRelic::Security
45
45
  # In rails 5 method name keeps chaning for same api call (ex: _app_views_sqli_sqlinjectionattackcase_html_erb__1999281606898621405_2624809100).
46
46
  # Hence, considering only frame absolute_path & lineno for apiId calculation.
47
47
  user_frame_index = get_user_frame_index(stk)
48
+ route = route&.gsub(/\d+/, EMPTY_STRING) if NewRelic::Security::Agent.config[:framework] == :rack || NewRelic::Security::Agent.config[:framework] == :roda
48
49
  event.apiId = "#{case_type}-#{calculate_api_id(stk[0..user_frame_index].map { |frame| "#{frame.absolute_path}:#{frame.lineno}" }, event.httpRequest[:method], route)}"
49
50
  stk.delete_if { |frame| frame.path.match?(/newrelic_security/) || frame.path.match?(/new_relic/) }
50
51
  user_frame_index = get_user_frame_index(stk)
@@ -60,7 +61,7 @@ module NewRelic::Security
60
61
  event.lineNumber = stk[0].lineno
61
62
  end
62
63
  event.stacktrace = stk[0..user_frame_index].map(&:to_s)
63
- NewRelic::Security::Agent.agent.event_processor.send_event(event)
64
+ NewRelic::Security::Agent.agent.event_processor&.send_event(event)
64
65
  if event.httpRequest[:headers].key?(NR_CSEC_FUZZ_REQUEST_ID) && event.apiId == event.httpRequest[:headers][NR_CSEC_FUZZ_REQUEST_ID].split(COLON_IAST_COLON)[0] && NewRelic::Security::Agent.agent.iast_client.completed_requests[event.parentId]
65
66
  NewRelic::Security::Agent.agent.iast_client.completed_requests[event.parentId] << event.id
66
67
  end
@@ -73,6 +74,10 @@ module NewRelic::Security
73
74
  else
74
75
  NewRelic::Security::Agent.agent.rasp_event_stats.error_count.increment
75
76
  end
77
+ ensure
78
+ event = nil
79
+ stk = nil
80
+ route = nil
76
81
  end
77
82
 
78
83
  private
@@ -80,6 +85,7 @@ module NewRelic::Security
80
85
  def get_user_frame_index(stk)
81
86
  return -1 if NewRelic::Security::Agent.config[:app_root].nil?
82
87
  stk.each_with_index do |val, index|
88
+ next if stk[index + 1] && stk[index + 1].path.start_with?(NewRelic::Security::Agent.config[:app_root])
83
89
  return index if val.path.start_with?(NewRelic::Security::Agent.config[:app_root])
84
90
  end
85
91
  return -1
@@ -1,4 +1,5 @@
1
1
  require 'json'
2
+ require 'securerandom'
2
3
 
3
4
  module NewRelic::Security
4
5
  module Agent
@@ -93,7 +94,7 @@ module NewRelic::Security
93
94
  NewRelic::Security::Agent.agent.iast_client.completed_requests.clear if NewRelic::Security::Agent.agent.iast_client
94
95
  NewRelic::Security::Agent.agent.iast_client.pending_request_ids.clear if NewRelic::Security::Agent.agent.iast_client
95
96
  NewRelic::Security::Agent.config.disable_security
96
- Thread.new { NewRelic::Security::Agent.agent.reconnect(0) }
97
+ Thread.new { NewRelic::Security::Agent.agent.reconnect(SecureRandom.random_number(10) + 5) }
97
98
  end
98
99
 
99
100
  def current_time_millis
@@ -40,22 +40,24 @@ module NewRelic::Security
40
40
  # TODO: when do refactoring of ctxt.route, use both route and method to generate key
41
41
  ctxt.route&.+ response_code.to_s
42
42
  else
43
- application_runtime_error.exception[:type] + application_runtime_error.exception[:stackTrace][0]
43
+ application_runtime_error.exception[:type]&.+ application_runtime_error.exception[:stackTrace]&.first
44
44
  end
45
+ return if key.nil? || key.empty?
45
46
  application_runtime_error.counter = @exceptions_map[key].counter + 1 if @exceptions_map.key?(key)
46
47
  @exceptions_map[key] = application_runtime_error
47
- rescue Exception => exception
48
+ rescue StandardError => exception
48
49
  NewRelic::Security::Agent.logger.error "Exception in generating unhandled exception: #{exception.inspect} #{exception.backtrace}\n"
49
50
  end
50
51
 
51
- def extract_noticed_error(current_transaction, ctxt, response_code)
52
+ def extract_noticed_error(current_transaction, ctxt, http_response_code)
53
+ return if http_response_code&.between?(400, 499)
52
54
  # TODO: Below operation is expensive, talk to APM to get optimized way to do this
53
55
  current_transaction.exceptions.each do |_, span|
54
56
  current_transaction.segments.each do |segment|
55
- generate_unhandled_exception(segment.noticed_error, ctxt, response_code) if span[:span_id] == segment.guid
57
+ generate_unhandled_exception(segment.noticed_error, ctxt, http_response_code) if span[:span_id] == segment.guid
56
58
  end
57
59
  end
58
- rescue Exception => exception
60
+ rescue StandardError => exception
59
61
  NewRelic::Security::Agent.logger.error "Exception in extract_noticed_error: #{exception.inspect} #{exception.backtrace}\n"
60
62
  end
61
63
 
@@ -64,7 +66,7 @@ module NewRelic::Security
64
66
  if current_transaction.exceptions.empty? && http_response_code&.between?(500, 599)
65
67
  generate_unhandled_exception(nil, ctxt, response_code)
66
68
  else
67
- extract_noticed_error(current_transaction, ctxt, response_code) unless current_transaction.exceptions.empty?
69
+ extract_noticed_error(current_transaction, ctxt, http_response_code) unless current_transaction.exceptions.empty?
68
70
  end
69
71
  end
70
72
 
@@ -104,6 +104,7 @@ module NewRelic::Security
104
104
  http_request[:headers] = ctxt.headers
105
105
  http_request[:contentType] = ctxt.req[CONTENT_TYPE] if ctxt.req.has_key?(CONTENT_TYPE)
106
106
  http_request[:headers][CONTENT_TYPE1] = ctxt.req[CONTENT_TYPE] if ctxt.req.has_key?(CONTENT_TYPE)
107
+ http_request[:customDataType] = ctxt.custom_data_type
107
108
  http_request[:dataTruncated] = ctxt.data_truncated
108
109
  @httpRequest = http_request
109
110
  @metaData[:isClientDetectedFromXFF] = ctxt.headers.has_key?(X_FORWARDED_FOR) ? true : false
@@ -9,7 +9,7 @@ module NewRelic::Security
9
9
 
10
10
  class EventProcessor
11
11
 
12
- attr_accessor :eventQ, :event_dequeue_thread, :healthcheck_thread
12
+ attr_accessor :eventQ, :event_dequeue_threads, :healthcheck_thread
13
13
 
14
14
  def initialize
15
15
  @first_event = true
@@ -24,18 +24,22 @@ module NewRelic::Security
24
24
  NewRelic::Security::Agent.init_logger.info "[STEP-3] => Gathering information about the application"
25
25
  app_info = NewRelic::Security::Agent::Control::AppInfo.new
26
26
  app_info.update_app_info
27
- NewRelic::Security::Agent.logger.info "Sending application info : #{app_info.to_json}"
28
- NewRelic::Security::Agent.init_logger.info "Sending application info : #{app_info.to_json}"
27
+ app_info_json = app_info.to_json
28
+ NewRelic::Security::Agent.logger.info "Sending application info : #{app_info_json}"
29
+ NewRelic::Security::Agent.init_logger.info "Sending application info : #{app_info_json}"
29
30
  enqueue(app_info)
30
31
  app_info = nil
32
+ app_info_json = nil
31
33
  end
32
34
 
33
35
  def send_application_url_mappings
34
36
  application_url_mappings = NewRelic::Security::Agent::Control::ApplicationURLMappings.new
35
37
  application_url_mappings.update_application_url_mappings
36
- NewRelic::Security::Agent.logger.info "Sending application URL Mappings : #{application_url_mappings.to_json}"
38
+ application_url_mappings_json = application_url_mappings.to_json
39
+ NewRelic::Security::Agent.logger.info "Sending application URL Mappings : #{application_url_mappings_json}"
37
40
  enqueue(application_url_mappings)
38
41
  application_url_mappings = nil
42
+ application_url_mappings_json = nil
39
43
  end
40
44
 
41
45
  def send_event(event)
@@ -65,7 +69,7 @@ module NewRelic::Security
65
69
  if exc
66
70
  exception = {}
67
71
  exception[:message] = exc.message
68
- exception[:cause] = exc.cause
72
+ exception[:cause] = { :message => exc.cause }
69
73
  exception[:stackTrace] = exc.backtrace.map(&:to_s)
70
74
  end
71
75
  critical_message = NewRelic::Security::Agent::Control::CriticalMessage.new(message, level, caller, thread_name, exception)
@@ -87,15 +91,17 @@ module NewRelic::Security
87
91
  private
88
92
 
89
93
  def create_dequeue_threads
90
- # TODO: Create 3 or more consumers for event sending
91
- @event_dequeue_thread = Thread.new do
92
- Thread.current.name = "newrelic_security_event_thread"
93
- loop do
94
- begin
95
- data_to_be_sent = @eventQ.pop
96
- NewRelic::Security::Agent::Control::WebsocketClient.instance.send(data_to_be_sent)
97
- rescue => exception
98
- NewRelic::Security::Agent.logger.error "Exception in event pop operation : #{exception.inspect}"
94
+ @event_dequeue_threads = []
95
+ 3.times do |t|
96
+ @event_dequeue_threads<< Thread.new do
97
+ Thread.current.name = "newrelic_security_event_thread-#{t}"
98
+ loop do
99
+ begin
100
+ data_to_be_sent = @eventQ.pop
101
+ NewRelic::Security::Agent::Control::WebsocketClient.instance.send(data_to_be_sent)
102
+ rescue => exception
103
+ NewRelic::Security::Agent.logger.error "Exception in event pop operation : #{exception.inspect}"
104
+ end
99
105
  end
100
106
  end
101
107
  end
@@ -8,7 +8,11 @@ module NewRelic::Security
8
8
  NewRelic::Security::Agent.init_logger.info "NewRelic server_source_configuration_added for pid : #{Process.pid}, Parent Pid : #{Process.ppid}"
9
9
  NewRelic::Security::Agent.config.update_server_config
10
10
  if NewRelic::Security::Agent.config[:'security.enabled'] && !NewRelic::Security::Agent.config[:high_security]
11
- Thread.new { NewRelic::Security::Agent.agent.scan_scheduler.init_via_scan_scheduler }
11
+ NewRelic::Security::Agent.agent.event_processor&.event_dequeue_threads&.each { |t| t&.kill }
12
+ NewRelic::Security::Agent.agent.event_processor = nil
13
+ @csec_agent_main_thread&.kill
14
+ @csec_agent_main_thread = nil
15
+ @csec_agent_main_thread = Thread.new { NewRelic::Security::Agent.agent.scan_scheduler.init_via_scan_scheduler }
12
16
  else
13
17
  NewRelic::Security::Agent.logger.info "New Relic Security is disabled by one of the user provided config `security.enabled` or `high_security`."
14
18
  NewRelic::Security::Agent.init_logger.info "New Relic Security is disabled by one of the user provided config `security.enabled` or `high_security`."
@@ -38,6 +38,7 @@ module NewRelic::Security
38
38
  @procStartTime = NewRelic::Security::Agent.config[:process_start_time]
39
39
  @trafficStartedTime = NewRelic::Security::Agent.config[:traffic_start_time]
40
40
  @scanStartTime = NewRelic::Security::Agent.config[:scan_start_time]
41
+ @iastTestIdentifer = NewRelic::Security::Agent.config[:'security.iast_test_identifier']
41
42
  end
42
43
 
43
44
  def as_json
@@ -19,7 +19,7 @@ module NewRelic::Security
19
19
 
20
20
  class HTTPContext
21
21
 
22
- attr_accessor :time_stamp, :req, :method, :headers, :params, :body, :data_truncated, :route, :cache, :fuzz_files, :event_counter, :mutex, :url
22
+ attr_accessor :time_stamp, :req, :method, :headers, :params, :body, :data_truncated, :route, :cache, :fuzz_files, :event_counter, :custom_data_type, :mutex, :url
23
23
 
24
24
  def initialize(env)
25
25
  @time_stamp = current_time_millis
@@ -48,6 +48,7 @@ module NewRelic::Security
48
48
  @data_truncated = @body && @body.size >= REQUEST_BODY_LIMIT * 1024
49
49
  strio&.rewind
50
50
  @body = @body.force_encoding(Encoding::UTF_8) if @body.is_a?(String)
51
+ @custom_data_type = {}
51
52
  @cache = Hash.new
52
53
  @fuzz_files = ::Set.new
53
54
  @event_counter = 0
@@ -99,7 +99,7 @@ module NewRelic::Security
99
99
  end
100
100
  iast_data_transfer_request.pendingRequestIds = pending_request_ids.to_a
101
101
  iast_data_transfer_request.completedRequests = completed_requests
102
- NewRelic::Security::Agent.agent.event_processor&.send_iast_data_transfer_request(iast_data_transfer_request)
102
+ NewRelic::Security::Agent.agent.event_processor&.send_iast_data_transfer_request(iast_data_transfer_request) if NewRelic::Security::Agent::Control::WebsocketClient.instance.is_open?
103
103
  end
104
104
  end
105
105
  end
@@ -108,8 +108,8 @@ module NewRelic::Security
108
108
  processed_data.add(body)
109
109
  end
110
110
  when APPLICATION_XML
111
- # Unescaping of xml data is remaining
112
- processed_data.add(body)
111
+ xml_data = ::CGI.unescapeHTML(body)
112
+ processed_data.add(xml_data)
113
113
  when APPLICATION_X_WWW_FORM_URLENCODED
114
114
  body = ::CGI.unescape(body, UTF_8)
115
115
  processed_data.add(body)
@@ -134,7 +134,7 @@ module NewRelic::Security
134
134
  # do while loop in java code here
135
135
  old_processed_body = processed_body
136
136
  body = ::JSON.parse(processed_body)
137
- processed_data.add(body) if old_processed_body != body && body.to_s.include?(LESS_THAN)
137
+ processed_data.add(body.to_s) if old_processed_body != body && body.to_s.include?(LESS_THAN)
138
138
  when APPLICATION_XML
139
139
  # Unescaping of xml data is remaining
140
140
  processed_data.add(processed_data)
@@ -176,7 +176,6 @@ module NewRelic::Security
176
176
  start_pos = 0
177
177
  tmp_curr_pos = 0
178
178
  tmp_start_pos = 0
179
-
180
179
  while curr_pos < data.length
181
180
  matcher = TAG_NAME_REGEX.match(data, curr_pos)
182
181
  is_attack_construct = false
@@ -23,6 +23,8 @@ module NewRelic::Security
23
23
  NR_CSEC_IAST_DATA_TRANSFER_MODE = 'NR-CSEC-IAST-DATA-TRANSFER-MODE'
24
24
  NR_CSEC_IGNORED_VUL_CATEGORIES = 'NR-CSEC-IGNORED-VUL-CATEGORIES'
25
25
  NR_CSEC_PROCESS_START_TIME = 'NR-CSEC-PROCESS-START-TIME'
26
+ NR_CSEC_IAST_SCAN_INSTANCE_COUNT = 'NR-CSEC-IAST-SCAN-INSTANCE-COUNT'
27
+ NR_CSEC_IAST_TEST_IDENTIFIER = 'NR-CSEC-IAST-TEST-IDENTIFIER'
26
28
 
27
29
  class WebsocketClient
28
30
  include Singleton
@@ -47,6 +49,11 @@ module NewRelic::Security
47
49
  headers[NR_CSEC_IAST_DATA_TRANSFER_MODE] = PULL
48
50
  headers[NR_CSEC_IGNORED_VUL_CATEGORIES] = ingnored_vul_categories.join(COMMA)
49
51
  headers[NR_CSEC_PROCESS_START_TIME] = NewRelic::Security::Agent.config[:process_start_time]
52
+ headers[NR_CSEC_IAST_SCAN_INSTANCE_COUNT] = NewRelic::Security::Agent.config[:'security.scan_controllers.scan_instance_count']
53
+ if NewRelic::Security::Agent.config[:'security.iast_test_identifier'] && !NewRelic::Security::Agent.config[:'security.iast_test_identifier'].empty?
54
+ headers[NR_CSEC_IAST_TEST_IDENTIFIER] = NewRelic::Security::Agent.config[:'security.iast_test_identifier']
55
+ headers[NR_CSEC_IAST_SCAN_INSTANCE_COUNT] = 1
56
+ end
50
57
 
51
58
  begin
52
59
  cert_store = ::OpenSSL::X509::Store.new
@@ -54,13 +61,16 @@ module NewRelic::Security
54
61
  NewRelic::Security::Agent.logger.info "Websocket connection URL : #{NewRelic::Security::Agent.config[:validator_service_url]}"
55
62
  connection = NewRelic::Security::WebSocket::Client::Simple.connect NewRelic::Security::Agent.config[:validator_service_url], headers: headers, cert_store: cert_store
56
63
  @ws = connection
64
+ @mutex = Mutex.new
57
65
 
58
66
  connection.on :open do
67
+ headers = nil
59
68
  NewRelic::Security::Agent.logger.debug "Websocket connected with IC, AgentEventMachine #{NewRelic::Security::Agent::Utils.filtered_log(connection.inspect)}"
60
69
  NewRelic::Security::Agent.init_logger.info "[STEP-4] => Web socket connection to SaaS validator established successfully"
61
70
  NewRelic::Security::Agent.agent.event_processor.send_app_info
62
71
  NewRelic::Security::Agent.agent.event_processor.send_application_url_mappings
63
72
  NewRelic::Security::Agent.config.enable_security
73
+ NewRelic::Security::Agent::Control::WebsocketClient.instance.start_ping_thread
64
74
  end
65
75
 
66
76
  connection.on :message do |msg|
@@ -75,13 +85,14 @@ module NewRelic::Security
75
85
  connection.on :close do |e|
76
86
  NewRelic::Security::Agent.logger.info "Closing websocket connection : #{e.inspect}\n"
77
87
  NewRelic::Security::Agent.config.disable_security
78
- Thread.new { NewRelic::Security::Agent.agent.reconnect(0) } if e
88
+ reconnect_interval = e.instance_of?(TrueClass) ? 0 : 15
89
+ Thread.new { NewRelic::Security::Agent.agent.reconnect(reconnect_interval) } if e
79
90
  end
80
91
 
81
92
  connection.on :error do |e|
82
93
  NewRelic::Security::Agent.logger.error "Error in websocket connection : #{e.inspect} #{e.backtrace}"
83
94
  ::NewRelic::Agent.notice_error(e)
84
- Thread.new { NewRelic::Security::Agent::Control::WebsocketClient.instance.close(true) }
95
+ Thread.new { NewRelic::Security::Agent::Control::WebsocketClient.instance.close(e) }
85
96
  end
86
97
  rescue Errno::EPIPE => exception
87
98
  NewRelic::Security::Agent.logger.error "Unable to connect to validator_service: #{exception.inspect}"
@@ -107,25 +118,37 @@ module NewRelic::Security
107
118
  end
108
119
 
109
120
  def send(message)
110
- message_json = message.to_json
111
- NewRelic::Security::Agent.logger.debug "Sending #{message.jsonName} : #{message_json}"
112
- res = @ws.send(message_json)
113
- if res && message.jsonName == :Event
114
- NewRelic::Security::Agent.agent.event_sent_count.increment
115
- if NewRelic::Security::Agent::Utils.is_IAST_request?(message.httpRequest[:headers])
116
- NewRelic::Security::Agent.agent.iast_event_stats.sent.increment
117
- else
118
- NewRelic::Security::Agent.agent.rasp_event_stats.sent.increment
121
+ message_json = nil
122
+ begin
123
+ message_json = message.to_json
124
+ NewRelic::Security::Agent.logger.debug "Sending #{message.jsonName} : #{message_json}"
125
+ @mutex.synchronize do
126
+ res = @ws.send(message_json)
127
+ if res && message.jsonName == :Event
128
+ NewRelic::Security::Agent.agent.event_sent_count.increment
129
+ if NewRelic::Security::Agent::Utils.is_IAST_request?(message.httpRequest[:headers])
130
+ NewRelic::Security::Agent.agent.iast_event_stats.sent.increment
131
+ else
132
+ NewRelic::Security::Agent.agent.rasp_event_stats.sent.increment
133
+ end
134
+ end
135
+ NewRelic::Security::Agent.agent.exit_event_stats.sent.increment if res && message.jsonName == :'exit-event'
119
136
  end
137
+ rescue Exception => exception
138
+ NewRelic::Security::Agent.logger.error "Exception in sending message : #{exception.inspect} #{exception.backtrace}, message: #{message_json}"
139
+ NewRelic::Security::Agent.agent.event_drop_count.increment if message.jsonName == :Event
140
+ NewRelic::Security::Agent.agent.event_processor.send_critical_message(exception.message, "SEVERE", caller_locations[0].to_s, Thread.current.name, exception)
141
+ ensure
142
+ message_json = nil
120
143
  end
121
- NewRelic::Security::Agent.agent.exit_event_stats.sent.increment if res && message.jsonName == :'exit-event'
122
- rescue Exception => exception
123
- NewRelic::Security::Agent.logger.error "Exception in sending message : #{exception.inspect} #{exception.backtrace}"
124
- NewRelic::Security::Agent.agent.event_drop_count.increment if message.jsonName == :Event
125
- NewRelic::Security::Agent.agent.event_processor.send_critical_message(exception.message, "SEVERE", caller_locations[0].to_s, Thread.current.name, exception)
126
144
  end
127
145
 
128
146
  def close(reconnect = true)
147
+ NewRelic::Security::Agent.config.disable_security
148
+ NewRelic::Security::Agent.logger.info "Flushing eventQ (#{NewRelic::Security::Agent.agent.event_processor.eventQ.size} events) and closing websocket connection"
149
+ NewRelic::Security::Agent.agent.event_processor&.eventQ&.clear
150
+ @iast_client&.iast_data_transfer_request_processor_thread&.kill
151
+ stop_ping_thread
129
152
  @ws.close(reconnect) if @ws
130
153
  end
131
154
 
@@ -134,8 +157,22 @@ module NewRelic::Security
134
157
  false
135
158
  end
136
159
 
160
+ def start_ping_thread
161
+ @ping_thread = Thread.new do
162
+ loop do
163
+ sleep 30
164
+ @ws.send(EMPTY_STRING, :type => :ping)
165
+ end
166
+ end
167
+ end
168
+
137
169
  private
138
170
 
171
+ def stop_ping_thread
172
+ @ping_thread&.kill
173
+ @ping_thread = nil
174
+ end
175
+
139
176
  def ingnored_vul_categories
140
177
  list = []
141
178
  list << FILE_OPERATION << FILE_INTEGRITY if NewRelic::Security::Agent.config[:'security.exclude_from_iast_scan.iast_detection_category.invalid_file_access']
@@ -114,12 +114,14 @@ module NewRelic::Security
114
114
  end
115
115
  end
116
116
  when :grape
117
- ObjectSpace.each_object(::Grape::Endpoint) { |z|
118
- z.instance_variable_get(:@routes)&.each { |route|
119
- http_method = route.instance_variable_get(:@request_method) || route.instance_variable_get(:@options)[:method]
120
- NewRelic::Security::Agent.agent.route_map << "#{http_method}@#{route.pattern.origin}"
121
- }
122
- }
117
+ if defined?(::Grape::API)
118
+ ObjectSpace.each_object(Class).select { |klass| klass < ::Grape::API }.each do |api_class|
119
+ api_class.routes.each do |route|
120
+ http_method = route.request_method || route.options[:method]
121
+ NewRelic::Security::Agent.agent.route_map << "#{http_method}@#{route.pattern.origin}"
122
+ end
123
+ end
124
+ end
123
125
  when :padrino
124
126
  if router.instance_of?(::Padrino::PathRouter::Router)
125
127
  router.instance_variable_get(:@routes).each do |route|
@@ -127,17 +129,19 @@ module NewRelic::Security
127
129
  end
128
130
  end
129
131
  when :roda
130
- NewRelic::Security::Agent.logger.warn "TODO: Roda is a routing tree web toolkit, which generates route dynamically, hence route extraction is not possible."
132
+ NewRelic::Security::Agent.logger.debug "TODO: Roda is a routing tree web toolkit, which generates route dynamically, hence route extraction is not possible."
131
133
  when :grpc
132
134
  router.owner.superclass.public_instance_methods(false).each do |m|
133
135
  NewRelic::Security::Agent.agent.route_map << "*@/#{router.owner}/#{m}"
134
136
  end
137
+ when :rack
138
+ # TODO: API enpointes(routes) extraction for rack
135
139
  else
136
140
  NewRelic::Security::Agent.logger.error "Unable to get app routes as Framework not detected"
137
141
  end
138
142
  disable_object_space_in_jruby if NewRelic::Security::Agent.config[:jruby_objectspace_enabled]
139
143
  NewRelic::Security::Agent.logger.debug "ALL ROUTES : #{NewRelic::Security::Agent.agent.route_map}"
140
- NewRelic::Security::Agent.agent.event_processor&.send_application_url_mappings
144
+ NewRelic::Security::Agent.agent.event_processor&.send_application_url_mappings unless NewRelic::Security::Agent.agent.route_map.empty?
141
145
  rescue Exception => exception
142
146
  NewRelic::Security::Agent.logger.error "Error in get app routes : #{exception.inspect} #{exception.backtrace}"
143
147
  end
@@ -199,14 +203,14 @@ module NewRelic::Security
199
203
  end
200
204
 
201
205
  def enable_object_space_in_jruby
202
- if RUBY_ENGINE == 'jruby' && !JRuby.objectspace
206
+ if RUBY_ENGINE == 'jruby' && JRuby.respond_to?(:objectspace) && !JRuby.objectspace
203
207
  JRuby.objectspace = true
204
208
  NewRelic::Security::Agent.config.jruby_objectspace_enabled = true
205
209
  end
206
210
  end
207
211
 
208
212
  def disable_object_space_in_jruby
209
- if RUBY_ENGINE == 'jruby' && JRuby.objectspace
213
+ if RUBY_ENGINE == 'jruby' && JRuby.respond_to?(:objectspace) && JRuby.objectspace
210
214
  JRuby.objectspace = false
211
215
  NewRelic::Security::Agent.config.jruby_objectspace_enabled = false
212
216
  end
@@ -0,0 +1,26 @@
1
+ module NewRelic::Security
2
+ module Instrumentation
3
+ module GraphQL
4
+ module Query
5
+ module Executor
6
+ module Chain
7
+ def self.instrument!
8
+ ::GraphQL::Query::Executor.class_eval do
9
+ class << self
10
+ include NewRelic::Security::Instrumentation::GraphQL::Query::Executor
11
+
12
+ alias_method :execute_without_security, :execute
13
+
14
+ def execute
15
+ execute_on_enter { return execute_without_security }
16
+ end
17
+
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,28 @@
1
+ require_relative 'prepend'
2
+ require_relative 'chain'
3
+
4
+ module NewRelic::Security
5
+ module Instrumentation
6
+ module GraphQL::Query::Executor
7
+
8
+ GRAPHQL_QUERY = 'GRAPHQL_QUERY'.freeze
9
+ GRAPHQL_VARIABLE = 'GRAPHQL_VARIABLE'.freeze
10
+ STAR_DOT_QUERY = '*.query'.freeze
11
+ STAR_DOT_VARIABLES = '*.variables'.freeze
12
+
13
+ def execute_on_enter
14
+ NewRelic::Security::Agent.logger.debug "OnEnter : #{self.class}.#{__method__}"
15
+ ctxt = NewRelic::Security::Agent::Control::HTTPContext.get_context
16
+ ctxt.custom_data_type[STAR_DOT_QUERY] = GRAPHQL_QUERY if query.query_string
17
+ ctxt.custom_data_type[STAR_DOT_VARIABLES] = GRAPHQL_VARIABLE if query.instance_variable_get(:@provided_variables)
18
+ rescue => exception
19
+ NewRelic::Security::Agent.logger.error "Exception in hook in #{self.class}.#{__method__}, #{exception.inspect}, #{exception.backtrace}"
20
+ ensure
21
+ yield
22
+ end
23
+
24
+ end
25
+ end
26
+ end
27
+
28
+ NewRelic::Security::Instrumentation::InstrumentationLoader.install_instrumentation(:graphql, ::GraphQL::Query::Executor, ::NewRelic::Security::Instrumentation::GraphQL::Query::Executor)
@@ -0,0 +1,18 @@
1
+ module NewRelic::Security
2
+ module Instrumentation
3
+ module GraphQL
4
+ module Query
5
+ module Executor
6
+ module Prepend
7
+ include NewRelic::Security::Instrumentation::GraphQL::Query::Executor
8
+
9
+ def execute
10
+ execute_on_enter { return super }
11
+ end
12
+
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -99,9 +99,9 @@ module NewRelic::Security
99
99
 
100
100
  alias_method :popen_without_security, :popen
101
101
 
102
- def popen(*var)
102
+ def popen(*var, &block)
103
103
  retval = nil
104
- event = popen_on_enter(*var) { retval = popen_without_security(*var) }
104
+ event = popen_on_enter(*var) { retval = popen_without_security(*var, &block) }
105
105
  popen_on_exit(event) { return retval }
106
106
  end
107
107
  end
@@ -75,7 +75,7 @@ module NewRelic::Security
75
75
  binwrite_on_exit(event, retval) { return retval }
76
76
  end
77
77
 
78
- def popen(*var)
78
+ def popen(*var, &block)
79
79
  retval = nil
80
80
  event = popen_on_enter(*var) { retval = super }
81
81
  popen_on_exit(event) { return retval }
@@ -0,0 +1,24 @@
1
+ module NewRelic::Security
2
+ module Instrumentation
3
+ module Rack
4
+ module Builder
5
+ module Chain
6
+ def self.instrument!
7
+ ::Rack::Builder.class_eval do
8
+ include NewRelic::Security::Instrumentation::Rack::Builder
9
+
10
+ alias_method :call_without_security, :call
11
+
12
+ def call(env)
13
+ retval = nil
14
+ event = call_on_enter(env) { retval = call_without_security(env) }
15
+ call_on_exit(event, retval) { return retval }
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,44 @@
1
+ require_relative 'prepend'
2
+ require_relative 'chain'
3
+
4
+ module NewRelic::Security
5
+ module Instrumentation
6
+ module Rack::Builder
7
+
8
+ def call_on_enter(env)
9
+ event = nil
10
+ NewRelic::Security::Agent.logger.debug "OnEnter : #{self.class}.#{__method__}"
11
+ return unless NewRelic::Security::Agent.config[:enabled]
12
+ NewRelic::Security::Agent.config.update_port = NewRelic::Security::Agent::Utils.app_port(env) unless NewRelic::Security::Agent.config[:listen_port]
13
+ NewRelic::Security::Agent::Utils.get_app_routes(:rack) if NewRelic::Security::Agent.agent.route_map.empty?
14
+ NewRelic::Security::Agent::Control::HTTPContext.set_context(env)
15
+ ctxt = NewRelic::Security::Agent::Control::HTTPContext.get_context
16
+ ctxt.route = "#{env[REQUEST_METHOD]}@#{env[PATH_INFO]}" if ctxt
17
+ NewRelic::Security::Agent::Utils.parse_fuzz_header(NewRelic::Security::Agent::Control::HTTPContext.get_context)
18
+ rescue => exception
19
+ NewRelic::Security::Agent.logger.error "Exception in hook in #{self.class}.#{__method__}, #{exception.inspect}, #{exception.backtrace}"
20
+ ensure
21
+ yield
22
+ return event
23
+ end
24
+
25
+ def call_on_exit(event, retval)
26
+ NewRelic::Security::Agent.logger.debug "OnExit : #{self.class}.#{__method__}"
27
+ # NewRelic::Security::Agent.logger.debug "\n\nHTTP Context : #{::NewRelic::Agent::Tracer.current_transaction.instance_variable_get(:@security_context_data).inspect}\n\n"
28
+ NewRelic::Security::Agent::Control::ReflectedXSS.check_xss(NewRelic::Security::Agent::Control::HTTPContext.get_context, retval) if NewRelic::Security::Agent.config[:'security.detection.rxss.enabled']
29
+ NewRelic::Security::Agent::Utils.delete_created_files(NewRelic::Security::Agent::Control::HTTPContext.get_context)
30
+ NewRelic::Security::Agent.agent.error_reporting&.report_unhandled_or_5xx_exceptions(NewRelic::Security::Agent::Control::HTTPContext.get_current_transaction, NewRelic::Security::Agent::Control::HTTPContext.get_context, retval[0])
31
+ NewRelic::Security::Agent::Control::HTTPContext.reset_context
32
+ NewRelic::Security::Agent.logger.debug "Exit event : #{event}"
33
+ rescue => exception
34
+ NewRelic::Security::Agent.logger.error "Exception in hook in #{self.class}.#{__method__}, #{exception.inspect}, #{exception.backtrace}"
35
+ ensure
36
+ yield
37
+ end
38
+
39
+ end
40
+
41
+ end
42
+ end
43
+
44
+ NewRelic::Security::Instrumentation::InstrumentationLoader.install_instrumentation(:rack, NewRelic::Security::Agent.config[:app_class].class, ::NewRelic::Security::Instrumentation::Rack::Builder) if NewRelic::Security::Agent.config[:framework] == :rack
@@ -0,0 +1,18 @@
1
+ module NewRelic::Security
2
+ module Instrumentation
3
+ module Rack
4
+ module Builder
5
+ module Prepend
6
+ include NewRelic::Security::Instrumentation::Rack::Builder
7
+
8
+ def call(env, &block)
9
+ retval = nil
10
+ event = call_on_enter(env) { retval = super }
11
+ call_on_exit(event, retval) { return retval }
12
+ end
13
+
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -1,5 +1,5 @@
1
1
  module NewRelic
2
2
  module Security
3
- VERSION = "0.3.0"
3
+ VERSION = "0.4.0"
4
4
  end
5
5
  end
@@ -72,7 +72,11 @@ module NewRelic::Security
72
72
  end
73
73
  end
74
74
  rescue IOError => e
75
- close false
75
+ if e.inspect =~ /stream closed in another thread/
76
+ close false
77
+ else
78
+ emit :error, e
79
+ end
76
80
  rescue => e
77
81
  emit :error, e
78
82
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: newrelic_security
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Prateek Sen
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-11-20 00:00:00.000000000 Z
11
+ date: 2025-01-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: newrelic_rpm
@@ -208,6 +208,9 @@ files:
208
208
  - lib/newrelic_security/instrumentation-security/grape/chain.rb
209
209
  - lib/newrelic_security/instrumentation-security/grape/instrumentation.rb
210
210
  - lib/newrelic_security/instrumentation-security/grape/prepend.rb
211
+ - lib/newrelic_security/instrumentation-security/graphql/chain.rb
212
+ - lib/newrelic_security/instrumentation-security/graphql/instrumentation.rb
213
+ - lib/newrelic_security/instrumentation-security/graphql/prepend.rb
211
214
  - lib/newrelic_security/instrumentation-security/grpc/client/chain.rb
212
215
  - lib/newrelic_security/instrumentation-security/grpc/client/instrumentation.rb
213
216
  - lib/newrelic_security/instrumentation-security/grpc/client/prepend.rb
@@ -258,6 +261,9 @@ files:
258
261
  - lib/newrelic_security/instrumentation-security/pty/chain.rb
259
262
  - lib/newrelic_security/instrumentation-security/pty/instrumentation.rb
260
263
  - lib/newrelic_security/instrumentation-security/pty/prepend.rb
264
+ - lib/newrelic_security/instrumentation-security/rack/chain.rb
265
+ - lib/newrelic_security/instrumentation-security/rack/instrumentation.rb
266
+ - lib/newrelic_security/instrumentation-security/rack/prepend.rb
261
267
  - lib/newrelic_security/instrumentation-security/rails/chain.rb
262
268
  - lib/newrelic_security/instrumentation-security/rails/instrumentation.rb
263
269
  - lib/newrelic_security/instrumentation-security/rails/prepend.rb
@@ -324,7 +330,7 @@ metadata:
324
330
  documentation_uri: https://docs.newrelic.com/docs/iast/introduction/
325
331
  source_code_uri: https://github.com/newrelic/csec-ruby-agent
326
332
  homepage_uri: https://github.com/newrelic/csec-ruby-agent
327
- post_install_message:
333
+ post_install_message:
328
334
  rdoc_options: []
329
335
  require_paths:
330
336
  - lib
@@ -340,7 +346,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
340
346
  version: 1.3.1
341
347
  requirements: []
342
348
  rubygems_version: 3.4.19
343
- signing_key:
349
+ signing_key:
344
350
  specification_version: 4
345
351
  summary: Extension for newrelic_rpm with security feature
346
352
  test_files: []