lapsoss 0.4.0 → 0.4.4
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 +4 -4
- data/README.md +195 -7
- data/lib/lapsoss/adapters/concerns/level_mapping.rb +1 -0
- data/lib/lapsoss/adapters/telebug_adapter.rb +58 -0
- data/lib/lapsoss/client.rb +1 -3
- data/lib/lapsoss/configuration.rb +14 -17
- data/lib/lapsoss/fingerprinter.rb +52 -47
- data/lib/lapsoss/middleware/release_tracker.rb +11 -98
- data/lib/lapsoss/pipeline_builder.rb +2 -2
- data/lib/lapsoss/rails_middleware.rb +2 -2
- data/lib/lapsoss/railtie.rb +14 -3
- data/lib/lapsoss/registry.rb +7 -7
- data/lib/lapsoss/router.rb +1 -3
- data/lib/lapsoss/scrubber.rb +15 -152
- data/lib/lapsoss/validators.rb +48 -112
- data/lib/lapsoss/version.rb +1 -1
- data/lib/lapsoss.rb +23 -0
- metadata +2 -21
- data/lib/lapsoss/exclusion_configuration.rb +0 -30
- data/lib/lapsoss/exclusion_presets.rb +0 -249
- data/lib/lapsoss/middleware/sample_filter.rb +0 -23
- data/lib/lapsoss/middleware/sampling_middleware.rb +0 -18
- data/lib/lapsoss/middleware/user_context_enhancer.rb +0 -46
- data/lib/lapsoss/release_providers.rb +0 -110
- data/lib/lapsoss/sampling/adaptive_sampler.rb +0 -46
- data/lib/lapsoss/sampling/composite_sampler.rb +0 -26
- data/lib/lapsoss/sampling/consistent_hash_sampler.rb +0 -30
- data/lib/lapsoss/sampling/exception_type_sampler.rb +0 -44
- data/lib/lapsoss/sampling/health_based_sampler.rb +0 -19
- data/lib/lapsoss/sampling/sampling_factory.rb +0 -69
- data/lib/lapsoss/sampling/time_based_sampler.rb +0 -44
- data/lib/lapsoss/sampling/user_based_sampler.rb +0 -42
- data/lib/lapsoss/user_context.rb +0 -185
- data/lib/lapsoss/user_context_integrations.rb +0 -39
- data/lib/lapsoss/user_context_middleware.rb +0 -50
- data/lib/lapsoss/user_context_provider.rb +0 -93
- data/lib/lapsoss/utils.rb +0 -11
- data/lib/tasks/cassettes.rake +0 -50
@@ -1,19 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Lapsoss
|
4
|
-
module Sampling
|
5
|
-
class HealthBasedSampler < Base
|
6
|
-
def initialize(health_check:, high_rate: 1.0, low_rate: 0.1)
|
7
|
-
@health_check = health_check
|
8
|
-
@high_rate = high_rate
|
9
|
-
@low_rate = low_rate
|
10
|
-
end
|
11
|
-
|
12
|
-
def sample?(_event, _hint = {})
|
13
|
-
healthy = @health_check.call
|
14
|
-
rate = healthy ? @high_rate : @low_rate
|
15
|
-
rate > rand
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
@@ -1,69 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Lapsoss
|
4
|
-
module Sampling
|
5
|
-
# Factory for creating common sampling configurations
|
6
|
-
class SamplingFactory
|
7
|
-
def self.create_production_sampling
|
8
|
-
CompositeSampler.new(
|
9
|
-
samplers: [
|
10
|
-
# Rate limit to prevent overwhelming
|
11
|
-
RateLimiter.new(max_events_per_second: 50),
|
12
|
-
|
13
|
-
# Different rates for different exception types
|
14
|
-
ExceptionTypeSampler.new(rates: {
|
15
|
-
# Critical errors - always sample
|
16
|
-
SecurityError => 1.0,
|
17
|
-
SystemStackError => 1.0,
|
18
|
-
NoMemoryError => 1.0,
|
19
|
-
|
20
|
-
# Common errors - sample less
|
21
|
-
ArgumentError => 0.1,
|
22
|
-
TypeError => 0.1,
|
23
|
-
|
24
|
-
# Network errors - medium sampling
|
25
|
-
/timeout/i => 0.3,
|
26
|
-
/connection/i => 0.3,
|
27
|
-
|
28
|
-
# Default for unknown errors
|
29
|
-
default: 0.5
|
30
|
-
}),
|
31
|
-
|
32
|
-
# Lower sampling during business hours
|
33
|
-
TimeBasedSampler.new(schedule: {
|
34
|
-
business_hours: 0.3,
|
35
|
-
weekends: 0.8,
|
36
|
-
default: 0.5
|
37
|
-
})
|
38
|
-
],
|
39
|
-
strategy: :all
|
40
|
-
)
|
41
|
-
end
|
42
|
-
|
43
|
-
def self.create_development_sampling
|
44
|
-
UniformSampler.new(1.0) # Sample everything in development
|
45
|
-
end
|
46
|
-
|
47
|
-
def self.create_user_focused_sampling
|
48
|
-
CompositeSampler.new(
|
49
|
-
samplers: [
|
50
|
-
# Higher sampling for internal users
|
51
|
-
UserBasedSampler.new(rates: {
|
52
|
-
internal: 1.0,
|
53
|
-
premium: 0.8,
|
54
|
-
beta: 0.9,
|
55
|
-
default: 0.1
|
56
|
-
}),
|
57
|
-
|
58
|
-
# Consistent sampling based on user ID
|
59
|
-
ConsistentHashSampler.new(
|
60
|
-
rate: 0.1,
|
61
|
-
key_extractor: ->(event, _hint) { event.context.dig(:user, :id) }
|
62
|
-
)
|
63
|
-
],
|
64
|
-
strategy: :any
|
65
|
-
)
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
@@ -1,44 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Lapsoss
|
4
|
-
module Sampling
|
5
|
-
class TimeBasedSampler < Base
|
6
|
-
def initialize(schedule: {})
|
7
|
-
@schedule = schedule
|
8
|
-
@default_rate = schedule.fetch(:default, 1.0)
|
9
|
-
end
|
10
|
-
|
11
|
-
def sample?(_event, _hint = {})
|
12
|
-
now = Time.zone.now
|
13
|
-
rate = find_rate_for_time(now)
|
14
|
-
rate > rand
|
15
|
-
end
|
16
|
-
|
17
|
-
private
|
18
|
-
|
19
|
-
def find_rate_for_time(time)
|
20
|
-
hour = time.hour
|
21
|
-
day_of_week = time.wday # 0 = Sunday
|
22
|
-
|
23
|
-
# Check specific hour
|
24
|
-
hour_key = :"hour_#{hour}"
|
25
|
-
return @schedule[hour_key] if @schedule.key?(hour_key)
|
26
|
-
|
27
|
-
# Check day of week
|
28
|
-
day_names = %i[sunday monday tuesday wednesday thursday friday saturday]
|
29
|
-
day_key = day_names[day_of_week]
|
30
|
-
return @schedule[day_key] if @schedule.key?(day_key)
|
31
|
-
|
32
|
-
# Check business hours
|
33
|
-
if @schedule.key?(:business_hours) && (9..17).cover?(hour) && (1..5).cover?(day_of_week)
|
34
|
-
return @schedule[:business_hours]
|
35
|
-
end
|
36
|
-
|
37
|
-
# Check weekends
|
38
|
-
return @schedule[:weekends] if @schedule.key?(:weekends) && [ 0, 6 ].include?(day_of_week)
|
39
|
-
|
40
|
-
@default_rate
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
44
|
-
end
|
@@ -1,42 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Lapsoss
|
4
|
-
module Sampling
|
5
|
-
class UserBasedSampler < Base
|
6
|
-
def initialize(rates: {})
|
7
|
-
@rates = rates
|
8
|
-
@default_rate = rates.fetch(:default, 1.0)
|
9
|
-
end
|
10
|
-
|
11
|
-
def sample?(event, _hint = {})
|
12
|
-
user = event.context[:user]
|
13
|
-
return @default_rate > rand unless user
|
14
|
-
|
15
|
-
rate = find_rate_for_user(user)
|
16
|
-
rate > rand
|
17
|
-
end
|
18
|
-
|
19
|
-
private
|
20
|
-
|
21
|
-
def find_rate_for_user(user)
|
22
|
-
# Check specific user ID
|
23
|
-
user_id = user[:id] || user["id"]
|
24
|
-
return @rates[user_id] if user_id && @rates.key?(user_id)
|
25
|
-
|
26
|
-
# Check user segments
|
27
|
-
@rates.each do |segment, rate|
|
28
|
-
case segment
|
29
|
-
when :internal, "internal"
|
30
|
-
return rate if user[:internal] || user["internal"]
|
31
|
-
when :premium, "premium"
|
32
|
-
return rate if user[:premium] || user["premium"]
|
33
|
-
when :beta, "beta"
|
34
|
-
return rate if user[:beta] || user["beta"]
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
@default_rate
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
end
|
data/lib/lapsoss/user_context.rb
DELETED
@@ -1,185 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Lapsoss
|
4
|
-
# Enhanced user context handling with privacy controls
|
5
|
-
class UserContext
|
6
|
-
SENSITIVE_FIELDS = %i[
|
7
|
-
email phone mobile telephone
|
8
|
-
address street city state zip postal_code country
|
9
|
-
ssn social_security_number
|
10
|
-
credit_card card_number cvv
|
11
|
-
password password_confirmation
|
12
|
-
secret token api_key
|
13
|
-
ip_address last_login_ip
|
14
|
-
birth_date date_of_birth dob
|
15
|
-
salary income wage
|
16
|
-
].freeze
|
17
|
-
|
18
|
-
def initialize(privacy_mode: false, allowed_fields: nil, field_transformers: {})
|
19
|
-
@privacy_mode = privacy_mode
|
20
|
-
@allowed_fields = allowed_fields&.map(&:to_sym)
|
21
|
-
@field_transformers = field_transformers
|
22
|
-
end
|
23
|
-
|
24
|
-
def process_user_data(user_data)
|
25
|
-
return {} unless user_data.is_a?(Hash)
|
26
|
-
|
27
|
-
processed = {}
|
28
|
-
|
29
|
-
user_data.each do |key, value|
|
30
|
-
key_sym = key.to_sym
|
31
|
-
|
32
|
-
# Skip if not in allowed fields list (when specified)
|
33
|
-
next if @allowed_fields&.exclude?(key_sym)
|
34
|
-
|
35
|
-
# Apply privacy filtering
|
36
|
-
processed[key] = if @privacy_mode && sensitive_field?(key_sym)
|
37
|
-
apply_privacy_filter(key_sym, value)
|
38
|
-
else
|
39
|
-
transform_field(key_sym, value)
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
processed
|
44
|
-
end
|
45
|
-
|
46
|
-
def merge_user_data(existing_data, new_data)
|
47
|
-
existing_processed = process_user_data(existing_data || {})
|
48
|
-
new_processed = process_user_data(new_data || {})
|
49
|
-
|
50
|
-
existing_processed.merge(new_processed)
|
51
|
-
end
|
52
|
-
|
53
|
-
def extract_user_id(user_data)
|
54
|
-
return nil unless user_data.is_a?(Hash)
|
55
|
-
|
56
|
-
# Try common user ID fields in order of preference
|
57
|
-
%i[id user_id uuid guid].each do |field|
|
58
|
-
value = user_data[field] || user_data[field.to_s]
|
59
|
-
return value if value
|
60
|
-
end
|
61
|
-
|
62
|
-
nil
|
63
|
-
end
|
64
|
-
|
65
|
-
def extract_user_segment(user_data)
|
66
|
-
return nil unless user_data.is_a?(Hash)
|
67
|
-
|
68
|
-
segments = {}
|
69
|
-
|
70
|
-
# Check for common user segments
|
71
|
-
segments[:internal] = !(user_data[:internal] || user_data["internal"]).nil?
|
72
|
-
segments[:premium] = !(user_data[:premium] || user_data["premium"]).nil?
|
73
|
-
segments[:beta] = !(user_data[:beta] || user_data["beta"]).nil?
|
74
|
-
segments[:admin] = !(user_data[:admin] || user_data["admin"]).nil?
|
75
|
-
|
76
|
-
# Check role-based segments
|
77
|
-
if role = user_data[:role] || user_data["role"]
|
78
|
-
segments[:role] = role.to_s.downcase
|
79
|
-
end
|
80
|
-
|
81
|
-
# Check plan-based segments
|
82
|
-
if plan = user_data[:plan] || user_data["plan"]
|
83
|
-
segments[:plan] = plan.to_s.downcase
|
84
|
-
end
|
85
|
-
|
86
|
-
segments.compact
|
87
|
-
end
|
88
|
-
|
89
|
-
def sanitize_for_logging(user_data)
|
90
|
-
return {} unless user_data.is_a?(Hash)
|
91
|
-
|
92
|
-
sanitized = {}
|
93
|
-
|
94
|
-
user_data.each do |key, value|
|
95
|
-
key_sym = key.to_sym
|
96
|
-
|
97
|
-
sanitized[key] = if sensitive_field?(key_sym)
|
98
|
-
"[REDACTED]"
|
99
|
-
else
|
100
|
-
case value
|
101
|
-
in Hash => h
|
102
|
-
sanitize_for_logging(h)
|
103
|
-
in Array => arr
|
104
|
-
arr.map do |v|
|
105
|
-
case v
|
106
|
-
in Hash => h
|
107
|
-
sanitize_for_logging(h)
|
108
|
-
else
|
109
|
-
v
|
110
|
-
end
|
111
|
-
end
|
112
|
-
else
|
113
|
-
value
|
114
|
-
end
|
115
|
-
end
|
116
|
-
end
|
117
|
-
|
118
|
-
sanitized
|
119
|
-
end
|
120
|
-
|
121
|
-
private
|
122
|
-
|
123
|
-
def sensitive_field?(field)
|
124
|
-
SENSITIVE_FIELDS.include?(field) || field.to_s.match?(/password|secret|token|key|ssn|credit|card/i)
|
125
|
-
end
|
126
|
-
|
127
|
-
def apply_privacy_filter(field, value)
|
128
|
-
case field
|
129
|
-
when :email
|
130
|
-
mask_email(value)
|
131
|
-
when :phone, :mobile, :telephone
|
132
|
-
mask_phone(value)
|
133
|
-
when :ip_address, :last_login_ip
|
134
|
-
mask_ip(value)
|
135
|
-
else
|
136
|
-
"[FILTERED]"
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
def transform_field(field, value)
|
141
|
-
if transformer = @field_transformers[field]
|
142
|
-
transformer.call(value)
|
143
|
-
else
|
144
|
-
value
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
def mask_email(email)
|
149
|
-
return "[INVALID_EMAIL]" unless email.is_a?(String) && email.include?("@")
|
150
|
-
|
151
|
-
local, domain = email.split("@", 2)
|
152
|
-
masked_local = local.length > 2 ? "#{local[0]}***#{local[-1]}" : "***"
|
153
|
-
"#{masked_local}@#{domain}"
|
154
|
-
end
|
155
|
-
|
156
|
-
def mask_phone(phone)
|
157
|
-
return "[INVALID_PHONE]" unless phone.is_a?(String)
|
158
|
-
|
159
|
-
# Remove all non-digits
|
160
|
-
digits = phone.gsub(/\D/, "")
|
161
|
-
return "[INVALID_PHONE]" if digits.length < 4
|
162
|
-
|
163
|
-
# Show last 4 digits
|
164
|
-
("*" * (digits.length - 4)) + digits[-4..]
|
165
|
-
end
|
166
|
-
|
167
|
-
def mask_ip(ip)
|
168
|
-
return "[INVALID_IP]" unless ip.is_a?(String)
|
169
|
-
|
170
|
-
if ip.include?(":")
|
171
|
-
# IPv6 - mask last 4 groups
|
172
|
-
parts = ip.split(":")
|
173
|
-
parts[-4..-1] = [ "****" ] * 4 if parts.length >= 4
|
174
|
-
parts.join(":")
|
175
|
-
else
|
176
|
-
# IPv4 - mask last octet
|
177
|
-
parts = ip.split(".")
|
178
|
-
return "[INVALID_IP]" if parts.length != 4
|
179
|
-
|
180
|
-
parts[-1] = "***"
|
181
|
-
parts.join(".")
|
182
|
-
end
|
183
|
-
end
|
184
|
-
end
|
185
|
-
end
|
@@ -1,39 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Lapsoss
|
4
|
-
# Integration with popular authentication gems
|
5
|
-
module UserContextIntegrations
|
6
|
-
def self.setup_devise_integration
|
7
|
-
return unless defined?(Devise)
|
8
|
-
|
9
|
-
# Add middleware to capture user context
|
10
|
-
Rails.application.config.middleware.use(UserContextMiddleware) if defined?(Rails)
|
11
|
-
end
|
12
|
-
|
13
|
-
def self.setup_clearance_integration
|
14
|
-
return unless defined?(Clearance)
|
15
|
-
|
16
|
-
# Clearance integration
|
17
|
-
return unless defined?(Rails)
|
18
|
-
|
19
|
-
Rails.application.config.middleware.use(UserContextMiddleware) do |middleware|
|
20
|
-
middleware.user_provider = lambda do |request|
|
21
|
-
request.env[:clearance].current_user if request.env[:clearance]
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
def self.setup_authlogic_integration
|
27
|
-
return unless defined?(Authlogic)
|
28
|
-
|
29
|
-
# Authlogic integration
|
30
|
-
return unless defined?(Rails)
|
31
|
-
|
32
|
-
Rails.application.config.middleware.use(UserContextMiddleware) do |middleware|
|
33
|
-
middleware.user_provider = lambda do |_request|
|
34
|
-
UserSession.find&.user if defined?(UserSession)
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
@@ -1,50 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Lapsoss
|
4
|
-
# Middleware to automatically capture user context
|
5
|
-
class UserContextMiddleware
|
6
|
-
def initialize(app, user_provider: nil)
|
7
|
-
@app = app
|
8
|
-
@user_provider = user_provider
|
9
|
-
end
|
10
|
-
|
11
|
-
def call(env)
|
12
|
-
request = Rack::Request.new(env)
|
13
|
-
|
14
|
-
# Capture user context
|
15
|
-
user_context = extract_user_context(request)
|
16
|
-
|
17
|
-
# Store in thread-local for access during request processing
|
18
|
-
Thread.current[:lapsoss_user_context] = user_context
|
19
|
-
|
20
|
-
@app.call(env)
|
21
|
-
ensure
|
22
|
-
Thread.current[:lapsoss_user_context] = nil
|
23
|
-
end
|
24
|
-
|
25
|
-
private
|
26
|
-
|
27
|
-
def extract_user_context(request)
|
28
|
-
if @user_provider
|
29
|
-
user = @user_provider.call(request)
|
30
|
-
return {} unless user
|
31
|
-
|
32
|
-
context = {
|
33
|
-
id: user.id,
|
34
|
-
email: user.email,
|
35
|
-
username: user.respond_to?(:username) ? user.username : nil
|
36
|
-
}
|
37
|
-
|
38
|
-
# Add role information if available
|
39
|
-
context[:role] = user.role if user.respond_to?(:role)
|
40
|
-
|
41
|
-
# Add plan information if available
|
42
|
-
context[:plan] = user.plan if user.respond_to?(:plan)
|
43
|
-
|
44
|
-
context.compact
|
45
|
-
else
|
46
|
-
{}
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
@@ -1,93 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Lapsoss
|
4
|
-
# User context provider that integrates with various authentication systems
|
5
|
-
class UserContextProvider
|
6
|
-
def initialize(providers: {})
|
7
|
-
@providers = providers
|
8
|
-
end
|
9
|
-
|
10
|
-
def get_user_context(event, hint = {})
|
11
|
-
context = {}
|
12
|
-
|
13
|
-
# Try each provider in order
|
14
|
-
@providers.each do |name, provider|
|
15
|
-
if provider_context = provider.call(event, hint)
|
16
|
-
context.merge!(provider_context)
|
17
|
-
end
|
18
|
-
rescue StandardError => e
|
19
|
-
# Log provider error but don't fail
|
20
|
-
warn "User context provider #{name} failed: #{e.message}"
|
21
|
-
end
|
22
|
-
|
23
|
-
context
|
24
|
-
end
|
25
|
-
|
26
|
-
# Built-in providers for common authentication systems
|
27
|
-
def self.devise_provider
|
28
|
-
lambda do |_event, hint|
|
29
|
-
return {} unless defined?(Devise) && defined?(Warden)
|
30
|
-
|
31
|
-
# Try to get user from Warden (used by Devise)
|
32
|
-
if request = hint[:request]
|
33
|
-
user = request.env["warden"]&.user
|
34
|
-
return {} unless user
|
35
|
-
|
36
|
-
{
|
37
|
-
id: user.id,
|
38
|
-
email: user.email,
|
39
|
-
username: user.respond_to?(:username) ? user.username : nil,
|
40
|
-
created_at: user.created_at,
|
41
|
-
role: user.respond_to?(:role) ? user.role : nil
|
42
|
-
}.compact
|
43
|
-
end
|
44
|
-
|
45
|
-
{}
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
def self.omniauth_provider
|
50
|
-
lambda do |_event, hint|
|
51
|
-
return {} unless defined?(OmniAuth)
|
52
|
-
|
53
|
-
if (request = hint[:request]) && (auth_info = request.env["omniauth.auth"])
|
54
|
-
{
|
55
|
-
provider: auth_info["provider"],
|
56
|
-
uid: auth_info["uid"],
|
57
|
-
name: auth_info.dig("info", "name"),
|
58
|
-
email: auth_info.dig("info", "email"),
|
59
|
-
username: auth_info.dig("info", "nickname")
|
60
|
-
}.compact
|
61
|
-
end
|
62
|
-
|
63
|
-
{}
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
def self.session_provider
|
68
|
-
lambda do |_event, hint|
|
69
|
-
return {} unless hint[:request]
|
70
|
-
|
71
|
-
request = hint[:request]
|
72
|
-
session = begin
|
73
|
-
request.session
|
74
|
-
rescue StandardError
|
75
|
-
{}
|
76
|
-
end
|
77
|
-
|
78
|
-
{
|
79
|
-
session_id: session[:session_id] || session["session_id"],
|
80
|
-
user_id: session[:user_id] || session["user_id"],
|
81
|
-
csrf_token: session[:_csrf_token] || session["_csrf_token"]
|
82
|
-
}.compact
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
def self.thread_local_provider
|
87
|
-
lambda do |_event, _hint|
|
88
|
-
# Get user from thread-local storage
|
89
|
-
Thread.current[:current_user] || {}
|
90
|
-
end
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
data/lib/lapsoss/utils.rb
DELETED
data/lib/tasks/cassettes.rake
DELETED
@@ -1,50 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "yaml"
|
4
|
-
|
5
|
-
namespace :cassettes do
|
6
|
-
desc "Sanitize VCR cassettes by scrubbing sensitive data"
|
7
|
-
task :sanitize do
|
8
|
-
dir = File.expand_path("../../test/cassettes", __dir__)
|
9
|
-
files = Dir[File.join(dir, "*.yml")]
|
10
|
-
puts "Sanitizing #{files.size} cassette(s) in #{dir}..."
|
11
|
-
|
12
|
-
email_re = /\b[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\b/i
|
13
|
-
hostname_key_re = /"hostname":"[^"]+"/
|
14
|
-
home_path_re = Regexp.new(Regexp.escape(Dir.home))
|
15
|
-
api_key_pairs = [
|
16
|
-
[ /"apiKey":"[^"]+"/, '"apiKey":"<INSIGHT_HUB_API_KEY>"' ],
|
17
|
-
[ /"access_token":"[^"]+"/, '"access_token":"<ROLLBAR_ACCESS_TOKEN>"' ],
|
18
|
-
[ /api_key=[^&]+/, "api_key=<APPSIGNAL_FRONTEND_API_KEY>" ]
|
19
|
-
]
|
20
|
-
|
21
|
-
header_key_map = {
|
22
|
-
"X-Rollbar-Access-Token" => "<ROLLBAR_ACCESS_TOKEN>",
|
23
|
-
"Bugsnag-Api-Key" => "<INSIGHT_HUB_API_KEY>",
|
24
|
-
"Authorization" => "<AUTHORIZATION>"
|
25
|
-
}
|
26
|
-
|
27
|
-
files.each do |file|
|
28
|
-
content = File.read(file)
|
29
|
-
|
30
|
-
# Replace patterns in raw content safely
|
31
|
-
content = content.gsub(email_re, "<EMAIL>")
|
32
|
-
content = content.gsub(hostname_key_re, '"hostname":"<HOSTNAME>"')
|
33
|
-
content = content.gsub(home_path_re, "/home/user")
|
34
|
-
api_key_pairs.each { |(re, rep)| content = content.gsub(re, rep) }
|
35
|
-
|
36
|
-
# Normalize User-Agent references to avoid machine leakage
|
37
|
-
content = content.gsub(/User-Agent:\n\s+-\s+.+/, "User-Agent:\n - lapsoss/x.y.z") rescue nil
|
38
|
-
|
39
|
-
# Rewrite known header values
|
40
|
-
header_key_map.each do |hdr, placeholder|
|
41
|
-
content = content.gsub(/#{hdr}:(?:\n\s+-\s+.*)/, "#{hdr}:\n - \"#{placeholder}\"")
|
42
|
-
end
|
43
|
-
|
44
|
-
File.write(file, content)
|
45
|
-
puts " scrubbed: #{File.basename(file)}"
|
46
|
-
end
|
47
|
-
|
48
|
-
puts "Done."
|
49
|
-
end
|
50
|
-
end
|