Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/spec/integration/msfmcpd/error_handling_spec.rb
70330 views
1
# frozen_string_literal: true
2
3
require 'msf/core/mcp'
4
require 'webmock/rspec'
5
6
RSpec.describe 'Error Handling Integration' do
7
# Disable real HTTP connections for integration tests
8
before(:all) do
9
WebMock.disable_net_connect!(allow_localhost: false)
10
end
11
12
after(:all) do
13
WebMock.allow_net_connect!
14
end
15
16
let(:host) { 'localhost' }
17
let(:port) { 55553 }
18
19
describe 'MessagePack Error Handling' do
20
let(:endpoint) { '/api/' }
21
let(:api_url) { "https://#{host}:#{port}#{endpoint}" }
22
let(:client) do
23
Msf::MCP::Metasploit::Client.new(
24
api_type: 'messagepack',
25
host: host,
26
port: port,
27
endpoint: endpoint,
28
ssl: true
29
)
30
end
31
32
before do
33
# Mock successful authentication to get the token
34
stub_request(:post, api_url)
35
.with(body: ['auth.login', 'user', 'pass'].to_msgpack)
36
.to_return(
37
status: 200,
38
body: { 'result' => 'success', 'token' => 'test_token' }.to_msgpack,
39
headers: { 'Content-Type' => 'binary/message-pack' }
40
)
41
42
client.authenticate('user', 'pass')
43
end
44
45
describe 'Network Connection Failures' do
46
it 'converts Errno::ECONNREFUSED to ConnectionError' do
47
# Mock a connection refused error
48
stub_request(:post, api_url)
49
.with(body: ['module.search', 'test_token', 'smb'].to_msgpack)
50
.to_raise(Errno::ECONNREFUSED)
51
52
expect { client.search_modules('smb') }.to raise_error(Msf::MCP::Metasploit::ConnectionError, /Cannot connect to Metasploit RPC/)
53
end
54
55
it 'converts SocketError to ConnectionError' do
56
stub_request(:post, api_url)
57
.to_raise(SocketError.new('getaddrinfo: Name or service not known'))
58
59
expect { client.search_modules('smb') }.to raise_error(Msf::MCP::Metasploit::ConnectionError, /Network error/)
60
end
61
62
it 'converts Timeout::Error to ConnectionError' do
63
# Timeout on search
64
stub_request(:post, api_url)
65
.with(body: ['module.search', 'test_token', 'smb'].to_msgpack)
66
.to_raise(Timeout::Error.new('execution expired'))
67
68
expect { client.search_modules('smb') }.to raise_error(Msf::MCP::Metasploit::ConnectionError, /Request timeout/)
69
end
70
71
it 'converts EOFError to ConnectionError' do
72
stub_request(:post, api_url)
73
.to_raise(EOFError.new('end of file reached'))
74
75
expect { client.search_modules('smb') }.to raise_error(Msf::MCP::Metasploit::ConnectionError, /Empty response/)
76
end
77
end
78
79
describe 'HTTP Status Code Handling' do
80
it 'converts HTTP 401 to AuthenticationError' do
81
# Reauthenticate with invalid creds
82
stub_request(:post, api_url)
83
.to_return(
84
status: 401,
85
body: { 'error_message' => 'Invalid credentials' }.to_msgpack,
86
headers: { 'Content-Type' => 'binary/message-pack' }
87
)
88
89
expect { client.authenticate('invalid', 'invalid') }.to raise_error(Msf::MCP::Metasploit::AuthenticationError, /Invalid credentials/)
90
end
91
92
it 'converts HTTP 500 to APIError' do
93
# Server error on API call
94
stub_request(:post, api_url)
95
.with(body: ['module.search', 'test_token', 'smb'].to_msgpack)
96
.to_return(
97
status: 500,
98
body: { 'error_message' => 'Internal server error' }.to_msgpack
99
)
100
101
expect { client.search_modules('smb') }.to raise_error(Msf::MCP::Metasploit::APIError, /Internal server error/)
102
end
103
104
it 'converts unexpected HTTP status to ConnectionError' do
105
stub_request(:post, api_url)
106
.to_return(status: 503, body: 'Service Unavailable')
107
108
expect { client.search_modules('smb') }.to raise_error(Msf::MCP::Metasploit::ConnectionError, /HTTP 503/)
109
end
110
end
111
112
describe 'Tool Error Handling Integration' do
113
let(:rate_limiter) { Msf::MCP::Security::RateLimiter.new(requests_per_minute: 60) }
114
let(:server_context) do
115
{
116
msf_client: client,
117
rate_limiter: rate_limiter,
118
config: {}
119
}
120
end
121
122
it 'converts Metasploit errors to MCP error responses end-to-end' do
123
# Test that errors propagate correctly through the entire stack:
124
# HTTP error → Metasploit exception → MCP error response (isError: true)
125
stub_request(:post, api_url)
126
.with(body: ['module.search', 'test_token', 'smb'].to_msgpack)
127
.to_return(
128
status: 401,
129
body: { 'error_message' => 'Token invalid' }.to_msgpack
130
)
131
132
result = Msf::MCP::Tools::SearchModules.call(query: 'smb', server_context: server_context)
133
expect(result.error?).to be true
134
expect(result.content.first[:text]).to match(/Authentication failed/)
135
end
136
end
137
end
138
139
describe 'JSON-RPC Error Handling' do
140
let(:jsonrpc_url) { "https://#{host}:#{port}/api/v1/json-rpc" }
141
let(:client) do
142
Msf::MCP::Metasploit::Client.new(
143
api_type: 'json-rpc',
144
host: host,
145
port: port,
146
endpoint: '/api/v1/json-rpc',
147
token: 'bearer_token',
148
ssl: true
149
)
150
end
151
152
it 'handles JSON-RPC error responses' do
153
stub_request(:post, jsonrpc_url)
154
.to_return(
155
status: 200,
156
body: {
157
jsonrpc: '2.0',
158
error: { code: -32601, message: 'Method not found' },
159
id: 1
160
}.to_json,
161
headers: { 'Content-Type' => 'application/json' }
162
)
163
164
expect { client.search_modules('smb') }.to raise_error(Msf::MCP::Metasploit::APIError, /Method not found/)
165
end
166
167
it 'handles network errors with JSON-RPC client' do
168
stub_request(:post, jsonrpc_url)
169
.to_raise(Errno::ECONNREFUSED)
170
171
expect { client.search_modules('smb') }.to raise_error(Msf::MCP::Metasploit::ConnectionError)
172
end
173
174
it 'raises error with invalid bearer token' do
175
stub_request(:post, jsonrpc_url)
176
.to_return(
177
status: 401,
178
body: { 'error' => 'Unauthorized' }.to_json,
179
headers: { 'Content-Type' => 'application/json' }
180
)
181
182
expect { client.search_modules('smb') }.to raise_error(Msf::MCP::Metasploit::AuthenticationError, /Invalid authentication token/)
183
end
184
end
185
end
186
187