newrelic_security 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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: []