sqreen 1.15.8 → 1.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5c3b1883dab046bead45a9f4918b23fd8c645c63d9e8526f2659a4864148f6f6
4
- data.tar.gz: be1317fecaa1476cabb5994ed25d8ba6852de00251004d43ca856aaed25dd225
3
+ metadata.gz: 88700ab65a0ac3080eb109fff2ab757f69da29f2a875bfa054a6ac89f6068ad4
4
+ data.tar.gz: 01f4d1e68fce364d10c4e54f37ef32e1b8490d36118d9cbe74488743ec3f6a31
5
5
  SHA512:
6
- metadata.gz: 40e08b9bdfe5fb86820ff3dae712c180637fdfe488233505dcfcd4b45b2ea465ba3658d5c9b63a2214c45309b8a975a9a4f66dc47e6a560f01d96d8b855693f8
7
- data.tar.gz: 04d801be1033efcd75e3b353ff7a9ffa44210fe0dd38fe04870bc7b7aa58509a6f09fc6ef23e2edb2967ed3f7e933bc8cd5948c5dc83259ffdef79ac55522759
6
+ metadata.gz: dcba32ae629498dce1051f3d9878ce974244622f967a7dd51fe9a5208c770fb4aef27f3aa502fe2033cc0ba1e8e94a2d28550777a1293184f7c86bc1bb0e266e
7
+ data.tar.gz: e8753a65ad853c63f6a0d18de2f453b7840db8a2b2fb24add9616cadf4e9e495d3d97dc33bc6c7b8d0d2d0460e2f48a98ef4216e984fd91c4c0b8d5db5fa7e63
data/Rakefile CHANGED
@@ -13,6 +13,7 @@ end
13
13
 
14
14
  Rake::TestTask.new do |t|
15
15
  t.pattern = 'test/**/*.rb'
16
+ t.warning = false
16
17
  t.libs << 'test'
17
18
  end
18
19
 
data/lib/sqreen.rb CHANGED
@@ -17,7 +17,7 @@ require 'thread'
17
17
  # Auto start the instrumentation.
18
18
 
19
19
  Sqreen.framework.on_start do |framework|
20
- if RUBY_VERSION =~ /\A2\.5\.[01]\z/ && Sqreen.framework.on_pre_fork_preload?
20
+ if Sqreen.framework.on_pre_fork_preload?
21
21
  STDERR.puts("Avoiding launching sqreen thread pre-fork") # sqreen log unavailable
22
22
  next
23
23
  end
@@ -265,45 +265,45 @@ module Sqreen
265
265
  end
266
266
  end
267
267
 
268
- # Blocks a user at the point Sqreen::identify()
269
- # or Sqreen::auth_track() are called
270
- class BlockUser < Base
271
- self.type_name = 'block_user'
272
-
273
- class << self
274
- def actions_matching(identity_params)
275
- return [] unless @idx
276
- key = stringify_keys(identity_params)
277
- actions = @idx[key]
278
- actions || []
279
- end
280
-
281
- def index(params, action)
282
- @idx ||= {}
283
- users = params['users']
284
- raise ::Sqreen::Exception, 'nil "users" param for block_user action' if users.nil?
285
- raise ::Sqreen::Exception, '"users" param must be an array' unless users.is_a? Array
268
+ module UserActionClass
269
+ def actions_matching(identity_params)
270
+ return [] unless @idx
271
+ key = stringify_keys(identity_params)
272
+ actions = @idx[key]
273
+ actions || []
274
+ end
286
275
 
287
- users.each do |u|
288
- @idx[u] ||= []
289
- @idx[u] << action
290
- end
276
+ def index(params, action)
277
+ @idx ||= {}
278
+ users = params['users']
279
+ raise ::Sqreen::Exception, 'nil "users" param for block_user action' if users.nil?
280
+ raise ::Sqreen::Exception, '"users" param must be an array' unless users.is_a? Array
281
+
282
+ users.each do |u|
283
+ @idx[u] ||= []
284
+ @idx[u] << action
291
285
  end
286
+ end
292
287
 
293
- def clear
294
- @idx = {}
295
- end
288
+ def clear
289
+ @idx = {}
290
+ end
296
291
 
297
- private
292
+ private
298
293
 
299
- def stringify_keys(hash)
300
- Hash[
301
- hash.map { |k, v| [k.to_s, v] }
302
- ]
303
- end
294
+ def stringify_keys(hash)
295
+ Hash[
296
+ hash.map { |k, v| [k.to_s, v] }
297
+ ]
304
298
  end
299
+ end
305
300
 
306
- # BlockUser proper definition continues
301
+ # Blocks a user at the point Sqreen::identify()
302
+ # or Sqreen::auth_track() are called
303
+ class BlockUser < Base
304
+ extend UserActionClass
305
+
306
+ self.type_name = 'block_user'
307
307
 
308
308
  def initialize(id, opts, params = {})
309
309
  super(id, opts)
@@ -330,5 +330,39 @@ module Sqreen
330
330
  { 'user' => identity_params }
331
331
  end
332
332
  end
333
+
334
+ # Redirects a user at the point Sqreen::identify()
335
+ # or Sqreen::auth_track() are called
336
+ class RedirectUser < Base
337
+ extend UserActionClass
338
+
339
+ self.type_name = 'redirect_user'
340
+
341
+ def initialize(id, opts, params = {})
342
+ super(id, opts)
343
+ @redirect_url = params['url']
344
+ raise "no url provided for action #{id}" unless @redirect_url
345
+ end
346
+
347
+ def do_run(identity_params)
348
+ Sqreen.log.info 'Will request redirect for user with identity ' \
349
+ "#{identity_params} (action: #{id})."
350
+
351
+ e = Sqreen::AttackBlocked.new(
352
+ "Redirected user with identity #{identity_params} " \
353
+ 'due to automatic security response. No action is required'
354
+ )
355
+ e.redirect_url = @redirect_url
356
+
357
+ {
358
+ :status => :raise,
359
+ :exception => e,
360
+ }
361
+ end
362
+
363
+ def event_properties(identity_params)
364
+ { 'user' => identity_params }
365
+ end
366
+ end
333
367
  end
334
368
  end
@@ -35,6 +35,8 @@ module Sqreen
35
35
  :default => 'https://back.sqreen.io' },
36
36
  { :env => :SQREEN_TOKEN, :name => :token,
37
37
  :default => nil },
38
+ { :env => :SQREEN_APP_NAME, :name => :app_name,
39
+ :default => nil },
38
40
  { :env => :SQREEN_RULES, :name => :local_rules,
39
41
  :default => nil },
40
42
  { :env => :SQREEN_RULES_SIGNATURE, :name => :rules_verify_signature,
@@ -28,6 +28,8 @@ module Sqreen
28
28
  # Sqreen users when watching their logs. It should not raise any concern to
29
29
  # them.
30
30
  class AttackBlocked < Exception
31
+ attr_accessor :redirect_url
32
+
31
33
  def log_message(msg)
32
34
  Sqreen.log.warn(msg)
33
35
  end
@@ -178,6 +178,10 @@ module Sqreen
178
178
  nil
179
179
  end
180
180
 
181
+ def application_name
182
+ nil
183
+ end
184
+
181
185
  # Main entry point for sqreen.
182
186
  # launch whenever we are ready
183
187
  def on_start
@@ -70,8 +70,11 @@ module Sqreen
70
70
  end
71
71
 
72
72
  def root
73
- return nil unless @application
74
- @application.root
73
+ Rails.application.root
74
+ end
75
+
76
+ def application_name
77
+ Rails.application.class.name.deconstantize.underscore
75
78
  end
76
79
 
77
80
  # Register a new initializer in rails to ba called when we are starting up
@@ -11,7 +11,7 @@ require 'sqreen/rules_signature'
11
11
  require 'sqreen/shared_storage'
12
12
  require 'sqreen/rules_callbacks/record_request_context'
13
13
  require 'sqreen/rules_callbacks/run_req_start_actions'
14
- require 'sqreen/rules_callbacks/run_block_user_actions'
14
+ require 'sqreen/rules_callbacks/run_user_actions'
15
15
  require 'sqreen/mono_time'
16
16
  require 'set'
17
17
 
@@ -706,8 +706,8 @@ module Sqreen
706
706
  def hardcoded_callbacks(framework)
707
707
  [
708
708
  Sqreen::Rules::RunReqStartActions.new(framework),
709
- Sqreen::Rules::RunBlockUserActions.new(Sqreen, :identify, 0),
710
- Sqreen::Rules::RunBlockUserActions.new(Sqreen, :auth_track, 1),
709
+ Sqreen::Rules::RunUserActions.new(Sqreen, :identify, 0),
710
+ Sqreen::Rules::RunUserActions.new(Sqreen, :auth_track, 1),
711
711
  ]
712
712
  end
713
713
 
@@ -81,89 +81,6 @@ module Sqreen
81
81
  end
82
82
  end
83
83
 
84
- class JsonConversion
85
- class << self
86
- if defined?(::JSON::Ext::Generator::State)
87
- def convert_arguments(_args)
88
- args = purge_dangerous_objs(_args)
89
- new_state.generate(args)
90
- rescue ::JSON::GeneratorError, ::Encoding::UndefinedConversionError
91
- fixed_args = fixup_bad_encoding(args)
92
- new_state.generate(fixed_args)
93
- end
94
- else
95
- def convert_arguments(_args)
96
- args = purge_dangerous_objs(_args)
97
- args.to_json(new_state)
98
- rescue ::EncodingError
99
- fixed_args = fixup_bad_encoding(args)
100
- fixed_args.to_json(new_state)
101
- end
102
- end
103
-
104
- private
105
-
106
- def new_state
107
- ::JSON::SAFE_STATE_PROTOTYPE.dup
108
- end
109
-
110
- def fixup_bad_encoding(arg, max_depth = 100)
111
- return nil if max_depth <= 0
112
-
113
- if arg.is_a?(::Array)
114
- return arg.map { |it| fixup_bad_encoding(it, max_depth - 1) }
115
- end
116
-
117
- if arg.is_a?(::Hash)
118
- return ::Hash[
119
- arg.map do |k, v|
120
- [fixup_bad_encoding(k, max_depth - 1),
121
- fixup_bad_encoding(v, max_depth - 1)]
122
- end
123
- ]
124
- end
125
-
126
- return arg unless arg.is_a?(::String)
127
-
128
- unless arg.valid_encoding?
129
- return arg.dup.force_encoding(::Encoding::ISO_8859_1)
130
- end
131
-
132
- # encoding is valid if it reaches this point
133
- return arg if arg.encoding == ::Encoding::UTF_8
134
-
135
- begin
136
- arg.encode(::Encoding::UTF_8)
137
- rescue ::Encoding::UndefinedConversionError
138
- arg.dup.force_encoding(::Encoding::ISO_8859_1)
139
- end
140
- end
141
-
142
- def purge_dangerous_objs(arg, max_depth = 100)
143
- return nil if max_depth <= 0
144
-
145
- if arg.is_a?(::Array)
146
- arg.map { |it| purge_dangerous_objs(it, max_depth - 1) }
147
- elsif arg.is_a?(::Hash)
148
- Hash[
149
- arg.map do |k, v|
150
- [purge_dangerous_objs(k, max_depth - 1),
151
- purge_dangerous_objs(v, max_depth - 1)]
152
- end
153
- ]
154
- elsif arg.is_a?(::String) || arg.nil? || arg == false ||
155
- arg == true || arg.is_a?(::Fixnum) || arg.is_a?(::Bignum) ||
156
- arg.is_a?(::Float)
157
- arg
158
- elsif arg.respond_to?(:to_json)
159
- arg.to_s
160
- else
161
- arg
162
- end
163
- end
164
- end
165
- end
166
-
167
84
  class MiniRacerExecutableJs < ExecutableJs
168
85
  @@ctx_defined = false
169
86
 
@@ -194,9 +111,7 @@ module Sqreen
194
111
  ctx.add_code(@code_id, @code) unless ctx.has_code?(@code_id)
195
112
 
196
113
  begin
197
- json_args = JsonConversion.convert_arguments(arguments)
198
- ctx.eval_unsafe(
199
- "sqreen_data['#{@code_id}']['#{cb_name}'].apply(this, #{json_args})", nil, budget)
114
+ ctx.call("sqreen_#{@code_id}_#{cb_name}", *arguments)
200
115
  rescue @module::ScriptTerminatedError
201
116
  Sqreen.log.debug "ScriptTerminatedError/#{cb_name}"
202
117
  nil
@@ -205,7 +120,7 @@ module Sqreen
205
120
  end
206
121
 
207
122
  def self.code_id(code)
208
- Digest::MD5.base64digest(code)
123
+ Digest::MD5.hexdigest(code)
209
124
  end
210
125
 
211
126
  private
@@ -231,6 +146,9 @@ module Sqreen
231
146
  end
232
147
 
233
148
  def add_code(code_id, code)
149
+ # It's important that the definition is run in its own scope (by executing it inside an anonymous function)
150
+ # Otherwise some auxiliary functions that the backend server sends will collide the name
151
+ # Because they're defined with `var`, running the definitions inside a function is enough
234
152
  eval_unsafe "(function() { #{code} })()"
235
153
  transf_global_funcs code_id
236
154
  @code_ids ||= Set.new
@@ -271,20 +189,14 @@ module Sqreen
271
189
  private
272
190
 
273
191
  def transf_global_funcs(code_id)
192
+ # Multiple callbacks may share the same name. In order to avoid collisions, we rename them here.
274
193
  eval_unsafe <<EOD
275
- if (typeof sqreen_data === 'undefined') {
276
- sqreen_data = {};
277
- }
278
-
279
- group = {};
280
194
  Object.keys(this).forEach(name => {
281
- if (typeof this[name] === "function") {
282
- group[name] = this[name];
195
+ if (typeof this[name] === "function" && !name.startsWith("sqreen_")) {
196
+ this['sqreen_#{code_id}_' + name] = this[name];
283
197
  this[name] = undefined;
284
198
  }
285
199
  });
286
- sqreen_data['#{code_id}'] = group
287
- delete group;
288
200
  EOD
289
201
 
290
202
  end
@@ -19,6 +19,20 @@ module Sqreen
19
19
 
20
20
  def call(env)
21
21
  @app.call(env)
22
+ rescue => e
23
+ sqreen_attack = nil
24
+ if e.is_a?(Sqreen::AttackBlocked)
25
+ sqreen_attack = e
26
+ elsif e.respond_to?(:original_exception) &&
27
+ e.original_exception.is_a?(Sqreen::AttackBlocked)
28
+ sqreen_attack = e.original_exception
29
+ end
30
+
31
+ if sqreen_attack && sqreen_attack.redirect_url
32
+ return [303, { 'Location' => sqreen_attack.redirect_url }, ['']]
33
+ else
34
+ raise
35
+ end
22
36
  end
23
37
  end
24
38
 
@@ -6,6 +6,19 @@ module Sqreen
6
6
  false
7
7
  end
8
8
 
9
+ @has_thread_cpu_time = begin
10
+ Process.clock_gettime Process::CLOCK_THREAD_CPUTIME_ID
11
+ true
12
+ rescue StandardError
13
+ false
14
+ end
15
+
16
+ class << self
17
+ def thread_cpu_time?
18
+ @has_thread_cpu_time
19
+ end
20
+ end
21
+
9
22
  if has_mono_time
10
23
  def self.time
11
24
  Process.clock_gettime Process::CLOCK_MONOTONIC
@@ -15,4 +28,14 @@ module Sqreen
15
28
  Time.now.to_f
16
29
  end
17
30
  end
31
+
32
+ if @has_thread_cpu_time
33
+ def self.thread_cpu_time
34
+ Process.clock_gettime Process::CLOCK_THREAD_CPUTIME_ID
35
+ end
36
+ else
37
+ def self.thread_cpu_time
38
+ 0
39
+ end
40
+ end
18
41
  end
@@ -15,6 +15,7 @@ module Sqreen
15
15
  EVENT_REQ = 'req'.freeze # request total time
16
16
  EVENT_TOTAL_TIME = 'sq'.freeze # sqreen total overhead callback time
17
17
  EVENT_PERCENT = 'pct'.freeze # sqreen total overhead percent of time
18
+ EVENT_SQ_THREAD_CPU_PCT = 'sq_thread_cpu_pct'.freeze # sqreen thread cpu time
18
19
 
19
20
  # @param metrics_store [Sqreen::MetricsStore]
20
21
  def initialize(metrics_store, period, perf_metric_opts, perf_metric_percent_opts)
@@ -23,6 +24,8 @@ module Sqreen
23
24
  @subid = nil
24
25
  @perf_metric_opts = perf_metric_opts
25
26
  @perf_metric_percent_opts = perf_metric_percent_opts
27
+ @clock_time = Sqreen.time
28
+ @watcher_cpu_time = 0
26
29
  end
27
30
 
28
31
  def enable
@@ -38,6 +41,12 @@ module Sqreen
38
41
  'name' => EVENT_PERCENT, 'period' => period, 'kind' => 'Binning', 'options' => @perf_metric_percent_opts
39
42
  )
40
43
 
44
+ if Sqreen.thread_cpu_time?
45
+ metrics_store.create_metric('name' => EVENT_SQ_THREAD_CPU_PCT,
46
+ 'period' => period, 'kind' => 'Binning',
47
+ 'options' => @perf_metric_percent_opts)
48
+ end
49
+
41
50
  @subid = Sqreen::PerformanceNotifications.subscribe(&method(:log))
42
51
  end
43
52
 
@@ -85,6 +94,26 @@ module Sqreen
85
94
  )
86
95
  end
87
96
 
97
+ def finish_watcher_run
98
+ return unless Sqreen.thread_cpu_time?
99
+ new_clock_time = Sqreen.time
100
+ # collect observation at min 30 second intervals so it's nicely averaged
101
+ return if new_clock_time - @clock_time < 30.0
102
+
103
+ clock_time_before = @clock_time
104
+ watcher_cpu_time_before = @watcher_cpu_time
105
+ @clock_time = new_clock_time
106
+ @watcher_cpu_time = Sqreen.thread_cpu_time
107
+
108
+ clock_time_diff = @clock_time - clock_time_before
109
+ watcher_cpu_diff = @watcher_cpu_time - watcher_cpu_time_before
110
+
111
+ Sqreen.observations_queue.push(
112
+ [EVENT_SQ_THREAD_CPU_PCT, nil,
113
+ (watcher_cpu_diff * 100.0) / clock_time_diff, Time.now.utc]
114
+ )
115
+ end
116
+
88
117
  private
89
118
 
90
119
  # @return [Sqreen::MetricsStore]
@@ -111,7 +140,7 @@ module Sqreen
111
140
  @instance = new(metrics_store, period,
112
141
  {'base'=> base, 'factor' => factor},
113
142
  {'base' => base_pct, 'factor' => factor_pct}
114
- ).tap(&:enable)
143
+ ).tap(&:enable)
115
144
  end
116
145
 
117
146
  def disable
@@ -129,6 +158,11 @@ module Sqreen
129
158
  return unless instance
130
159
  instance.finish_request
131
160
  end
161
+
162
+ def finish_watcher_run
163
+ return unless instance
164
+ instance.finish_watcher_run
165
+ end
132
166
  end
133
167
  end
134
168
  end
@@ -7,14 +7,15 @@ require 'sqreen/actions'
7
7
  module Sqreen
8
8
  module Rules
9
9
  # Runs the block_user actions (for hooking Sqreen.{identify,auth_user})
10
- class RunBlockUserActions < CB
10
+ class RunUserActions < CB
11
11
  def initialize(klass, method, auth_keys_idx)
12
12
  super(klass, method)
13
13
  @auth_keys_idx = auth_keys_idx
14
14
  end
15
15
 
16
16
  def post(_retval, _inst, args, _budget = nil)
17
- actions = actions_repo.get('block_user', args[@auth_keys_idx])
17
+ actions = actions_repo.get('block_user', args[@auth_keys_idx]) +
18
+ actions_repo.get('redirect_user', args[@auth_keys_idx])
18
19
 
19
20
  actions.each do |action|
20
21
  res = action.run args[@auth_keys_idx]
data/lib/sqreen/runner.rb CHANGED
@@ -109,6 +109,7 @@ module Sqreen
109
109
  @running = true
110
110
 
111
111
  @token = @configuration.get(:token)
112
+ @app_name = @configuration.get(:app_name)
112
113
  @url = @configuration.get(:url)
113
114
  Sqreen.update_whitelisted_paths([])
114
115
  Sqreen.update_whitelisted_ips({})
@@ -121,7 +122,7 @@ module Sqreen
121
122
  self.metrics_engine = MetricsStore.new
122
123
  @instrumenter = Instrumentation.new(metrics_engine)
123
124
 
124
- Sqreen.log.warn "using token #{@token}"
125
+ Sqreen.log.warn "Using token #{@token}"
125
126
  response = create_session(session_class)
126
127
  wanted_features = response.fetch('features', {})
127
128
  conf_initial_features = configuration.get(:initial_features)
@@ -151,7 +152,7 @@ module Sqreen
151
152
  end
152
153
 
153
154
  def create_session(session_class)
154
- @session = session_class.new(@url, @token)
155
+ @session = session_class.new(@url, @token, @app_name)
155
156
  session.login(@framework)
156
157
  end
157
158
 
@@ -378,6 +379,8 @@ module Sqreen
378
379
  Sqreen.log.debug 'Forced an heartbeat'
379
380
  periodic_cleanup # will trigger do_heartbeat since it's time
380
381
  end
382
+ ensure
383
+ PerformanceNotifications::BinnedMetrics.finish_watcher_run
381
384
  end
382
385
 
383
386
  def periodic_cleanup
@@ -44,8 +44,9 @@ module Sqreen
44
44
 
45
45
  attr_accessor :request_compression
46
46
 
47
- def initialize(server_url, token)
47
+ def initialize(server_url, token, app_name = nil)
48
48
  @token = token
49
+ @app_name = app_name
49
50
  @session_id = nil
50
51
  @server_url = server_url
51
52
  @request_compression = false
@@ -221,7 +222,12 @@ module Sqreen
221
222
  end
222
223
 
223
224
  def login(framework)
224
- headers = { 'x-api-key' => @token }
225
+ headers = {
226
+ 'x-api-key' => @token,
227
+ 'x-app-name' => @app_name || framework.application_name,
228
+ }.reject { |k, v| v == nil }
229
+
230
+ Sqreen.log.warn "Using app name: #{headers['x-app-name']}"
225
231
 
226
232
  res = resilient_post('app-login', RuntimeInfos.all(framework), headers)
227
233
 
@@ -1,5 +1,5 @@
1
1
  # Copyright (c) 2015 Sqreen. All Rights Reserved.
2
2
  # Please refer to our terms for more information: https://www.sqreen.io/terms.html
3
3
  module Sqreen
4
- VERSION = '1.15.8'.freeze
4
+ VERSION = '1.16.0'.freeze
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sqreen
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.15.8
4
+ version: 1.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sqreen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-01-07 00:00:00.000000000 Z
11
+ date: 2019-01-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sq_mini_racer
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 0.2.4.sqreen1
19
+ version: 0.2.4.sqreen2
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 0.2.4.sqreen1
26
+ version: 0.2.4.sqreen2
27
27
  description: Sqreen is a SaaS based Application protection and monitoring platform
28
28
  that integrates directly into your Ruby applications. Learn more at https://sqreen.io.
29
29
  email: contact@sqreen.io
@@ -104,8 +104,8 @@ files:
104
104
  - lib/sqreen/rules_callbacks/record_request_context.rb
105
105
  - lib/sqreen/rules_callbacks/reflected_xss.rb
106
106
  - lib/sqreen/rules_callbacks/regexp_rule.rb
107
- - lib/sqreen/rules_callbacks/run_block_user_actions.rb
108
107
  - lib/sqreen/rules_callbacks/run_req_start_actions.rb
108
+ - lib/sqreen/rules_callbacks/run_user_actions.rb
109
109
  - lib/sqreen/rules_callbacks/shell_env.rb
110
110
  - lib/sqreen/rules_callbacks/url_matches.rb
111
111
  - lib/sqreen/rules_callbacks/user_agent_matches.rb