sentry-raven 2.1.3 → 3.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.craft.yml +19 -0
- data/.scripts/bump-version.rb +5 -0
- data/CHANGELOG.md +703 -0
- data/Gemfile +37 -0
- data/Makefile +3 -0
- data/README.md +116 -18
- data/Rakefile +30 -0
- data/exe/raven +32 -0
- data/lib/raven/backtrace.rb +16 -6
- data/lib/raven/base.rb +17 -4
- data/lib/raven/breadcrumbs/{activesupport.rb → active_support_logger.rb} +9 -3
- data/lib/raven/breadcrumbs/logger.rb +2 -92
- data/lib/raven/breadcrumbs/sentry_logger.rb +73 -0
- data/lib/raven/breadcrumbs.rb +3 -1
- data/lib/raven/cli.rb +31 -43
- data/lib/raven/client.rb +39 -17
- data/lib/raven/configuration.rb +277 -37
- data/lib/raven/context.rb +17 -11
- data/lib/raven/core_ext/object/deep_dup.rb +57 -0
- data/lib/raven/core_ext/object/duplicable.rb +153 -0
- data/lib/raven/event.rb +172 -233
- data/lib/raven/helpers/deprecation_helper.rb +17 -0
- data/lib/raven/instance.rb +51 -25
- data/lib/raven/integrations/delayed_job.rb +18 -18
- data/lib/raven/integrations/rack-timeout.rb +11 -5
- data/lib/raven/integrations/rack.rb +36 -19
- data/lib/raven/integrations/rails/active_job.rb +52 -20
- data/lib/raven/integrations/rails/backtrace_cleaner.rb +29 -0
- data/lib/raven/integrations/rails/controller_transaction.rb +13 -0
- data/lib/raven/integrations/rails/overrides/debug_exceptions_catcher.rb +2 -2
- data/lib/raven/integrations/rails.rb +24 -8
- data/lib/raven/integrations/rake.rb +6 -1
- data/lib/raven/integrations/sidekiq/cleanup_middleware.rb +13 -0
- data/lib/raven/integrations/sidekiq/error_handler.rb +38 -0
- data/lib/raven/integrations/sidekiq.rb +6 -57
- data/lib/raven/interface.rb +2 -2
- data/lib/raven/interfaces/exception.rb +0 -2
- data/lib/raven/interfaces/http.rb +0 -2
- data/lib/raven/interfaces/message.rb +1 -1
- data/lib/raven/interfaces/single_exception.rb +0 -2
- data/lib/raven/interfaces/stack_trace.rb +19 -27
- data/lib/raven/linecache.rb +34 -17
- data/lib/raven/logger.rb +11 -18
- data/lib/raven/processor/cookies.rb +27 -7
- data/lib/raven/processor/http_headers.rb +18 -5
- data/lib/raven/processor/post_data.rb +16 -3
- data/lib/raven/processor/removecircularreferences.rb +12 -8
- data/lib/raven/processor/removestacktrace.rb +17 -6
- data/lib/raven/processor/sanitizedata.rb +88 -29
- data/lib/raven/processor/utf8conversion.rb +39 -14
- data/lib/raven/processor.rb +1 -1
- data/lib/raven/transports/http.rb +29 -21
- data/lib/raven/transports/stdout.rb +20 -0
- data/lib/raven/transports.rb +4 -8
- data/lib/raven/utils/context_filter.rb +42 -0
- data/lib/raven/utils/deep_merge.rb +6 -12
- data/lib/raven/utils/exception_cause_chain.rb +20 -0
- data/lib/raven/utils/real_ip.rb +1 -1
- data/lib/raven/utils/request_id.rb +16 -0
- data/lib/raven/version.rb +2 -2
- data/lib/sentry-raven-without-integrations.rb +6 -1
- data/lib/sentry_raven_without_integrations.rb +1 -0
- data/sentry-raven.gemspec +28 -0
- metadata +37 -103
- data/lib/raven/error.rb +0 -4
@@ -1,64 +1,13 @@
|
|
1
1
|
require 'time'
|
2
2
|
require 'sidekiq'
|
3
|
-
|
4
|
-
|
5
|
-
class Sidekiq
|
6
|
-
ACTIVEJOB_RESERVED_PREFIX = "_aj_".freeze
|
7
|
-
|
8
|
-
def call(ex, context)
|
9
|
-
context = filter_context(context)
|
10
|
-
Raven.capture_exception(
|
11
|
-
ex,
|
12
|
-
:message => ex.message,
|
13
|
-
:extra => { :sidekiq => context },
|
14
|
-
:culprit => culprit_from_context(context)
|
15
|
-
)
|
16
|
-
ensure
|
17
|
-
Context.clear!
|
18
|
-
BreadcrumbBuffer.clear!
|
19
|
-
end
|
20
|
-
|
21
|
-
private
|
22
|
-
|
23
|
-
# Once an ActiveJob is queued, ActiveRecord references get serialized into
|
24
|
-
# some internal reserved keys, such as _aj_globalid.
|
25
|
-
#
|
26
|
-
# The problem is, if this job in turn gets queued back into ActiveJob with
|
27
|
-
# these magic reserved keys, ActiveJob will throw up and error. We want to
|
28
|
-
# capture these and mutate the keys so we can sanely report it.
|
29
|
-
def filter_context(context)
|
30
|
-
case context
|
31
|
-
when Array
|
32
|
-
context.map { |arg| filter_context(arg) }
|
33
|
-
when Hash
|
34
|
-
Hash[context.map { |key, value| filter_context_hash(key, value) }]
|
35
|
-
else
|
36
|
-
context
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
def filter_context_hash(key, value)
|
41
|
-
(key = key[3..-1]) if key [0..3] == ACTIVEJOB_RESERVED_PREFIX
|
42
|
-
[key, filter_context(value)]
|
43
|
-
end
|
44
|
-
|
45
|
-
# this will change in the future:
|
46
|
-
# https://github.com/mperham/sidekiq/pull/3161
|
47
|
-
def culprit_from_context(context)
|
48
|
-
classname = (context["class"] || (context["job"] && context["job"]["class"]))
|
49
|
-
if classname
|
50
|
-
"Sidekiq/#{classname}"
|
51
|
-
elsif context["event"]
|
52
|
-
"Sidekiq/#{context['event']}"
|
53
|
-
else
|
54
|
-
"Sidekiq"
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
3
|
+
require 'raven/integrations/sidekiq/cleanup_middleware'
|
4
|
+
require 'raven/integrations/sidekiq/error_handler'
|
59
5
|
|
60
6
|
if Sidekiq::VERSION > '3'
|
61
7
|
Sidekiq.configure_server do |config|
|
62
|
-
config.error_handlers << Raven::Sidekiq.new
|
8
|
+
config.error_handlers << Raven::Sidekiq::ErrorHandler.new
|
9
|
+
config.server_middleware do |chain|
|
10
|
+
chain.add Raven::Sidekiq::CleanupMiddleware
|
11
|
+
end
|
63
12
|
end
|
64
13
|
end
|
data/lib/raven/interface.rb
CHANGED
@@ -1,11 +1,8 @@
|
|
1
|
-
require 'raven/interface'
|
2
|
-
|
3
1
|
module Raven
|
4
2
|
class StacktraceInterface < Interface
|
5
3
|
attr_accessor :frames
|
6
4
|
|
7
5
|
def initialize(*arguments)
|
8
|
-
self.frames = []
|
9
6
|
super(*arguments)
|
10
7
|
end
|
11
8
|
|
@@ -21,23 +18,16 @@ module Raven
|
|
21
18
|
|
22
19
|
# Not actually an interface, but I want to use the same style
|
23
20
|
class Frame < Interface
|
24
|
-
attr_accessor :abs_path
|
25
|
-
|
26
|
-
attr_accessor :vars
|
27
|
-
attr_accessor :pre_context
|
28
|
-
attr_accessor :post_context
|
29
|
-
attr_accessor :context_line
|
30
|
-
attr_accessor :module
|
31
|
-
attr_accessor :lineno
|
32
|
-
attr_accessor :in_app
|
21
|
+
attr_accessor :abs_path, :context_line, :function, :in_app,
|
22
|
+
:lineno, :module, :pre_context, :post_context, :vars
|
33
23
|
|
34
24
|
def initialize(*arguments)
|
35
|
-
self.vars, self.pre_context, self.post_context = [], [], []
|
36
25
|
super(*arguments)
|
37
26
|
end
|
38
27
|
|
39
28
|
def filename
|
40
|
-
return
|
29
|
+
return if abs_path.nil?
|
30
|
+
return @filename if instance_variable_defined?(:@filename)
|
41
31
|
|
42
32
|
prefix =
|
43
33
|
if under_project_root? && in_app
|
@@ -48,19 +38,7 @@ module Raven
|
|
48
38
|
longest_load_path
|
49
39
|
end
|
50
40
|
|
51
|
-
prefix ? abs_path[prefix.to_s.chomp(File::SEPARATOR).length + 1..-1] : abs_path
|
52
|
-
end
|
53
|
-
|
54
|
-
def under_project_root?
|
55
|
-
project_root && abs_path.start_with?(project_root)
|
56
|
-
end
|
57
|
-
|
58
|
-
def project_root
|
59
|
-
@project_root ||= Raven.configuration.project_root && Raven.configuration.project_root.to_s
|
60
|
-
end
|
61
|
-
|
62
|
-
def longest_load_path
|
63
|
-
$LOAD_PATH.select { |s| abs_path.start_with?(s.to_s) }.sort_by { |s| s.to_s.length }.last
|
41
|
+
@filename = prefix ? abs_path[prefix.to_s.chomp(File::SEPARATOR).length + 1..-1] : abs_path
|
64
42
|
end
|
65
43
|
|
66
44
|
def to_hash(*args)
|
@@ -72,6 +50,20 @@ module Raven
|
|
72
50
|
data.delete(:context_line) unless context_line && !context_line.empty?
|
73
51
|
data
|
74
52
|
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def under_project_root?
|
57
|
+
project_root && abs_path.start_with?(project_root)
|
58
|
+
end
|
59
|
+
|
60
|
+
def project_root
|
61
|
+
@project_root ||= Raven.configuration.project_root&.to_s
|
62
|
+
end
|
63
|
+
|
64
|
+
def longest_load_path
|
65
|
+
$LOAD_PATH.select { |path| abs_path.start_with?(path.to_s) }.max_by(&:size)
|
66
|
+
end
|
75
67
|
end
|
76
68
|
end
|
77
69
|
end
|
data/lib/raven/linecache.rb
CHANGED
@@ -1,27 +1,44 @@
|
|
1
1
|
module Raven
|
2
2
|
class LineCache
|
3
|
-
|
4
|
-
|
3
|
+
def initialize
|
4
|
+
@cache = {}
|
5
|
+
end
|
5
6
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
# Any linecache you provide to Raven must implement this method.
|
8
|
+
# Returns an Array of Strings representing the lines in the source
|
9
|
+
# file. The number of lines retrieved is (2 * context) + 1, the middle
|
10
|
+
# line should be the line requested by lineno. See specs for more information.
|
11
|
+
def get_file_context(filename, lineno, context)
|
12
|
+
return nil, nil, nil unless valid_path?(filename)
|
10
13
|
|
11
|
-
|
12
|
-
|
13
|
-
IO.readlines(path)
|
14
|
-
rescue
|
15
|
-
nil
|
16
|
-
end
|
14
|
+
lines = Array.new(2 * context + 1) do |i|
|
15
|
+
getline(filename, lineno - context + i)
|
17
16
|
end
|
17
|
+
[lines[0..(context - 1)], lines[context], lines[(context + 1)..-1]]
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def valid_path?(path)
|
23
|
+
lines = getlines(path)
|
24
|
+
!lines.nil?
|
25
|
+
end
|
18
26
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
27
|
+
def getlines(path)
|
28
|
+
@cache[path] ||= begin
|
29
|
+
IO.readlines(path)
|
30
|
+
rescue
|
31
|
+
nil
|
24
32
|
end
|
25
33
|
end
|
34
|
+
|
35
|
+
def getline(path, n)
|
36
|
+
return nil if n < 1
|
37
|
+
|
38
|
+
lines = getlines(path)
|
39
|
+
return nil if lines.nil?
|
40
|
+
|
41
|
+
lines[n - 1]
|
42
|
+
end
|
26
43
|
end
|
27
44
|
end
|
data/lib/raven/logger.rb
CHANGED
@@ -1,26 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'logger'
|
3
4
|
|
4
5
|
module Raven
|
5
|
-
class Logger
|
6
|
-
LOG_PREFIX = "** [Raven] "
|
7
|
-
|
8
|
-
[
|
9
|
-
:fatal,
|
10
|
-
:error,
|
11
|
-
:warn,
|
12
|
-
:info,
|
13
|
-
:debug
|
14
|
-
].each do |level|
|
15
|
-
define_method level do |*args, &block|
|
16
|
-
logger = Raven.configuration[:logger]
|
17
|
-
logger = ::Logger.new(STDOUT) if logger.nil?
|
18
|
-
return unless logger
|
19
|
-
|
20
|
-
msg = args[0] # Block-level default args is a 1.9 feature
|
21
|
-
msg ||= block.call if block
|
6
|
+
class Logger < ::Logger
|
7
|
+
LOG_PREFIX = "** [Raven] "
|
8
|
+
PROGNAME = "sentry"
|
22
9
|
|
23
|
-
|
10
|
+
def initialize(*)
|
11
|
+
super
|
12
|
+
@level = ::Logger::INFO
|
13
|
+
original_formatter = ::Logger::Formatter.new
|
14
|
+
@default_formatter = proc do |severity, datetime, _progname, msg|
|
15
|
+
msg = "#{LOG_PREFIX}#{msg}"
|
16
|
+
original_formatter.call(severity, datetime, PROGNAME, msg)
|
24
17
|
end
|
25
18
|
end
|
26
19
|
end
|
@@ -1,16 +1,36 @@
|
|
1
1
|
module Raven
|
2
2
|
class Processor::Cookies < Processor
|
3
3
|
def process(data)
|
4
|
-
if data[:request]
|
5
|
-
|
6
|
-
data[:request][:cookies] = STRING_MASK if data[:request][:cookies]
|
4
|
+
process_if_symbol_keys(data) if data[:request]
|
5
|
+
process_if_string_keys(data) if data["request"]
|
7
6
|
|
8
|
-
|
9
|
-
|
10
|
-
|
7
|
+
data
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def process_if_symbol_keys(data)
|
13
|
+
if cookies = data.dig(:request, :cookies)
|
14
|
+
data[:request][:cookies] = generate_masked_cookies(cookies)
|
11
15
|
end
|
12
16
|
|
13
|
-
data
|
17
|
+
if cookies_header = data[:request][:headers]["Cookie"]
|
18
|
+
data[:request][:headers]["Cookie"] = generate_masked_cookies(cookies_header)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def process_if_string_keys(data)
|
23
|
+
if cookies = data.dig("request", "cookies")
|
24
|
+
data["request"]["cookies"] = generate_masked_cookies(cookies)
|
25
|
+
end
|
26
|
+
|
27
|
+
if cookies_header = data.dig("request", "headers", "Cookie")
|
28
|
+
data["request"]["headers"]["Cookie"] = generate_masked_cookies(cookies_header)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def generate_masked_cookies(cookies)
|
33
|
+
cookies.merge(cookies) { STRING_MASK } if cookies.respond_to?(:merge)
|
14
34
|
end
|
15
35
|
end
|
16
36
|
end
|
@@ -10,17 +10,30 @@ module Raven
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def process(data)
|
13
|
-
|
14
|
-
|
15
|
-
data[:request][:headers][k] = STRING_MASK
|
16
|
-
end
|
17
|
-
end
|
13
|
+
process_if_symbol_keys(data) if data[:request]
|
14
|
+
process_if_string_keys(data) if data["request"]
|
18
15
|
|
19
16
|
data
|
20
17
|
end
|
21
18
|
|
22
19
|
private
|
23
20
|
|
21
|
+
def process_if_symbol_keys(data)
|
22
|
+
return unless data[:request][:headers]
|
23
|
+
|
24
|
+
data[:request][:headers].keys.select { |k| fields_re.match(k.to_s) }.each do |k|
|
25
|
+
data[:request][:headers][k] = STRING_MASK
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def process_if_string_keys(data)
|
30
|
+
return unless data["request"]["headers"]
|
31
|
+
|
32
|
+
data["request"]["headers"].keys.select { |k| fields_re.match(k) }.each do |k|
|
33
|
+
data["request"]["headers"][k] = STRING_MASK
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
24
37
|
def matches_regexes?(k)
|
25
38
|
fields_re.match(k.to_s)
|
26
39
|
end
|
@@ -1,11 +1,24 @@
|
|
1
1
|
module Raven
|
2
2
|
class Processor::PostData < Processor
|
3
3
|
def process(data)
|
4
|
-
|
5
|
-
|
6
|
-
end
|
4
|
+
process_if_symbol_keys(data) if data[:request]
|
5
|
+
process_if_string_keys(data) if data["request"]
|
7
6
|
|
8
7
|
data
|
9
8
|
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def process_if_symbol_keys(data)
|
13
|
+
return unless data[:request][:method] == "POST"
|
14
|
+
|
15
|
+
data[:request][:data] = STRING_MASK
|
16
|
+
end
|
17
|
+
|
18
|
+
def process_if_string_keys(data)
|
19
|
+
return unless data["request"]["method"] == "POST"
|
20
|
+
|
21
|
+
data["request"]["data"] = STRING_MASK
|
22
|
+
end
|
10
23
|
end
|
11
24
|
end
|
@@ -1,14 +1,18 @@
|
|
1
1
|
module Raven
|
2
2
|
class Processor::RemoveCircularReferences < Processor
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
3
|
+
ELISION_STRING = "(...)".freeze
|
4
|
+
def process(value, visited = [])
|
5
|
+
return ELISION_STRING if visited.include?(value.__id__)
|
6
|
+
|
7
|
+
visited << value.__id__ if value.is_a?(Array) || value.is_a?(Hash)
|
8
|
+
|
9
|
+
case value
|
10
|
+
when Hash
|
11
|
+
!value.frozen? ? value.merge!(value) { |_, v| process v, visited } : value.merge(value) { |_, v| process v, visited }
|
12
|
+
when Array
|
13
|
+
!value.frozen? ? value.map! { |v| process v, visited } : value.map { |v| process v, visited }
|
10
14
|
else
|
11
|
-
|
15
|
+
value
|
12
16
|
end
|
13
17
|
end
|
14
18
|
end
|
@@ -1,13 +1,24 @@
|
|
1
1
|
module Raven
|
2
2
|
class Processor::RemoveStacktrace < Processor
|
3
|
-
def process(
|
4
|
-
if
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
def process(data)
|
4
|
+
process_if_symbol_keys(data) if data[:exception]
|
5
|
+
process_if_string_keys(data) if data["exception"]
|
6
|
+
|
7
|
+
data
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def process_if_symbol_keys(data)
|
13
|
+
data[:exception][:values].map do |single_exception|
|
14
|
+
single_exception.delete(:stacktrace) if single_exception[:stacktrace]
|
8
15
|
end
|
16
|
+
end
|
9
17
|
|
10
|
-
|
18
|
+
def process_if_string_keys(data)
|
19
|
+
data["exception"]["values"].map do |single_exception|
|
20
|
+
single_exception.delete("stacktrace") if single_exception["stacktrace"]
|
21
|
+
end
|
11
22
|
end
|
12
23
|
end
|
13
24
|
end
|
@@ -1,61 +1,118 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'json'
|
3
4
|
|
4
5
|
module Raven
|
5
6
|
class Processor::SanitizeData < Processor
|
6
7
|
DEFAULT_FIELDS = %w(authorization password passwd secret ssn social(.*)?sec).freeze
|
7
|
-
CREDIT_CARD_RE =
|
8
|
+
CREDIT_CARD_RE = /\b(?:3[47]\d|(?:4\d|5[1-5]|65)\d{2}|6011)\d{12}\b/.freeze
|
9
|
+
QUERY_STRING = ['query_string', :query_string].freeze
|
10
|
+
JSON_STARTS_WITH = ["[", "{"].freeze
|
8
11
|
|
9
|
-
attr_accessor :sanitize_fields, :sanitize_credit_cards
|
12
|
+
attr_accessor :sanitize_fields, :sanitize_credit_cards, :sanitize_fields_excluded
|
10
13
|
|
11
14
|
def initialize(client)
|
12
15
|
super
|
13
16
|
self.sanitize_fields = client.configuration.sanitize_fields
|
14
17
|
self.sanitize_credit_cards = client.configuration.sanitize_credit_cards
|
18
|
+
self.sanitize_fields_excluded = client.configuration.sanitize_fields_excluded
|
19
|
+
end
|
20
|
+
|
21
|
+
def process(value, key = nil)
|
22
|
+
case value
|
23
|
+
when Hash
|
24
|
+
sanitize_hash_value(key, value)
|
25
|
+
when Array
|
26
|
+
sanitize_array_value(key, value)
|
27
|
+
when Integer
|
28
|
+
matches_regexes?(key, value.to_s) ? INT_MASK : value
|
29
|
+
when String
|
30
|
+
sanitize_string_value(key, value)
|
31
|
+
else
|
32
|
+
value
|
33
|
+
end
|
15
34
|
end
|
16
35
|
|
17
|
-
|
18
|
-
|
36
|
+
private
|
37
|
+
|
38
|
+
# CGI.parse takes our nice UTF-8 strings and converts them back to ASCII,
|
39
|
+
# so we have to convert them back, again.
|
40
|
+
def utf8_processor
|
41
|
+
@utf8_processor ||= Processor::UTF8Conversion.new
|
19
42
|
end
|
20
43
|
|
21
|
-
def
|
22
|
-
if
|
23
|
-
|
24
|
-
elsif
|
25
|
-
|
26
|
-
elsif k.to_s == 'query_string'
|
27
|
-
sanitize_query_string(v)
|
28
|
-
elsif v.is_a?(Integer) && matches_regexes?(k, v)
|
29
|
-
INT_MASK
|
30
|
-
elsif v.is_a?(String)
|
31
|
-
if fields_re.match(v.to_s) && (json = parse_json_or_nil(v))
|
32
|
-
# if this string is actually a json obj, convert and sanitize
|
33
|
-
json.is_a?(Hash) ? process(json).to_json : v
|
34
|
-
elsif matches_regexes?(k, v)
|
35
|
-
STRING_MASK
|
36
|
-
else
|
37
|
-
v
|
38
|
-
end
|
44
|
+
def sanitize_hash_value(key, value)
|
45
|
+
if key =~ sensitive_fields
|
46
|
+
STRING_MASK
|
47
|
+
elsif value.frozen?
|
48
|
+
value.merge(value) { |k, v| process v, k }
|
39
49
|
else
|
40
|
-
v
|
50
|
+
value.merge!(value) { |k, v| process v, k }
|
41
51
|
end
|
42
52
|
end
|
43
53
|
|
44
|
-
|
54
|
+
def sanitize_array_value(key, value)
|
55
|
+
if value.frozen?
|
56
|
+
value.map { |v| process v, key }
|
57
|
+
else
|
58
|
+
value.map! { |v| process v, key }
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def sanitize_string_value(key, value)
|
63
|
+
if value =~ sensitive_fields && (json = parse_json_or_nil(value))
|
64
|
+
# if this string is actually a json obj, convert and sanitize
|
65
|
+
process(json).to_json
|
66
|
+
elsif matches_regexes?(key, value)
|
67
|
+
STRING_MASK
|
68
|
+
elsif QUERY_STRING.include?(key)
|
69
|
+
sanitize_query_string(value)
|
70
|
+
elsif value =~ sensitive_fields
|
71
|
+
sanitize_sensitive_string_content(value)
|
72
|
+
else
|
73
|
+
value
|
74
|
+
end
|
75
|
+
end
|
45
76
|
|
46
77
|
def sanitize_query_string(query_string)
|
47
78
|
query_hash = CGI.parse(query_string)
|
48
|
-
|
79
|
+
sanitized = utf8_processor.process(query_hash)
|
80
|
+
processed_query_hash = process(sanitized)
|
49
81
|
URI.encode_www_form(processed_query_hash)
|
50
82
|
end
|
51
83
|
|
84
|
+
# this scrubs some sensitive info from the string content. for example:
|
85
|
+
#
|
86
|
+
# ```
|
87
|
+
# unexpected token at '{
|
88
|
+
# "role": "admin","password": "Abc@123","foo": "bar"
|
89
|
+
# }'
|
90
|
+
# ```
|
91
|
+
#
|
92
|
+
# will become
|
93
|
+
#
|
94
|
+
# ```
|
95
|
+
# unexpected token at '{
|
96
|
+
# "role": "admin","password": *******,"foo": "bar"
|
97
|
+
# }'
|
98
|
+
# ```
|
99
|
+
#
|
100
|
+
# it's particularly useful in hash or param-parsing related errors
|
101
|
+
def sanitize_sensitive_string_content(value)
|
102
|
+
value.gsub(/(#{sensitive_fields}['":]\s?(:|=>)?\s?)(".*?"|'.*?')/, '\1' + STRING_MASK)
|
103
|
+
end
|
104
|
+
|
52
105
|
def matches_regexes?(k, v)
|
53
|
-
(sanitize_credit_cards && CREDIT_CARD_RE
|
54
|
-
|
106
|
+
(sanitize_credit_cards && v =~ CREDIT_CARD_RE) ||
|
107
|
+
k =~ sensitive_fields
|
55
108
|
end
|
56
109
|
|
57
|
-
def
|
58
|
-
@
|
110
|
+
def sensitive_fields
|
111
|
+
return @sensitive_fields if instance_variable_defined?(:@sensitive_fields)
|
112
|
+
|
113
|
+
fields = DEFAULT_FIELDS | sanitize_fields
|
114
|
+
fields -= sanitize_fields_excluded
|
115
|
+
@sensitive_fields = /#{fields.map do |f|
|
59
116
|
use_boundary?(f) ? "\\b#{f}\\b" : f
|
60
117
|
end.join("|")}/i
|
61
118
|
end
|
@@ -69,6 +126,8 @@ module Raven
|
|
69
126
|
end
|
70
127
|
|
71
128
|
def parse_json_or_nil(string)
|
129
|
+
return unless string.start_with?(*JSON_STARTS_WITH)
|
130
|
+
|
72
131
|
JSON.parse(string)
|
73
132
|
rescue JSON::ParserError, NoMethodError
|
74
133
|
nil
|