immunio 0.15.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (157) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +234 -0
  3. data/README.md +147 -0
  4. data/bin/immunio +5 -0
  5. data/lib/immunio.rb +29 -0
  6. data/lib/immunio/agent.rb +260 -0
  7. data/lib/immunio/authentication.rb +96 -0
  8. data/lib/immunio/blocked_app.rb +38 -0
  9. data/lib/immunio/channel.rb +432 -0
  10. data/lib/immunio/cli.rb +39 -0
  11. data/lib/immunio/context.rb +114 -0
  12. data/lib/immunio/errors.rb +43 -0
  13. data/lib/immunio/immunio_ca.crt +45 -0
  14. data/lib/immunio/logger.rb +87 -0
  15. data/lib/immunio/plugins/action_dispatch.rb +45 -0
  16. data/lib/immunio/plugins/action_view.rb +431 -0
  17. data/lib/immunio/plugins/active_record.rb +707 -0
  18. data/lib/immunio/plugins/active_record_relation.rb +370 -0
  19. data/lib/immunio/plugins/authlogic.rb +80 -0
  20. data/lib/immunio/plugins/csrf.rb +24 -0
  21. data/lib/immunio/plugins/devise.rb +40 -0
  22. data/lib/immunio/plugins/environment_reporter.rb +69 -0
  23. data/lib/immunio/plugins/eval.rb +51 -0
  24. data/lib/immunio/plugins/exception_handler.rb +55 -0
  25. data/lib/immunio/plugins/gems_tracker.rb +5 -0
  26. data/lib/immunio/plugins/haml.rb +36 -0
  27. data/lib/immunio/plugins/http_finisher.rb +50 -0
  28. data/lib/immunio/plugins/http_tracker.rb +203 -0
  29. data/lib/immunio/plugins/io.rb +96 -0
  30. data/lib/immunio/plugins/redirect.rb +42 -0
  31. data/lib/immunio/plugins/warden.rb +66 -0
  32. data/lib/immunio/processor.rb +234 -0
  33. data/lib/immunio/rails.rb +26 -0
  34. data/lib/immunio/request.rb +139 -0
  35. data/lib/immunio/rufus_lua_ext/ref.rb +27 -0
  36. data/lib/immunio/rufus_lua_ext/state.rb +157 -0
  37. data/lib/immunio/rufus_lua_ext/table.rb +137 -0
  38. data/lib/immunio/rufus_lua_ext/utils.rb +13 -0
  39. data/lib/immunio/version.rb +5 -0
  40. data/lib/immunio/vm.rb +291 -0
  41. data/lua-hooks/ext/all.c +78 -0
  42. data/lua-hooks/ext/bitop/README +22 -0
  43. data/lua-hooks/ext/bitop/bit.c +189 -0
  44. data/lua-hooks/ext/extconf.rb +38 -0
  45. data/lua-hooks/ext/libinjection/COPYING +37 -0
  46. data/lua-hooks/ext/libinjection/libinjection.h +65 -0
  47. data/lua-hooks/ext/libinjection/libinjection_html5.c +847 -0
  48. data/lua-hooks/ext/libinjection/libinjection_html5.h +54 -0
  49. data/lua-hooks/ext/libinjection/libinjection_sqli.c +2301 -0
  50. data/lua-hooks/ext/libinjection/libinjection_sqli.h +295 -0
  51. data/lua-hooks/ext/libinjection/libinjection_sqli_data.h +9349 -0
  52. data/lua-hooks/ext/libinjection/libinjection_xss.c +531 -0
  53. data/lua-hooks/ext/libinjection/libinjection_xss.h +21 -0
  54. data/lua-hooks/ext/libinjection/lualib.c +109 -0
  55. data/lua-hooks/ext/lpeg/HISTORY +90 -0
  56. data/lua-hooks/ext/lpeg/lpcap.c +537 -0
  57. data/lua-hooks/ext/lpeg/lpcap.h +43 -0
  58. data/lua-hooks/ext/lpeg/lpcode.c +986 -0
  59. data/lua-hooks/ext/lpeg/lpcode.h +34 -0
  60. data/lua-hooks/ext/lpeg/lpeg-128.gif +0 -0
  61. data/lua-hooks/ext/lpeg/lpeg.html +1429 -0
  62. data/lua-hooks/ext/lpeg/lpprint.c +244 -0
  63. data/lua-hooks/ext/lpeg/lpprint.h +35 -0
  64. data/lua-hooks/ext/lpeg/lptree.c +1238 -0
  65. data/lua-hooks/ext/lpeg/lptree.h +77 -0
  66. data/lua-hooks/ext/lpeg/lptypes.h +149 -0
  67. data/lua-hooks/ext/lpeg/lpvm.c +355 -0
  68. data/lua-hooks/ext/lpeg/lpvm.h +58 -0
  69. data/lua-hooks/ext/lpeg/makefile +55 -0
  70. data/lua-hooks/ext/lpeg/re.html +498 -0
  71. data/lua-hooks/ext/lpeg/test.lua +1409 -0
  72. data/lua-hooks/ext/lua-cmsgpack/CMakeLists.txt +45 -0
  73. data/lua-hooks/ext/lua-cmsgpack/README.md +115 -0
  74. data/lua-hooks/ext/lua-cmsgpack/lua_cmsgpack.c +957 -0
  75. data/lua-hooks/ext/lua-cmsgpack/test.lua +570 -0
  76. data/lua-hooks/ext/lua-snapshot/LICENSE +7 -0
  77. data/lua-hooks/ext/lua-snapshot/Makefile +12 -0
  78. data/lua-hooks/ext/lua-snapshot/README.md +18 -0
  79. data/lua-hooks/ext/lua-snapshot/dump.lua +15 -0
  80. data/lua-hooks/ext/lua-snapshot/snapshot.c +455 -0
  81. data/lua-hooks/ext/lua/COPYRIGHT +34 -0
  82. data/lua-hooks/ext/lua/lapi.c +1087 -0
  83. data/lua-hooks/ext/lua/lapi.h +16 -0
  84. data/lua-hooks/ext/lua/lauxlib.c +652 -0
  85. data/lua-hooks/ext/lua/lauxlib.h +174 -0
  86. data/lua-hooks/ext/lua/lbaselib.c +659 -0
  87. data/lua-hooks/ext/lua/lcode.c +831 -0
  88. data/lua-hooks/ext/lua/lcode.h +76 -0
  89. data/lua-hooks/ext/lua/ldblib.c +398 -0
  90. data/lua-hooks/ext/lua/ldebug.c +638 -0
  91. data/lua-hooks/ext/lua/ldebug.h +33 -0
  92. data/lua-hooks/ext/lua/ldo.c +519 -0
  93. data/lua-hooks/ext/lua/ldo.h +57 -0
  94. data/lua-hooks/ext/lua/ldump.c +164 -0
  95. data/lua-hooks/ext/lua/lfunc.c +174 -0
  96. data/lua-hooks/ext/lua/lfunc.h +34 -0
  97. data/lua-hooks/ext/lua/lgc.c +710 -0
  98. data/lua-hooks/ext/lua/lgc.h +110 -0
  99. data/lua-hooks/ext/lua/linit.c +38 -0
  100. data/lua-hooks/ext/lua/liolib.c +556 -0
  101. data/lua-hooks/ext/lua/llex.c +463 -0
  102. data/lua-hooks/ext/lua/llex.h +81 -0
  103. data/lua-hooks/ext/lua/llimits.h +128 -0
  104. data/lua-hooks/ext/lua/lmathlib.c +263 -0
  105. data/lua-hooks/ext/lua/lmem.c +86 -0
  106. data/lua-hooks/ext/lua/lmem.h +49 -0
  107. data/lua-hooks/ext/lua/loadlib.c +705 -0
  108. data/lua-hooks/ext/lua/loadlib_rel.c +760 -0
  109. data/lua-hooks/ext/lua/lobject.c +214 -0
  110. data/lua-hooks/ext/lua/lobject.h +381 -0
  111. data/lua-hooks/ext/lua/lopcodes.c +102 -0
  112. data/lua-hooks/ext/lua/lopcodes.h +268 -0
  113. data/lua-hooks/ext/lua/loslib.c +243 -0
  114. data/lua-hooks/ext/lua/lparser.c +1339 -0
  115. data/lua-hooks/ext/lua/lparser.h +82 -0
  116. data/lua-hooks/ext/lua/lstate.c +214 -0
  117. data/lua-hooks/ext/lua/lstate.h +169 -0
  118. data/lua-hooks/ext/lua/lstring.c +111 -0
  119. data/lua-hooks/ext/lua/lstring.h +31 -0
  120. data/lua-hooks/ext/lua/lstrlib.c +871 -0
  121. data/lua-hooks/ext/lua/ltable.c +588 -0
  122. data/lua-hooks/ext/lua/ltable.h +40 -0
  123. data/lua-hooks/ext/lua/ltablib.c +287 -0
  124. data/lua-hooks/ext/lua/ltm.c +75 -0
  125. data/lua-hooks/ext/lua/ltm.h +54 -0
  126. data/lua-hooks/ext/lua/lua.c +392 -0
  127. data/lua-hooks/ext/lua/lua.def +131 -0
  128. data/lua-hooks/ext/lua/lua.h +388 -0
  129. data/lua-hooks/ext/lua/lua.rc +28 -0
  130. data/lua-hooks/ext/lua/lua_dll.rc +26 -0
  131. data/lua-hooks/ext/lua/luac.c +200 -0
  132. data/lua-hooks/ext/lua/luac.rc +1 -0
  133. data/lua-hooks/ext/lua/luaconf.h +763 -0
  134. data/lua-hooks/ext/lua/luaconf.h.in +724 -0
  135. data/lua-hooks/ext/lua/luaconf.h.orig +763 -0
  136. data/lua-hooks/ext/lua/lualib.h +53 -0
  137. data/lua-hooks/ext/lua/lundump.c +227 -0
  138. data/lua-hooks/ext/lua/lundump.h +36 -0
  139. data/lua-hooks/ext/lua/lvm.c +767 -0
  140. data/lua-hooks/ext/lua/lvm.h +36 -0
  141. data/lua-hooks/ext/lua/lzio.c +82 -0
  142. data/lua-hooks/ext/lua/lzio.h +67 -0
  143. data/lua-hooks/ext/lua/print.c +227 -0
  144. data/lua-hooks/ext/luautf8/README.md +152 -0
  145. data/lua-hooks/ext/luautf8/lutf8lib.c +1274 -0
  146. data/lua-hooks/ext/luautf8/unidata.h +3064 -0
  147. data/lua-hooks/lib/boot.lua +254 -0
  148. data/lua-hooks/lib/encode.lua +4 -0
  149. data/lua-hooks/lib/lexers/LICENSE +21 -0
  150. data/lua-hooks/lib/lexers/bash.lua +134 -0
  151. data/lua-hooks/lib/lexers/bash_dqstr.lua +62 -0
  152. data/lua-hooks/lib/lexers/css.lua +216 -0
  153. data/lua-hooks/lib/lexers/html.lua +106 -0
  154. data/lua-hooks/lib/lexers/javascript.lua +68 -0
  155. data/lua-hooks/lib/lexers/lexer.lua +1575 -0
  156. data/lua-hooks/lib/lexers/markers.lua +33 -0
  157. metadata +308 -0
@@ -0,0 +1,40 @@
1
+ # Register callbacks using Devise internals.
2
+
3
+ begin
4
+ require "devise"
5
+ rescue LoadError # rubocop:disable Lint/HandleExceptions
6
+ # Ignore
7
+ end
8
+
9
+ if defined? Devise
10
+ module Immunio
11
+ # Hook into password recovery feature to trigger the `framework_password_reset` hook.
12
+ module DeviseRecoverableHooks
13
+ extend ActiveSupport::Concern
14
+
15
+ included do
16
+ alias_method_chain :send_reset_password_instructions, :immunio
17
+ end
18
+
19
+ def send_reset_password_instructions_with_immunio(attributes={})
20
+ Request.time "plugin", "#{Module.nesting[0]}::#{__method__}" do
21
+ Immunio.logger.debug "Devise instrumentation fired for send_reset_password_instructions"
22
+
23
+ recoverable = find_or_initialize_with_errors(reset_password_keys, attributes, :not_found)
24
+
25
+ if recoverable.persisted? # Found
26
+ Immunio.password_reset user_record: recoverable, plugin: 'devise'
27
+ else
28
+ Immunio.failed_password_reset email: recoverable.email, plugin: 'devise'
29
+ end
30
+
31
+ Request.pause "plugin", "#{Module.nesting[0]}::#{__method__}" do
32
+ send_reset_password_instructions_without_immunio(attributes)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ Devise::Models::Recoverable::ClassMethods.send :include, Immunio::DeviseRecoverableHooks
40
+ end
@@ -0,0 +1,69 @@
1
+ # Report environment data on the first request.
2
+ #
3
+ # XXX: Completely untested outside of MRI (official Ruby runtime) 1.9.3 and up!
4
+
5
+ module Immunio
6
+ class EnvironmentReporter
7
+ def initialize(app)
8
+ @app = app
9
+
10
+ # No need for mutex, reporting environment is idempotent
11
+ @reported = false
12
+ end
13
+
14
+ def call(env)
15
+ response = @app.call(env)
16
+
17
+ # Report gem usage at the end so that everything as been loaded.
18
+ Request.time "plugin", "#{Module.nesting[0]}::#{__method__}" do
19
+ report if !@reported
20
+ end
21
+
22
+ response
23
+ end
24
+
25
+ def runtime_name
26
+ if Object.const_defined?(:RUBY_DESCRIPTION) && RUBY_DESCRIPTION =~ /Enterprise/
27
+ 'ree'
28
+ elsif Object.const_defined? :RUBY_ENGINE
29
+ RUBY_ENGINE.to_s
30
+ else
31
+ 'unknown'
32
+ end
33
+ end
34
+
35
+ def runtime_version
36
+ case runtime_name
37
+ when 'ruby' then RUBY_VERSION
38
+ when 'jruby' then JRUBY_VERSION
39
+ when 'rbx' then Rubinius::VERSION
40
+ else 'unknown'
41
+ end
42
+ end
43
+
44
+ def report
45
+ @reported = true
46
+
47
+ info = {
48
+ runtime: {
49
+ name: runtime_name,
50
+ version: runtime_version
51
+ },
52
+ language: {
53
+ name: 'ruby',
54
+ version: RUBY_VERSION
55
+ },
56
+ platform: {
57
+ description: RUBY_PLATFORM
58
+ },
59
+ dependencies: {}
60
+ }
61
+
62
+ Gem.loaded_specs.each_pair do |name, spec|
63
+ info[:dependencies][name] = spec.version.to_s
64
+ end
65
+
66
+ Immunio.agent.environment = info
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,51 @@
1
+ require_relative '../context'
2
+ # We use DebugInspector to execute eval() in the callers frame.
3
+
4
+ require 'binding_of_caller'
5
+ module Immunio
6
+ module KernelEvalHook
7
+ extend ActiveSupport::Concern
8
+
9
+ def self.extended(base)
10
+ base.singleton_class.alias_method_chain :eval, :immunio
11
+ end
12
+
13
+ def self.included(base)
14
+ base.alias_method_chain :eval, :immunio
15
+ end
16
+
17
+ protected
18
+ def eval_with_immunio(*args)
19
+ Immunio.logger.debug {"Eval_with_immunio with args: #{args}"}
20
+ Request.time "plugin", "#{Module.nesting[0]}::#{__method__}" do
21
+ strict_context, loose_context, stack, loose_stack = Immunio::Context.context()
22
+ Immunio.run_hook! "eval", "eval",
23
+ parameters: args,
24
+ context_key: loose_context,
25
+ stack: loose_stack
26
+ Request.pause "plugin", "#{Module.nesting[0]}::#{__method__}" do
27
+ if args.length == 1 then
28
+ # eval call did not include a binding, so we eval it in the caller's binding
29
+ # NOTE: the 7 here is the number of frames above us the caller was. Changing
30
+ # the call stack will break this...
31
+ rval = eval_without_immunio(args[0], binding.of_caller(7))
32
+ # XXX We can and should log here, but it breaks a ruby test (atom_feed_helper)
33
+ #Immunio.logger.debug {"Eval_without_immunio (binding 7) returned: #{rval.inspect}\nEval stack was: #{stack}"}
34
+ else
35
+ # eval call included a binding, so we still eval in the binding
36
+ rval = eval_without_immunio(*args)
37
+ # XXX We can and should log here, but it breaks a ruby test (atom_feed_helper)
38
+ #Immunio.logger.debug {"Eval_without_immunio returned: #{rval.inspect}"}
39
+ end
40
+ rval
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ if Immunio::agent.plugin_enabled?("eval") then
48
+ Kernel.send :include, Immunio::KernelEvalHook
49
+ Kernel.extend Immunio::KernelEvalHook
50
+ Immunio.logger.debug "Eval: All hooks installed."
51
+ end
@@ -0,0 +1,55 @@
1
+ require_relative "../blocked_app"
2
+ require_relative "../errors"
3
+
4
+ module Immunio
5
+ # Rack middleware trigger proper hook whenever an exception is raised in the app.
6
+ class ExceptionHandler
7
+ def initialize(app)
8
+ @app = app
9
+ end
10
+
11
+ def call(env)
12
+ @app.call(env)
13
+ rescue RequestBlocked
14
+ Request.time "plugin", "#{Module.nesting[0]}::#{__method__}[RequestBlocked]" do
15
+ # Avoid bubbling exception to logging middlewares.
16
+ Immunio.blocked_app.call(env)
17
+ end
18
+ rescue OverrideResponse => override
19
+ status, headers, body = Immunio.override_response.call(env, override)
20
+
21
+ Immunio.run_hook "http_tracker", "http_response_start",
22
+ status: status, headers: headers
23
+ [status, headers, body]
24
+ rescue StandardError => e
25
+ Request.time "plugin", "#{Module.nesting[0]}::#{__method__}[#{e.class}]" do
26
+ # Check for template, DB errors and such generated by the agent.
27
+ original_exception = unwrap_exception(e)
28
+ if RequestBlocked === original_exception || original_exception.message =~ /^Immunio::RequestBlocked:/
29
+ return Immunio.blocked_app.call(env)
30
+ end
31
+ if OverrideResponse === original_exception # We can't do anything with just the message:
32
+ status, headers, body = Immunio.override_response.call(env, original_exception)
33
+
34
+ Immunio.run_hook "http_tracker", "http_response_start",
35
+ status: status, headers: headers
36
+ return [status, headers, body]
37
+ end
38
+
39
+ # A real app exception, not generated by the agent.
40
+ Immunio.run_hook! "exception_handler", "exception", exception: "#{e.class.name}: #{e}"
41
+
42
+ # Re-raise
43
+ raise
44
+ end
45
+ end
46
+
47
+ # Unwrap the innermost original exception.
48
+ def unwrap_exception(e)
49
+ while e.respond_to? :original_exception
50
+ e = e.original_exception
51
+ end
52
+ e
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,5 @@
1
+ require_relative 'environment_reporter'
2
+
3
+ module Immunio
4
+ GemsTracker = EnvironmentReporter
5
+ end
@@ -0,0 +1,36 @@
1
+ # Haml compiles templates to something like:
2
+ #
3
+ #begin;extend Haml::Helpers;_hamlout = @haml_buffer = Haml::Buffer.new(haml_buffer, {:autoclose=>["area", "base", "basefont", "br", "col", "command", "embed", "frame", "hr", "img", "input", "isindex", "keygen", "link", "menuitem", "meta", "param", "source", "track", "wbr"], :preserve=>["textarea", "pre", "code"], :attr_wrapper=>"'", :ugly=>true, :format=>:html5, :encoding=>"UTF-8", :escape_html=>true, :escape_attrs=>true, :hyphenate_data_attrs=>true, :cdata=>false});_erbout = _hamlout.buffer;@output_buffer = output_buffer ||= ActionView::OutputBuffer.new rescue nil;;_hamlout.buffer << "<script>console.log('test')</script>\n";::Haml::Util.html_safe(_erbout);ensure;@haml_buffer = @haml_buffer.upper if @haml_buffer;end;
4
+ #
5
+ # In there is a call to ::Haml::Util.html_safe, which calls the real html_safe.
6
+ # But lots of haml code paths call ::Haml::Util.html_safe, so we can't mark it
7
+ # as safe. Instead, we need to modify the compiled template code to call the
8
+ # method in a marked-safe context.
9
+ #
10
+ # But haml doesn't make it easy, as it alias-method-chains its own methods. This
11
+ # module continues the chain to subvert the
12
+ # precompiled_method_return_value_with_haml_xss method and instead call our own
13
+ # method, which calls html_safe while in a safe context.
14
+
15
+ module Immunio
16
+ module Haml
17
+ module Compiler
18
+ extend ActiveSupport::Concern
19
+
20
+ included do
21
+ alias_method :precompiled_method_return_value_without_haml_xss_without_immunio, :precompiled_method_return_value_without_haml_xss
22
+ alias_method :precompiled_method_return_value, :precompiled_method_return_value_with_haml_xss_with_immunio
23
+ end
24
+
25
+ def self.immunio_html_safe(string)
26
+ Immunio::UnsafeBufferDetection.in_safe_context { ::Haml::Util.html_safe(string) }
27
+ end
28
+
29
+ def precompiled_method_return_value_with_haml_xss_with_immunio
30
+ "Immunio::Haml::Compiler.immunio_html_safe(#{precompiled_method_return_value_without_haml_xss_without_immunio})"
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ ::Haml::Compiler.send :include, Immunio::Haml::Compiler if defined? ::Haml::Compiler
@@ -0,0 +1,50 @@
1
+ require "delegate"
2
+
3
+ module Immunio
4
+ # Rack middleware running at the very end of the stack to finish HTTP requests.
5
+ class HTTPFinisher
6
+ def initialize(app)
7
+ @app = app
8
+ end
9
+
10
+ def call(env)
11
+ status, headers, body = @app.call(env)
12
+ if Request.current
13
+ Immunio.logger.debug "Finishing request in HTTPFinisher"
14
+ [status, headers, BodyWrapper.new(body)]
15
+ else
16
+ [status, headers, body]
17
+ end
18
+ end
19
+ end
20
+
21
+ class BodyWrapper < SimpleDelegator
22
+ def initialize(body)
23
+ super body
24
+ @body = body
25
+ end
26
+
27
+ def each
28
+ begin
29
+ @body.each do |chunk|
30
+ Immunio.run_hook "http_finisher", "http_response_body_chunk",
31
+ chunk: chunk
32
+ yield chunk
33
+ end
34
+ rescue RequestBlocked
35
+ # We need to catch this exception here as access to static files is
36
+ # deferred. If one of our hooks, such as file_io throws when the Rake
37
+ # stack has unwound this far it will kill the request with a 500
38
+ yield "Request Blocked"
39
+ end
40
+ Immunio.run_hook "http_finisher", "http_request_finish"
41
+
42
+ # Reset current request
43
+ Immunio.finish_request
44
+ end
45
+
46
+ def respond_to?(*args)
47
+ @body.respond_to?(*args)
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,203 @@
1
+ require_relative "../blocked_app"
2
+ require_relative "../errors"
3
+
4
+ module Immunio
5
+ # Rack middleware tracking HTTP requests and responses and triggers the proper hooks.
6
+ class HTTPTracker
7
+ def initialize(app)
8
+ @app = app
9
+ end
10
+
11
+ def call(env)
12
+ request = Request.new(env)
13
+ request.time "plugin", "#{Module.nesting[0]}::#{__method__}" do
14
+ Immunio.logger.debug "Creating new request in HTTPTracker"
15
+ Immunio.new_request(request)
16
+
17
+ Immunio.run_hook! "http_tracker", "http_request_start", meta_from_env(env)
18
+
19
+ env['rack.input'] = InputWrapper.new(env['rack.input'])
20
+
21
+ status, headers, body = Request.pause "plugin", "#{Module.nesting[0]}::#{__method__}" do
22
+ @app.call(env)
23
+ end
24
+
25
+ # Run hook for the session only if it was loaded
26
+ session = env["rack.session"]
27
+ if session_was_loaded?(session)
28
+ Immunio.run_hook! "http_tracker", "framework_session",
29
+ session_id: extract_session_id(session)
30
+ end
31
+
32
+ # Immunio expects response headers as a list of tuples.
33
+ list_headers = headers_to_list(headers)
34
+
35
+ result = Immunio.run_hook! "http_tracker", "http_response_start",
36
+ status: status, headers: list_headers
37
+
38
+ # If new headers are specified, convert them back to the Ruby hash format.
39
+ if result["headers"] != nil
40
+ # The new_headers completely replace the originals.
41
+ headers = list_to_headers(result["headers"].to_a)
42
+ end
43
+
44
+ # Send the response
45
+ [status, headers, body]
46
+ end
47
+
48
+ rescue RequestBlocked
49
+ Request.time "plugin", "#{Module.nesting[0]}::#{__method__}[RequestBlocked]" do
50
+ status, headers, body = Immunio.blocked_app.call(env)
51
+ # Do not allow blocking the request here
52
+ Immunio.run_hook "http_tracker", "http_response_start",
53
+ status: status, headers: headers
54
+
55
+ [status, headers, body]
56
+ end
57
+ rescue OverrideResponse => override
58
+ status, headers, body = Immunio.override_response.call(env, override)
59
+
60
+ Immunio.run_hook "http_tracker", "http_response_start",
61
+ status: status, headers: headers
62
+ [status, headers, body]
63
+ end
64
+
65
+ private
66
+ def headers_to_list(headers)
67
+ list_headers = []
68
+ headers.each do |name, value|
69
+ # Ruby treats the `Set-Cookie` header specially. If there are multiple
70
+ # Set-Cookie headers to send, they are joined into a single field,
71
+ # separated by line-feeds.
72
+ if name == "Set-Cookie"
73
+ value.split("\n").each do |cookie_val|
74
+ list_headers.push(["Set-Cookie", cookie_val])
75
+ end
76
+ else
77
+ list_headers.push([name, value])
78
+ end
79
+ end
80
+ list_headers
81
+ end
82
+
83
+ def list_to_headers(list)
84
+ new_headers = {}
85
+ list.each do |name, value|
86
+ # If this header is already in `new_headers`, append to the
87
+ # existing value with a linefeed separator.
88
+ if new_headers.has_key?(name)
89
+ new_headers[name] += ("\n" + value)
90
+ else
91
+ new_headers[name] = value
92
+ end
93
+ end
94
+ new_headers
95
+ end
96
+
97
+ def meta_from_env(env)
98
+ request = Rack::Request.new(env)
99
+
100
+ # Extract request headers from `env`.
101
+ headers = env.select { |k| k.starts_with? "HTTP_" }.
102
+ each_with_object({}) { |(k, v), h| h.store k[5..-1].downcase.tr('_', '-'), v }
103
+
104
+ # Determine scheme (http://www.rubydoc.info/github/rack/rack/master/file/SPEC)
105
+ # There are also some HTTP headers from proxies that may affect the
106
+ # scheme seen by the end user. We process those in the hooks.
107
+ scheme = env["rack.url_scheme"]
108
+ if env["HTTPS"] == "on"
109
+ # Some servers will set the HTTPS var explicity. If set, use it
110
+ scheme = "https"
111
+ end
112
+
113
+ # Determine the route name in controller#action format:
114
+ route_name = nil
115
+
116
+ if defined?(Rails.application)
117
+ begin
118
+ path = request.env['PATH_INFO']
119
+ method = request.env['REQUEST_METHOD'].downcase.to_sym
120
+ url = Rails.application.routes.recognize_path(path, method: method)
121
+ route_name = "#{url[:controller]}##{url[:action]}"
122
+ rescue ActionController::RoutingError
123
+ route_name = nil
124
+ end
125
+ end
126
+
127
+ {
128
+ protocol: env["SERVER_PROTOCOL"],
129
+ scheme: scheme,
130
+ uri: env["REQUEST_URI"],
131
+ server_name: env["SERVER_NAME"],
132
+ # SERVER_ADDR is non-standard, but rack uses it as a fallback, so
133
+ # include it here as well so we can access it from Lua.
134
+ server_addr: env["SERVER_ADDR"],
135
+ server_port: env["SERVER_PORT"],
136
+ route_name: route_name,
137
+ querystring: request.query_string,
138
+ method: request.request_method,
139
+ path: request.path_info,
140
+ socket_ip: request.ip,
141
+ socket_port: request.port,
142
+ headers: headers
143
+ }
144
+ end
145
+
146
+ def session_was_loaded?(session)
147
+ session && (session.respond_to?(:loaded?) ? session.loaded? : true)
148
+ end
149
+
150
+ def extract_session_id(session)
151
+ case
152
+ when session.respond_to?(:id)
153
+ session.id
154
+ when session[:id]
155
+ session[:id]
156
+ when session[:session_id]
157
+ session[:session_id]
158
+ else
159
+ nil
160
+ end
161
+ end
162
+ end
163
+
164
+ class InputWrapper < SimpleDelegator
165
+ def initialize(input)
166
+ super input
167
+ @input = input
168
+ end
169
+
170
+ def gets(*)
171
+ v = super
172
+ Request.time "plugin", "#{Module.nesting[0]}::#{__method__}" do
173
+ report_chunk v
174
+ end
175
+ v
176
+ end
177
+
178
+ def read(*)
179
+ v = super
180
+ Request.time "plugin", "#{Module.nesting[0]}::#{__method__}" do
181
+ report_chunk v
182
+ end
183
+ v
184
+ end
185
+
186
+ def each
187
+ Request.time "plugin", "#{Module.nesting[0]}::#{__method__}" do
188
+ @input.each do |chunk|
189
+ report_chunk chunk
190
+ Request.pause "plugin", "#{Module.nesting[0]}::#{__method__}" do
191
+ yield chunk
192
+ end
193
+ end
194
+ end
195
+ end
196
+
197
+ private
198
+ def report_chunk(chunk)
199
+ Immunio.run_hook! "http_tracker", "http_request_body_chunk",
200
+ chunk: chunk
201
+ end
202
+ end
203
+ end