Path: blob/master/spec/integration/msfmcpd/error_handling_spec.rb
70330 views
# frozen_string_literal: true12require 'msf/core/mcp'3require 'webmock/rspec'45RSpec.describe 'Error Handling Integration' do6# Disable real HTTP connections for integration tests7before(:all) do8WebMock.disable_net_connect!(allow_localhost: false)9end1011after(:all) do12WebMock.allow_net_connect!13end1415let(:host) { 'localhost' }16let(:port) { 55553 }1718describe 'MessagePack Error Handling' do19let(:endpoint) { '/api/' }20let(:api_url) { "https://#{host}:#{port}#{endpoint}" }21let(:client) do22Msf::MCP::Metasploit::Client.new(23api_type: 'messagepack',24host: host,25port: port,26endpoint: endpoint,27ssl: true28)29end3031before do32# Mock successful authentication to get the token33stub_request(:post, api_url)34.with(body: ['auth.login', 'user', 'pass'].to_msgpack)35.to_return(36status: 200,37body: { 'result' => 'success', 'token' => 'test_token' }.to_msgpack,38headers: { 'Content-Type' => 'binary/message-pack' }39)4041client.authenticate('user', 'pass')42end4344describe 'Network Connection Failures' do45it 'converts Errno::ECONNREFUSED to ConnectionError' do46# Mock a connection refused error47stub_request(:post, api_url)48.with(body: ['module.search', 'test_token', 'smb'].to_msgpack)49.to_raise(Errno::ECONNREFUSED)5051expect { client.search_modules('smb') }.to raise_error(Msf::MCP::Metasploit::ConnectionError, /Cannot connect to Metasploit RPC/)52end5354it 'converts SocketError to ConnectionError' do55stub_request(:post, api_url)56.to_raise(SocketError.new('getaddrinfo: Name or service not known'))5758expect { client.search_modules('smb') }.to raise_error(Msf::MCP::Metasploit::ConnectionError, /Network error/)59end6061it 'converts Timeout::Error to ConnectionError' do62# Timeout on search63stub_request(:post, api_url)64.with(body: ['module.search', 'test_token', 'smb'].to_msgpack)65.to_raise(Timeout::Error.new('execution expired'))6667expect { client.search_modules('smb') }.to raise_error(Msf::MCP::Metasploit::ConnectionError, /Request timeout/)68end6970it 'converts EOFError to ConnectionError' do71stub_request(:post, api_url)72.to_raise(EOFError.new('end of file reached'))7374expect { client.search_modules('smb') }.to raise_error(Msf::MCP::Metasploit::ConnectionError, /Empty response/)75end76end7778describe 'HTTP Status Code Handling' do79it 'converts HTTP 401 to AuthenticationError' do80# Reauthenticate with invalid creds81stub_request(:post, api_url)82.to_return(83status: 401,84body: { 'error_message' => 'Invalid credentials' }.to_msgpack,85headers: { 'Content-Type' => 'binary/message-pack' }86)8788expect { client.authenticate('invalid', 'invalid') }.to raise_error(Msf::MCP::Metasploit::AuthenticationError, /Invalid credentials/)89end9091it 'converts HTTP 500 to APIError' do92# Server error on API call93stub_request(:post, api_url)94.with(body: ['module.search', 'test_token', 'smb'].to_msgpack)95.to_return(96status: 500,97body: { 'error_message' => 'Internal server error' }.to_msgpack98)99100expect { client.search_modules('smb') }.to raise_error(Msf::MCP::Metasploit::APIError, /Internal server error/)101end102103it 'converts unexpected HTTP status to ConnectionError' do104stub_request(:post, api_url)105.to_return(status: 503, body: 'Service Unavailable')106107expect { client.search_modules('smb') }.to raise_error(Msf::MCP::Metasploit::ConnectionError, /HTTP 503/)108end109end110111describe 'Tool Error Handling Integration' do112let(:rate_limiter) { Msf::MCP::Security::RateLimiter.new(requests_per_minute: 60) }113let(:server_context) do114{115msf_client: client,116rate_limiter: rate_limiter,117config: {}118}119end120121it 'converts Metasploit errors to MCP error responses end-to-end' do122# Test that errors propagate correctly through the entire stack:123# HTTP error → Metasploit exception → MCP error response (isError: true)124stub_request(:post, api_url)125.with(body: ['module.search', 'test_token', 'smb'].to_msgpack)126.to_return(127status: 401,128body: { 'error_message' => 'Token invalid' }.to_msgpack129)130131result = Msf::MCP::Tools::SearchModules.call(query: 'smb', server_context: server_context)132expect(result.error?).to be true133expect(result.content.first[:text]).to match(/Authentication failed/)134end135end136end137138describe 'JSON-RPC Error Handling' do139let(:jsonrpc_url) { "https://#{host}:#{port}/api/v1/json-rpc" }140let(:client) do141Msf::MCP::Metasploit::Client.new(142api_type: 'json-rpc',143host: host,144port: port,145endpoint: '/api/v1/json-rpc',146token: 'bearer_token',147ssl: true148)149end150151it 'handles JSON-RPC error responses' do152stub_request(:post, jsonrpc_url)153.to_return(154status: 200,155body: {156jsonrpc: '2.0',157error: { code: -32601, message: 'Method not found' },158id: 1159}.to_json,160headers: { 'Content-Type' => 'application/json' }161)162163expect { client.search_modules('smb') }.to raise_error(Msf::MCP::Metasploit::APIError, /Method not found/)164end165166it 'handles network errors with JSON-RPC client' do167stub_request(:post, jsonrpc_url)168.to_raise(Errno::ECONNREFUSED)169170expect { client.search_modules('smb') }.to raise_error(Msf::MCP::Metasploit::ConnectionError)171end172173it 'raises error with invalid bearer token' do174stub_request(:post, jsonrpc_url)175.to_return(176status: 401,177body: { 'error' => 'Unauthorized' }.to_json,178headers: { 'Content-Type' => 'application/json' }179)180181expect { client.search_modules('smb') }.to raise_error(Msf::MCP::Metasploit::AuthenticationError, /Invalid authentication token/)182end183end184end185186187