sentry-raven 2.1.3 → 3.1.2
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.
- 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
|