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
|
# File 'lib/query_guard/subscriber.rb', line 9
def self.install!(config)
return if @installed
@subscriber = ActiveSupport::Notifications.subscribe(SQL_EVENT) do |_, started, finished, _, payload|
context = Thread.current[:query_guard_context]
next unless context
stats = Thread.current[:query_guard_stats]
next unless stats
name = payload[:name].to_s
next if name == "SCHEMA"
sql = payload[:sql].to_s
next if config.ignored_sql.any? { |r| r === sql }
duration_ms = (finished - started) * 1000.0
context.add_query(
sql: sql,
duration_ms: duration_ms,
name: name,
started_at: Time.at(started),
finished_at: Time.at(finished)
)
stats = Thread.current[:query_guard_stats] ||= { count: 0, total_duration_ms: 0.0, violations: [] }
stats[:count] += 1
stats[:total_duration_ms] += duration_ms
fp = QueryGuard::Security.fingerprint(sql)
stats[:fingerprints][fp] += 1
if config.max_duration_ms_per_query && duration_ms > config.max_duration_ms_per_query
stats[:violations] << { type: :slow_query, duration_ms: duration_ms.round(2), sql: sql }
end
if config.block_select_star && sql =~ /\bSELECT\s+\*/i
stats[:violations] << { type: :select_star, sql: sql }
end
if config.enable_security && config.detect_sql_injection
if QueryGuard::Security.suspicious_sql_injection?(sql, config.sql_injection_patterns)
stats[:violations] << { type: :sql_injection_suspected, sql: sql }
end
end
if config.enable_security && config.detect_data_exfiltration
if QueryGuard::Security.possible_exfiltration_query?(sql)
stats[:violations] << { type: :possible_data_exfiltration_query, sql: sql }
end
end
max = config.max_query_events_per_req || 200
if stats[:queries].length < max
stats[:queries] << {
sql: sql,
duration_ms: duration_ms.round(2),
occurred_at: Time.now.utc.iso8601
}
end
end
@installed = true
end
|