rack-libinjection 0.1.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.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +55 -0
  3. data/CHANGELOG.md +112 -0
  4. data/GET_STARTED.md +418 -0
  5. data/LICENSE-libinjection.txt +33 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +68 -0
  8. data/SECURITY.md +65 -0
  9. data/ext/libinjection/extconf.rb +113 -0
  10. data/ext/libinjection/libinjection_ext.c +1132 -0
  11. data/ext/libinjection/vendor/libinjection/.vendored +5 -0
  12. data/ext/libinjection/vendor/libinjection/COPYING +33 -0
  13. data/ext/libinjection/vendor/libinjection/MIGRATION.md +393 -0
  14. data/ext/libinjection/vendor/libinjection/README.md +251 -0
  15. data/ext/libinjection/vendor/libinjection/src/libinjection.h +70 -0
  16. data/ext/libinjection/vendor/libinjection/src/libinjection_error.h +26 -0
  17. data/ext/libinjection/vendor/libinjection/src/libinjection_html5.c +830 -0
  18. data/ext/libinjection/vendor/libinjection/src/libinjection_html5.h +56 -0
  19. data/ext/libinjection/vendor/libinjection/src/libinjection_sqli.c +2342 -0
  20. data/ext/libinjection/vendor/libinjection/src/libinjection_sqli.h +297 -0
  21. data/ext/libinjection/vendor/libinjection/src/libinjection_sqli_data.h +9651 -0
  22. data/ext/libinjection/vendor/libinjection/src/libinjection_xss.c +1203 -0
  23. data/ext/libinjection/vendor/libinjection/src/libinjection_xss.h +23 -0
  24. data/lib/libinjection/version.rb +6 -0
  25. data/lib/libinjection.rb +31 -0
  26. data/lib/rack/libinjection.rb +586 -0
  27. data/lib/rack-libinjection.rb +3 -0
  28. data/samples/README.md +67 -0
  29. data/samples/libinjection_detect_raw_hot_path.rb +161 -0
  30. data/samples/rack_all_surfaces_hot_path.rb +198 -0
  31. data/samples/rack_params_hot_path.rb +166 -0
  32. data/samples/rack_query_hot_path.rb +176 -0
  33. data/samples/results/.gitkeep +0 -0
  34. data/script/fuzz_smoke.rb +39 -0
  35. data/script/vendor_libs.rb +227 -0
  36. data/test/test_helper.rb +7 -0
  37. data/test/test_libinjection.rb +223 -0
  38. data/test/test_middleware.rb +404 -0
  39. metadata +148 -0
@@ -0,0 +1,404 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "test_helper"
4
+
5
+ class TestRackLibInjection < Minitest::Test
6
+ def app(**options)
7
+ Rack::LibInjection.new(
8
+ ->(env) { [200, { "content-type" => "text/plain" }, [env[Rack::LibInjection::ATTACK_ENV_KEY].inspect]] },
9
+ **options
10
+ )
11
+ end
12
+
13
+ def test_report_mode_continues_request
14
+ res = Rack::MockRequest.new(app).get("/search?q=1%20OR%201%3D1--")
15
+ assert_equal 200, res.status
16
+ assert_includes res.body, ":sqli"
17
+ end
18
+
19
+ def test_block_mode_returns_403
20
+ res = Rack::MockRequest.new(app(mode: :block)).get("/search?q=1%20OR%201%3D1--")
21
+ assert_equal 403, res.status
22
+ end
23
+
24
+ def test_off_mode_does_not_scan_or_set_env_key
25
+ res = Rack::MockRequest.new(app(mode: :off)).get("/search?q=1%20OR%201%3D1--")
26
+
27
+ assert_equal 200, res.status
28
+ assert_equal "nil", res.body
29
+ end
30
+
31
+ def test_empty_attack_list_is_mutable_for_downstream_rack_apps
32
+ middleware = Rack::LibInjection.new(
33
+ lambda { |env|
34
+ attacks = env.fetch(Rack::LibInjection::ATTACK_ENV_KEY)
35
+ attacks << :downstream_marker
36
+ [200, { "content-type" => "text/plain" }, [attacks.inspect]]
37
+ }
38
+ )
39
+
40
+ res = Rack::MockRequest.new(middleware).get("/clean?q=ordinary")
41
+
42
+ assert_equal 200, res.status
43
+ assert_includes res.body, ":downstream_marker"
44
+ end
45
+
46
+ def test_notifier_receives_payload
47
+ events = []
48
+ notifier = ->(event, payload) { events << [event, payload] }
49
+ Rack::MockRequest.new(app(notifier: notifier)).get("/search?q=1%20OR%201%3D1--")
50
+
51
+ assert_equal "rack.libinjection.attack", events.fetch(0).fetch(0)
52
+ assert_equal :sqli, events.fetch(0).fetch(1).fetch(:type)
53
+ end
54
+
55
+ def test_xss_payload_is_reported
56
+ res = Rack::MockRequest.new(app).get("/search?q=%3Cscript%3Ealert(1)%3C/script%3E")
57
+
58
+ assert_equal 200, res.status
59
+ assert_includes res.body, ":xss"
60
+ end
61
+
62
+ def test_ignore_params_skips_key_and_value
63
+ events = []
64
+ notifier = ->(event, payload) { events << [event, payload] }
65
+
66
+ res = Rack::MockRequest.new(app(ignore_params: %w[q], notifier: notifier)).get("/search?q=1%20OR%201%3D1--")
67
+
68
+ assert_equal 200, res.status
69
+ refute_includes res.body, ":sqli"
70
+ assert_empty events.select { |event, _| event == "rack.libinjection.attack" }
71
+ end
72
+
73
+ def test_nested_params_are_scanned
74
+ res = Rack::MockRequest.new(app).get("/search?filter[items][]=1%20OR%201%3D1--")
75
+
76
+ assert_equal 200, res.status
77
+ assert_includes res.body, ":sqli"
78
+ assert_includes res.body, "filter.items[0]"
79
+ end
80
+
81
+ def test_uploaded_filename_is_scanned
82
+ middleware = app
83
+ upload = Struct.new(:original_filename).new("1 OR 1=1--.jpg")
84
+ attacks = []
85
+
86
+ middleware.send(
87
+ :walk_into,
88
+ attacks,
89
+ upload,
90
+ location: :params,
91
+ path: "avatar",
92
+ depth: 0,
93
+ context: { method: "POST", path: "/upload", ip: "127.0.0.1" }
94
+ )
95
+
96
+ assert_equal :sqli, attacks.fetch(0).type
97
+ assert_equal "avatar.filename", attacks.fetch(0).key
98
+ end
99
+
100
+ def test_password_is_scanned_by_default
101
+ res = Rack::MockRequest.new(app).get("/login?password=1%20OR%201%3D1--")
102
+
103
+ assert_equal 200, res.status
104
+ assert_includes res.body, ":sqli"
105
+ assert_includes res.body, "password"
106
+ end
107
+
108
+ def test_max_value_bytes_skips_and_notifies
109
+ events = []
110
+ notifier = ->(event, payload) { events << [event, payload] }
111
+
112
+ Rack::MockRequest.new(app(max_value_bytes: 3, notifier: notifier)).get("/search?q=abcd")
113
+
114
+ skipped = events.find { |event, _| event == "rack.libinjection.skipped" }
115
+ assert skipped
116
+ assert_equal :max_value_bytes, skipped.fetch(1).fetch(:reason)
117
+ assert_equal 4, skipped.fetch(1).fetch(:bytes)
118
+ end
119
+
120
+ def test_max_value_bytes_blocks_in_block_mode_by_default
121
+ res = Rack::MockRequest.new(app(mode: :block, max_value_bytes: 3)).get("/search?q=abcd")
122
+
123
+ assert_equal 403, res.status
124
+ end
125
+
126
+ def test_skipped_inputs_can_allow_oversized_values_in_block_mode
127
+ res = Rack::MockRequest.new(app(mode: :block, max_value_bytes: 3, skipped_inputs: :allow)).get("/search?q=abcd")
128
+
129
+ assert_equal 200, res.status
130
+ end
131
+
132
+ def test_max_depth_skips_and_notifies
133
+ events = []
134
+ notifier = ->(event, payload) { events << [event, payload] }
135
+
136
+ Rack::MockRequest.new(app(max_depth: 0, notifier: notifier)).get("/search?a[b]=1")
137
+
138
+ skipped = events.find { |event, _| event == "rack.libinjection.skipped" }
139
+ assert skipped
140
+ assert_equal :max_depth, skipped.fetch(1).fetch(:reason)
141
+ end
142
+
143
+ def test_max_depth_blocks_in_block_mode_by_default
144
+ res = Rack::MockRequest.new(app(mode: :block, max_depth: 0)).get("/search?a[b]=1")
145
+
146
+ assert_equal 403, res.status
147
+ end
148
+
149
+ def test_notify_skipped_can_be_disabled
150
+ events = []
151
+ notifier = ->(event, payload) { events << [event, payload] }
152
+
153
+ Rack::MockRequest.new(app(max_value_bytes: 3, notify_skipped: false, notifier: notifier)).get("/search?q=abcd")
154
+
155
+ assert_empty events
156
+ end
157
+
158
+ def test_path_scan_is_opt_in
159
+ res = Rack::MockRequest.new(app(scan: %i[path])).get("/items/1%20OR%201=1--")
160
+
161
+ assert_equal 200, res.status
162
+ assert_includes res.body, ":sqli"
163
+ end
164
+
165
+ def test_path_scan_decodes_twice_by_default
166
+ res = Rack::MockRequest.new(app(scan: %i[path])).get("/items/1%2520OR%25201%253D1--")
167
+
168
+ assert_equal 200, res.status
169
+ assert_includes res.body, ":sqli"
170
+ end
171
+
172
+ def test_path_segment_keys_start_at_zero_for_first_non_empty_segment
173
+ middleware = app(scan: %i[path])
174
+ attacks = []
175
+
176
+ middleware.send(
177
+ :scan_path_segments_into,
178
+ attacks,
179
+ "/1%20OR%201=1--/items",
180
+ { method: "GET", path: "/1 OR 1=1--/items", ip: "127.0.0.1" }
181
+ )
182
+
183
+ assert_equal "path[0]", attacks.fetch(0).key
184
+ end
185
+
186
+ def test_path_decode_depth_has_hard_cap
187
+ assert_raises ArgumentError do
188
+ app(scan: %i[path], path_decode_depth: Rack::LibInjection::MAX_PATH_DECODE_DEPTH + 1)
189
+ end
190
+ end
191
+
192
+ def test_path_decode_depth_can_disable_double_decode
193
+ middleware = app(scan: %i[path], path_decode_depth: 1)
194
+ attacks = []
195
+
196
+ middleware.send(
197
+ :scan_path_into,
198
+ attacks,
199
+ "/items/1%2520OR%25201%253D1--",
200
+ { method: "GET", path: "/items", ip: "127.0.0.1" }
201
+ )
202
+
203
+ assert_empty attacks
204
+ end
205
+
206
+ def test_headers_scan_is_opt_in
207
+ res = Rack::MockRequest.new(app(scan: %i[headers])).get("/", "HTTP_USER_AGENT" => "1 OR 1=1--")
208
+
209
+ assert_equal 200, res.status
210
+ assert_includes res.body, ":headers"
211
+ end
212
+
213
+ def test_standard_headers_are_ignored_by_default
214
+ res = Rack::MockRequest.new(app(scan: %i[headers])).get("/", "HTTP_ACCEPT" => "1 OR 1=1--")
215
+
216
+ assert_equal 200, res.status
217
+ refute_includes res.body, ":headers"
218
+ end
219
+
220
+ def test_ignored_headers_can_be_overridden
221
+ res = Rack::MockRequest.new(app(scan: %i[headers], ignore_headers: [])).get("/", "HTTP_ACCEPT" => "1 OR 1=1--")
222
+
223
+ assert_equal 200, res.status
224
+ assert_includes res.body, ":headers"
225
+ end
226
+
227
+ def test_cookies_scan_is_opt_in
228
+ res = Rack::MockRequest.new(app(scan: %i[cookies])).get("/", "HTTP_COOKIE" => "q=1%20OR%201=1--")
229
+
230
+ assert_equal 200, res.status
231
+ assert_includes res.body, ":cookies"
232
+ end
233
+
234
+ def test_cookie_names_are_not_scanned_by_default
235
+ middleware = app(scan: %i[cookies])
236
+ req = Struct.new(:cookies).new({ "1 OR 1=1--" => "ordinary" })
237
+ attacks = []
238
+
239
+ middleware.send(:scan_cookies_into, attacks, req, { method: "GET", path: "/", ip: "127.0.0.1" })
240
+
241
+ assert_empty attacks
242
+ end
243
+
244
+ def test_cookie_name_scanning_can_be_enabled
245
+ middleware = app(scan: %i[cookies], scan_cookie_names: true)
246
+ req = Struct.new(:cookies).new({ "1 OR 1=1--" => "ordinary" })
247
+ attacks = []
248
+
249
+ middleware.send(:scan_cookies_into, attacks, req, { method: "GET", path: "/", ip: "127.0.0.1" })
250
+
251
+ assert_equal :sqli, attacks.fetch(0).type
252
+ assert attacks.fetch(0).detected_in_key_name?
253
+ end
254
+
255
+
256
+ def test_query_scan_can_use_minimal_env_without_rack_request
257
+ middleware = app(scan: %i[query])
258
+ env = {
259
+ "REQUEST_METHOD" => "GET",
260
+ "PATH_INFO" => "/search",
261
+ "QUERY_STRING" => "q=1%20OR%201%3D1--",
262
+ "REMOTE_ADDR" => "127.0.0.1"
263
+ }
264
+
265
+ status, = middleware.call(env)
266
+
267
+ assert_equal 200, status
268
+ assert_equal :sqli, env.fetch(Rack::LibInjection::ATTACK_ENV_KEY).fetch(0).type
269
+ end
270
+
271
+ def test_query_scan_notifier_metadata_can_come_from_env
272
+ events = []
273
+ middleware = app(scan: %i[query], notifier: ->(event, payload) { events << [event, payload] })
274
+ env = {
275
+ "REQUEST_METHOD" => "GET",
276
+ "PATH_INFO" => "/search",
277
+ "QUERY_STRING" => "q=1%20OR%201%3D1--",
278
+ "REMOTE_ADDR" => "127.0.0.1"
279
+ }
280
+
281
+ middleware.call(env)
282
+
283
+ payload = events.fetch(0).fetch(1)
284
+ assert_equal "GET", payload.fetch(:method)
285
+ assert_equal "/search", payload.fetch(:path)
286
+ assert_equal "127.0.0.1", payload.fetch(:ip)
287
+ end
288
+
289
+ def test_query_scan_is_fast_raw_surface
290
+ res = Rack::MockRequest.new(app(scan: %i[query])).get("/search?q=1%20OR%201%3D1--")
291
+
292
+ assert_equal 200, res.status
293
+ assert_includes res.body, ":query"
294
+ assert_includes res.body, ":sqli"
295
+ end
296
+
297
+ def test_query_scan_decodes_twice
298
+ res = Rack::MockRequest.new(app(scan: %i[query])).get("/search?q=1%2520OR%25201%253D1--")
299
+
300
+ assert_equal 200, res.status
301
+ assert_includes res.body, ":query"
302
+ assert_includes res.body, ":sqli"
303
+ end
304
+
305
+
306
+ def test_query_scan_decodes_plus_as_space_in_native_hot_path
307
+ res = Rack::MockRequest.new(app(scan: %i[query])).get("/search?q=1+OR+1%3D1--")
308
+
309
+ assert_equal 200, res.status
310
+ assert_includes res.body, ":query"
311
+ assert_includes res.body, ":sqli"
312
+ end
313
+
314
+ def test_query_decode_depth_zero_scans_only_raw_query
315
+ res = Rack::MockRequest.new(app(scan: %i[query], path_decode_depth: 0)).get("/search?q=1%20OR%201%3D1--")
316
+
317
+ assert_equal 200, res.status
318
+ refute_includes res.body, ":sqli"
319
+ end
320
+
321
+ def test_threats_can_scan_only_sqli
322
+ res = Rack::MockRequest.new(app(threats: %i[sqli])).get("/search?q=%3Cscript%3Ealert(1)%3C/script%3E")
323
+
324
+ assert_equal 200, res.status
325
+ refute_includes res.body, ":xss"
326
+ end
327
+
328
+ def test_threats_can_scan_only_xss
329
+ res = Rack::MockRequest.new(app(threats: %i[xss])).get("/search?q=%3Cscript%3Ealert(1)%3C/script%3E")
330
+
331
+ assert_equal 200, res.status
332
+ assert_includes res.body, ":xss"
333
+ end
334
+
335
+ def test_parser_errors_block_in_block_mode_by_default
336
+ middleware = app(mode: :block)
337
+ middleware.define_singleton_method(:scan_params_into) do |_attacks, _req, _context|
338
+ raise ::LibInjection::ParserError, "parser failure"
339
+ end
340
+
341
+ res = Rack::MockRequest.new(middleware).get("/search?q=ordinary")
342
+
343
+ assert_equal 403, res.status
344
+ end
345
+
346
+ def test_parser_errors_can_raise
347
+ middleware = app(parser_errors: :raise)
348
+ middleware.define_singleton_method(:scan_params_into) do |_attacks, _req, _context|
349
+ raise ::LibInjection::ParserError, "parser failure"
350
+ end
351
+
352
+ assert_raises ::LibInjection::ParserError do
353
+ Rack::MockRequest.new(middleware).get("/search?q=ordinary")
354
+ end
355
+ end
356
+
357
+ def test_parameter_parser_errors_block_in_block_mode
358
+ error_class = Rack::LibInjection::PARAMETER_ERRORS.first || skip("Rack parameter error class unavailable")
359
+ req = Object.new
360
+ req.define_singleton_method(:params) { raise error_class, "bad params" }
361
+ middleware = app(mode: :block)
362
+ blocked = Rack::LibInjection.const_get(:ParserBlocked)
363
+
364
+ assert_raises blocked do
365
+ middleware.send(:scan_params_into, [], req, { method: "GET", path: "/", ip: "127.0.0.1" })
366
+ end
367
+ end
368
+
369
+ def test_parameter_parser_errors_are_notified
370
+ error_class = Rack::LibInjection::PARAMETER_ERRORS.first || RuntimeError
371
+ req = Object.new
372
+ req.define_singleton_method(:params) { raise error_class, "bad params" }
373
+ events = []
374
+ middleware = app(notifier: ->(event, payload) { events << [event, payload] })
375
+
376
+ middleware.send(:scan_params_into, [], req, { method: "GET", path: "/", ip: "127.0.0.1" })
377
+
378
+ event = events.find { |name, _| name == "rack.libinjection.error" }
379
+ assert event
380
+ assert_equal error_class.name, event.fetch(1).fetch(:error)
381
+ end
382
+
383
+ def test_notifier_errors_are_ignored_by_default
384
+ res = Rack::MockRequest.new(app(notifier: ->(*) { raise "boom" })).get("/search?q=1%20OR%201%3D1--")
385
+
386
+ assert_equal 200, res.status
387
+ end
388
+
389
+ def test_notifier_errors_can_raise
390
+ assert_raises RuntimeError do
391
+ Rack::MockRequest.new(app(notifier: ->(*) { raise "boom" }, notifier_errors: :raise)).get("/search?q=1%20OR%201%3D1--")
392
+ end
393
+ end
394
+
395
+ def test_logger_wins_over_active_support_detection
396
+ logger = Object.new
397
+ messages = []
398
+ logger.define_singleton_method(:warn) { |msg| messages << msg }
399
+
400
+ Rack::MockRequest.new(app(logger: logger)).get("/search?q=1%20OR%201%3D1--")
401
+
402
+ refute_empty messages
403
+ end
404
+ end
metadata ADDED
@@ -0,0 +1,148 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-libinjection
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Roman Haydarov
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rack
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '2.2'
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '4'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: '2.2'
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '4'
32
+ - !ruby/object:Gem::Dependency
33
+ name: rake
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - "~>"
37
+ - !ruby/object:Gem::Version
38
+ version: '13.0'
39
+ type: :development
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - "~>"
44
+ - !ruby/object:Gem::Version
45
+ version: '13.0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rake-compiler
48
+ requirement: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - "~>"
51
+ - !ruby/object:Gem::Version
52
+ version: '1.2'
53
+ type: :development
54
+ prerelease: false
55
+ version_requirements: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: '1.2'
60
+ - !ruby/object:Gem::Dependency
61
+ name: minitest
62
+ requirement: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - "~>"
65
+ - !ruby/object:Gem::Version
66
+ version: '5.0'
67
+ type: :development
68
+ prerelease: false
69
+ version_requirements: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - "~>"
72
+ - !ruby/object:Gem::Version
73
+ version: '5.0'
74
+ description: 'Native Ruby binding and Rack middleware for libinjection. Report-only
75
+ by default: detects SQLi/XSS-like payloads, emits structured attack signals, and
76
+ can be combined with Rack::Attack. Ships with a pinned vendoring workflow for libinjection
77
+ v4.0.0.'
78
+ email:
79
+ - romanhajdarov@gmail.com
80
+ executables: []
81
+ extensions:
82
+ - ext/libinjection/extconf.rb
83
+ extra_rdoc_files: []
84
+ files:
85
+ - ".github/workflows/ci.yml"
86
+ - CHANGELOG.md
87
+ - GET_STARTED.md
88
+ - LICENSE-libinjection.txt
89
+ - LICENSE.txt
90
+ - README.md
91
+ - SECURITY.md
92
+ - ext/libinjection/extconf.rb
93
+ - ext/libinjection/libinjection_ext.c
94
+ - ext/libinjection/vendor/libinjection/.vendored
95
+ - ext/libinjection/vendor/libinjection/COPYING
96
+ - ext/libinjection/vendor/libinjection/MIGRATION.md
97
+ - ext/libinjection/vendor/libinjection/README.md
98
+ - ext/libinjection/vendor/libinjection/src/libinjection.h
99
+ - ext/libinjection/vendor/libinjection/src/libinjection_error.h
100
+ - ext/libinjection/vendor/libinjection/src/libinjection_html5.c
101
+ - ext/libinjection/vendor/libinjection/src/libinjection_html5.h
102
+ - ext/libinjection/vendor/libinjection/src/libinjection_sqli.c
103
+ - ext/libinjection/vendor/libinjection/src/libinjection_sqli.h
104
+ - ext/libinjection/vendor/libinjection/src/libinjection_sqli_data.h
105
+ - ext/libinjection/vendor/libinjection/src/libinjection_xss.c
106
+ - ext/libinjection/vendor/libinjection/src/libinjection_xss.h
107
+ - lib/libinjection.rb
108
+ - lib/libinjection/version.rb
109
+ - lib/rack-libinjection.rb
110
+ - lib/rack/libinjection.rb
111
+ - samples/README.md
112
+ - samples/libinjection_detect_raw_hot_path.rb
113
+ - samples/rack_all_surfaces_hot_path.rb
114
+ - samples/rack_params_hot_path.rb
115
+ - samples/rack_query_hot_path.rb
116
+ - samples/results/.gitkeep
117
+ - script/fuzz_smoke.rb
118
+ - script/vendor_libs.rb
119
+ - test/test_helper.rb
120
+ - test/test_libinjection.rb
121
+ - test/test_middleware.rb
122
+ homepage: https://github.com/roman-haidarov/rack-libinjection
123
+ licenses:
124
+ - MIT
125
+ metadata:
126
+ homepage_uri: https://github.com/roman-haidarov/rack-libinjection
127
+ source_code_uri: https://github.com/roman-haidarov/rack-libinjection/tree/main
128
+ changelog_uri: https://github.com/roman-haidarov/rack-libinjection/blob/main/CHANGELOG.md
129
+ bug_tracker_uri: https://github.com/roman-haidarov/rack-libinjection/issues
130
+ security_policy_uri: https://github.com/roman-haidarov/rack-libinjection/blob/main/SECURITY.md
131
+ rdoc_options: []
132
+ require_paths:
133
+ - lib
134
+ required_ruby_version: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: 3.3.0
139
+ required_rubygems_version: !ruby/object:Gem::Requirement
140
+ requirements:
141
+ - - ">="
142
+ - !ruby/object:Gem::Version
143
+ version: '0'
144
+ requirements: []
145
+ rubygems_version: 3.6.7
146
+ specification_version: 4
147
+ summary: Tokenizer/fingerprint-based attack signal layer for Rack/Rails
148
+ test_files: []