Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/spec/integration/msfmcpd/logging_pipeline_spec.rb
70330 views
1
# frozen_string_literal: true
2
3
require 'msf/core/mcp'
4
require 'stringio'
5
require 'tempfile'
6
require 'json'
7
8
RSpec.describe 'Logging Pipeline Integration' do
9
let(:output) { StringIO.new }
10
let(:log_file) { Tempfile.new(['logging_integration', '.log']).tap(&:close).path }
11
let(:log_src) { Msf::MCP::LOG_SOURCE }
12
let(:lvl_info) { Msf::MCP::LOG_INFO }
13
let(:lvl_warn) { Msf::MCP::LOG_WARN }
14
let(:lvl_error) { Msf::MCP::LOG_ERROR }
15
16
after do
17
deregister_log_source(log_src) if log_source_registered?(log_src)
18
File.delete(log_file) if File.exist?(log_file)
19
end
20
21
describe 'initialize_logger with sanitize enabled' do
22
it 'produces JSON log entries with sensitive data redacted' do
23
app = Msf::MCP::Application.new([], output: output)
24
app.send(:parse_arguments)
25
app.instance_variable_set(:@config, {
26
logging: {
27
enabled: true,
28
level: 'INFO',
29
log_file: log_file,
30
sanitize: true
31
}
32
})
33
app.send(:initialize_logger)
34
35
ilog({ message: 'Connection established', context: { password: 's3cret', host: 'localhost' } }, log_src, lvl_info)
36
37
content = File.read(log_file)
38
expect(content).not_to be_empty
39
40
entry = JSON.parse(content.strip.split("\n").last)
41
42
expect(entry['timestamp']).not_to be_nil
43
expect(entry['severity']).to eq('INFO')
44
expect(entry['message']).to include('Connection established')
45
46
expect(entry['context']['password']).to eq('[REDACTED]')
47
expect(entry['context']['host']).to eq('localhost')
48
49
expect(content).not_to include('s3cret')
50
end
51
end
52
53
describe 'initialize_logger with sanitize disabled' do
54
it 'produces JSON log entries without redaction' do
55
app = Msf::MCP::Application.new([], output: output)
56
app.send(:parse_arguments)
57
app.instance_variable_set(:@config, {
58
logging: {
59
enabled: true,
60
level: 'INFO',
61
log_file: log_file,
62
sanitize: false
63
}
64
})
65
app.send(:initialize_logger)
66
67
ilog({ message: 'Connection established', context: { password: 's3cret', host: 'localhost' } }, log_src, lvl_info)
68
69
content = File.read(log_file)
70
entry = JSON.parse(content.strip.split("\n").last)
71
72
expect(entry['severity']).to eq('INFO')
73
expect(entry['message']).to include('Connection established')
74
75
expect(entry['context']['password']).to eq('s3cret')
76
expect(entry['context']['host']).to eq('localhost')
77
end
78
end
79
80
describe 'log level filtering' do
81
it 'filters messages below the configured threshold' do
82
app = Msf::MCP::Application.new([], output: output)
83
app.send(:parse_arguments)
84
app.instance_variable_set(:@config, {
85
logging: {
86
enabled: true,
87
level: 'WARN',
88
log_file: log_file,
89
sanitize: false
90
}
91
})
92
app.send(:initialize_logger)
93
94
# INFO (LEV_2) should be filtered at WARN (LEV_1) threshold
95
ilog({ message: 'This should be filtered' }, log_src, lvl_info)
96
# WARN (LEV_1) should pass
97
wlog({ message: 'This should appear' }, log_src, lvl_warn)
98
# ERROR (LEV_0) should pass
99
elog({ message: 'This error should appear' }, log_src, lvl_error)
100
101
content = File.read(log_file)
102
lines = content.strip.split("\n").reject(&:empty?)
103
104
expect(lines.length).to eq(2)
105
106
entries = lines.map { |l| JSON.parse(l) }
107
expect(entries.map { |e| e['severity'] }).to contain_exactly('WARN', 'ERROR')
108
expect(content).not_to include('This should be filtered')
109
end
110
end
111
112
describe 'exception logging through the pipeline' do
113
it 'formats exceptions as structured JSON with sanitized messages' do
114
app = Msf::MCP::Application.new([], output: output)
115
app.send(:parse_arguments)
116
app.instance_variable_set(:@config, {
117
logging: {
118
enabled: true,
119
level: 'ERROR',
120
log_file: log_file,
121
sanitize: true
122
}
123
})
124
app.send(:initialize_logger)
125
126
error = StandardError.new('Failed with password=hunter2')
127
error.set_backtrace(['lib/msf/core/mcp/server.rb:42:in `start`'])
128
129
elog({ message: 'Startup failed', exception: error }, log_src, lvl_error)
130
131
content = File.read(log_file)
132
entry = JSON.parse(content.strip.split("\n").last)
133
134
expect(entry['severity']).to eq('ERROR')
135
expect(entry['message']).to include('Startup failed')
136
expect(entry['exception']).to be_a(Hash)
137
expect(entry['exception']['class']).to eq('StandardError')
138
139
expect(entry['exception']['message']).to include('[REDACTED]')
140
expect(entry['exception']['message']).not_to include('hunter2')
141
end
142
end
143
end
144
145