Module: Immunio::Context

Defined in:
lib/immunio/context.rb

Constant Summary collapse

RAILS_TEMPLATE_FILTER =
Regexp.new("(.*(_erb|_haml))__+\\d+_\\d+(.*)")
@@hash_cache =

Cache for contexts (named in tribute to our buddy Adam Back who invented proof of work)

{}

Class Method Summary collapse

Class Method Details

.context(additional_data = nil) ⇒ Object

Calculate context hashes and a stack trace. Additional data, in the form of a String, may be provided to mix into the strict context hash.



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/immunio/context.rb', line 9

def self.context(additional_data=nil)
  # We can filter out at least the top two frames
  cache_key = Digest::SHA1.hexdigest(caller(2).join())
  if @@hash_cache.has_key?(cache_key) then
    loose_context = @@hash_cache[cache_key]["loose_context"]
    strict_context = @@hash_cache[cache_key]["strict_context"]
    stack = @@hash_cache[cache_key]["stack"]
    loose_stack = @@hash_cache[cache_key]["stack"]

    if Immunio.agent.config.log_context_data
      Immunio.logger.info {"Stack contexts from cache"}
    end
  else
    # Use ropes as they're faster than string concatenation
    loose_stack_rope = []
    loose_context_rope = []
    stack_rope = []
    strict_context_rope = []

    # drop the top frame as it's us, but retain the rest. Immunio frames
    # are filtered by the Gem regex.
    locations = caller(1).map do |frame|
      frame = frame.split(":", 3)
      {path: frame[0], line: frame[1], label: frame[2]}
    end

    locations.each do |frame|

      # Filter frame names from template rendering to remove generated random bits
      matchdata = RAILS_TEMPLATE_FILTER.match(frame[:label])
      if matchdata != nil then
        frame[:label] = matchdata[1] + matchdata[3]
      end

      # Reduce paths to be relative to root if possible, to allow
      # relocation. If there's no rails root, or the path doesn't start with
      # the rails root, just use the filename part.
      if defined?(Rails) && defined?(Rails.root) &&
         Rails.root && frame[:path].start_with?(Rails.root.to_s)
        strict_path = frame[:path].sub(Rails.root.to_s, '')
      else
        strict_path = File.basename(frame[:path])
      end

      stack_rope << "\n" unless stack_rope.empty?
      stack_rope << frame[:path]
      stack_rope << ":"
      stack_rope << frame[:line]
      stack_rope << ":"
      stack_rope << frame[:label]

      strict_context_rope << "\n" unless strict_context_rope.empty?
      strict_context_rope << strict_path
      strict_context_rope << ":"
      strict_context_rope << frame[:line]
      strict_context_rope << ":"
      strict_context_rope << frame[:label]

      # Remove pathname from the loose context. The goal here is to prevent
      # upgrading gem versions from changing the loose context key, so for instance
      # users don't have to rebuild their whitelists every time they update a gem
      loose_context_rope << "\n" unless loose_context_rope.empty?
      loose_context_rope << File.basename(frame[:path])
      loose_context_rope << ":"
      loose_context_rope << frame[:label]

      # build a second seperate rope for the stack that determines ou loose context key
      # This includes filenames for usability -- just method names not being very good
      # for display purposes...
      loose_stack_rope << "\n" unless loose_stack_rope.empty?
      loose_stack_rope << frame[:path]
      loose_stack_rope << ":"
      loose_stack_rope << frame[:label]

    end
    stack = stack_rope.join()
    strict_stack = strict_context_rope.join()
    loose_stack = loose_stack_rope.join()

    if Immunio.agent.config.log_context_data
      Immunio.logger.info {"Strict context stack:\n#{strict_stack}"}
      Immunio.logger.info {"Loose context stack:\n#{loose_stack}"}
    end

    strict_context = Digest::SHA1.hexdigest(strict_stack)
    loose_context = Digest::SHA1.hexdigest(loose_context_rope.join())
    @@hash_cache[cache_key] = {
        "strict_context" => strict_context,
        "loose_context" => loose_context,
        "stack" => stack,
        "loose_stack" => loose_stack
      }
  end

  # Mix in additional context data
  unless additional_data.nil?
    if Immunio.agent.config.log_context_data
      Immunio.logger.info {"Additional context data:\n#{additional_data}"}
    end
    strict_context = Digest::SHA1.hexdigest(strict_context + additional_data)
  end

  return strict_context, loose_context, stack, loose_stack
end