Skip to content

Commit 2cd0d34

Browse files
committed
Security hardening and test coverage improvements
This commit addresses multiple security vulnerabilities and adds comprehensive test coverage for HTTP client API integration. Security Fixes: - Add payload and timeout limits to HTTP/SSE servers (issue #3) - Fix SSE session security with crypto-random IDs and session binding (issue #2) - Add optional authentication and restrict CORS (issue #1) - Fix HTTP client scheme handling and disable redirects (issue #4) - Add security middleware for logging, rate limiting, and concurrency control (issue #5) Test Coverage: - Add HTTP client API integration tests (not LoopbackTransport) - Add SSE HTTP integration tests with real network stack - Fix SSE server test to extract and use session_id All 45 tests passing (100% pass rate)
1 parent b2b2772 commit 2cd0d34

16 files changed

Lines changed: 1917 additions & 42 deletions

CMakeLists.txt

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ add_library(fastmcpp_core
2525
src/server/server.cpp
2626
src/server/context.cpp
2727
src/server/middleware.cpp
28+
src/server/security_middleware.cpp
2829
src/server/http_server.cpp
2930
src/server/stdio_server.cpp
3031
src/server/sse_server.cpp
@@ -250,10 +251,40 @@ if(FASTMCPP_BUILD_TESTS)
250251
target_link_libraries(fastmcpp_server_context_meta PRIVATE fastmcpp_core)
251252
add_test(NAME fastmcpp_server_context_meta COMMAND fastmcpp_server_context_meta)
252253

254+
add_executable(fastmcpp_server_security_limits tests/server/security_limits.cpp)
255+
target_link_libraries(fastmcpp_server_security_limits PRIVATE fastmcpp_core)
256+
add_test(NAME fastmcpp_server_security_limits COMMAND fastmcpp_server_security_limits)
257+
258+
add_executable(fastmcpp_server_sse_session_security tests/server/sse_session_security.cpp)
259+
target_link_libraries(fastmcpp_server_sse_session_security PRIVATE fastmcpp_core)
260+
add_test(NAME fastmcpp_server_sse_session_security COMMAND fastmcpp_server_sse_session_security)
261+
262+
# SSE session security with fastmcpp::client::HttpTransport (not raw httplib)
263+
add_executable(fastmcpp_client_sse_session_client tests/client/sse_session_client.cpp)
264+
target_link_libraries(fastmcpp_client_sse_session_client PRIVATE fastmcpp_core)
265+
add_test(NAME fastmcpp_client_sse_session_client COMMAND fastmcpp_client_sse_session_client)
266+
267+
# SSE + HTTP integration (real network, not LoopbackTransport)
268+
add_executable(fastmcpp_server_sse_http_integration tests/server/sse_http_integration.cpp)
269+
target_link_libraries(fastmcpp_server_sse_http_integration PRIVATE fastmcpp_core)
270+
add_test(NAME fastmcpp_server_sse_http_integration COMMAND fastmcpp_server_sse_http_integration)
271+
272+
add_executable(fastmcpp_server_auth_cors_security tests/server/auth_cors_security.cpp)
273+
target_link_libraries(fastmcpp_server_auth_cors_security PRIVATE fastmcpp_core)
274+
add_test(NAME fastmcpp_server_auth_cors_security COMMAND fastmcpp_server_auth_cors_security)
275+
276+
add_executable(fastmcpp_server_security_middleware tests/server/security_middleware.cpp)
277+
target_link_libraries(fastmcpp_server_security_middleware PRIVATE fastmcpp_core)
278+
add_test(NAME fastmcpp_server_security_middleware COMMAND fastmcpp_server_security_middleware)
279+
253280
add_executable(fastmcpp_client_transports tests/client/transports.cpp)
254281
target_link_libraries(fastmcpp_client_transports PRIVATE fastmcpp_core)
255282
add_test(NAME fastmcpp_client_transports COMMAND fastmcpp_client_transports)
256283

284+
add_executable(fastmcpp_client_http_client_security tests/client/http_client_security.cpp)
285+
target_link_libraries(fastmcpp_client_http_client_security PRIVATE fastmcpp_core)
286+
add_test(NAME fastmcpp_client_http_client_security COMMAND fastmcpp_client_http_client_security)
287+
257288
add_executable(fastmcpp_client_api_basic tests/client/api_basic.cpp)
258289
target_link_libraries(fastmcpp_client_api_basic PRIVATE fastmcpp_core)
259290
add_test(NAME fastmcpp_client_api_basic COMMAND fastmcpp_client_api_basic)

include/fastmcpp/server/http_server.hpp

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,18 @@ namespace fastmcpp::server
1717
class HttpServerWrapper
1818
{
1919
public:
20+
/**
21+
* Construct an HTTP server with a core Server instance.
22+
*
23+
* @param core Shared pointer to the core Server (routes handler)
24+
* @param host Host address to bind to (default: "127.0.0.1" for localhost)
25+
* @param port Port to listen on (default: 18080)
26+
* @param auth_token Optional auth token for Bearer authentication (empty = no auth required)
27+
* @param cors_origin Optional CORS origin to allow (empty = no CORS header, use "*" for wildcard)
28+
*/
2029
HttpServerWrapper(std::shared_ptr<Server> core, std::string host = "127.0.0.1",
21-
int port = 18080);
30+
int port = 18080, std::string auth_token = "",
31+
std::string cors_origin = "");
2232
~HttpServerWrapper();
2333

2434
bool start();
@@ -37,9 +47,13 @@ class HttpServerWrapper
3747
}
3848

3949
private:
50+
bool check_auth(const std::string& auth_header) const;
51+
4052
std::shared_ptr<Server> core_;
4153
std::string host_;
4254
int port_;
55+
std::string auth_token_; // Optional Bearer token for authentication
56+
std::string cors_origin_; // Optional CORS origin (empty = no CORS)
4357
std::unique_ptr<httplib::Server> svr_;
4458
std::thread thread_;
4559
std::atomic<bool> running_{false};
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
#pragma once
2+
#include "fastmcpp/server/middleware.hpp"
3+
#include "fastmcpp/types.hpp"
4+
5+
#include <atomic>
6+
#include <chrono>
7+
#include <deque>
8+
#include <functional>
9+
#include <mutex>
10+
#include <string>
11+
#include <unordered_map>
12+
13+
namespace fastmcpp::server
14+
{
15+
16+
/// Log entry for a request
17+
struct RequestLogEntry
18+
{
19+
std::chrono::system_clock::time_point timestamp;
20+
std::string route;
21+
size_t payload_size;
22+
bool success;
23+
std::string error_message; // Empty if success
24+
};
25+
26+
/// Logging callback function type
27+
using LogCallback = std::function<void(const RequestLogEntry&)>;
28+
29+
/// Logging middleware for audit trail (v2.13.0+)
30+
///
31+
/// Provides optional request logging to track all route/tool invocations.
32+
/// Can be used as both BeforeHook and AfterHook for comprehensive logging.
33+
///
34+
/// Usage:
35+
/// ```cpp
36+
/// auto logger = std::make_shared<LoggingMiddleware>(
37+
/// [](const RequestLogEntry& entry) {
38+
/// std::cout << entry.timestamp << " " << entry.route << std::endl;
39+
/// });
40+
/// srv.add_before(logger->create_before_hook());
41+
/// srv.add_after(logger->create_after_hook());
42+
/// ```
43+
class LoggingMiddleware
44+
{
45+
public:
46+
explicit LoggingMiddleware(LogCallback callback) : callback_(std::move(callback))
47+
{
48+
}
49+
50+
/// Create a BeforeHook that logs incoming requests
51+
BeforeHook create_before_hook();
52+
53+
/// Create an AfterHook that logs completed requests
54+
AfterHook create_after_hook();
55+
56+
private:
57+
LogCallback callback_;
58+
std::mutex mutex_;
59+
std::unordered_map<std::string, size_t> request_sizes_; // Track sizes for after hook
60+
};
61+
62+
/// Rate limiting middleware for DoS prevention (v2.13.0+)
63+
///
64+
/// Enforces per-route request limits using a sliding window algorithm.
65+
/// Rejects requests that exceed the configured rate.
66+
///
67+
/// Usage:
68+
/// ```cpp
69+
/// auto limiter = std::make_shared<RateLimitMiddleware>(
70+
/// 100, // max requests
71+
/// std::chrono::minutes(1) // per time window
72+
/// );
73+
/// srv.add_before(limiter->create_hook());
74+
/// ```
75+
class RateLimitMiddleware
76+
{
77+
public:
78+
/// Construct rate limiter
79+
/// @param max_requests Maximum requests allowed in time window
80+
/// @param window Time window for rate limiting
81+
RateLimitMiddleware(size_t max_requests,
82+
std::chrono::steady_clock::duration window = std::chrono::minutes(1))
83+
: max_requests_(max_requests), window_(window)
84+
{
85+
}
86+
87+
/// Create a BeforeHook that enforces rate limits
88+
BeforeHook create_hook();
89+
90+
/// Get current request count for a route
91+
size_t get_request_count(const std::string& route);
92+
93+
/// Reset rate limit counters (for testing)
94+
void reset();
95+
96+
private:
97+
size_t max_requests_;
98+
std::chrono::steady_clock::duration window_;
99+
std::mutex mutex_;
100+
101+
struct RouteStats
102+
{
103+
std::deque<std::chrono::steady_clock::time_point> timestamps;
104+
};
105+
106+
std::unordered_map<std::string, RouteStats> stats_;
107+
108+
void cleanup_old_entries(RouteStats& stats);
109+
};
110+
111+
/// Concurrency limiting middleware for resource control (v2.13.0+)
112+
///
113+
/// Limits the number of concurrent route handler executions.
114+
/// Uses atomic counters for thread-safe tracking.
115+
///
116+
/// Usage:
117+
/// ```cpp
118+
/// auto limiter = std::make_shared<ConcurrencyLimitMiddleware>(10); // Max 10 parallel
119+
/// srv.add_before(limiter->create_before_hook());
120+
/// srv.add_after(limiter->create_after_hook());
121+
/// ```
122+
class ConcurrencyLimitMiddleware
123+
{
124+
public:
125+
/// Construct concurrency limiter
126+
/// @param max_concurrent Maximum number of concurrent handler executions
127+
explicit ConcurrencyLimitMiddleware(size_t max_concurrent) : max_concurrent_(max_concurrent)
128+
{
129+
}
130+
131+
/// Create a BeforeHook that checks concurrency limit
132+
BeforeHook create_before_hook();
133+
134+
/// Create an AfterHook that releases concurrency slot
135+
AfterHook create_after_hook();
136+
137+
/// Get current concurrent request count
138+
size_t get_current_count() const
139+
{
140+
return current_count_.load();
141+
}
142+
143+
private:
144+
size_t max_concurrent_;
145+
std::atomic<size_t> current_count_{0};
146+
};
147+
148+
} // namespace fastmcpp::server

include/fastmcpp/server/sse_server.hpp

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <queue>
1212
#include <string>
1313
#include <thread>
14+
#include <unordered_map>
1415

1516
namespace fastmcpp::server
1617
{
@@ -50,10 +51,14 @@ class SseServerWrapper
5051
* @param port Port to listen on (default: 18080)
5152
* @param sse_path Path for SSE GET endpoint (default: "/sse")
5253
* @param message_path Path for POST message endpoint (default: "/messages")
54+
* @param auth_token Optional auth token for Bearer authentication (empty = no auth required)
55+
* @param cors_origin Optional CORS origin to allow (empty = no CORS header, use "*" for wildcard)
5356
*/
5457
explicit SseServerWrapper(McpHandler handler, std::string host = "127.0.0.1", int port = 18080,
5558
std::string sse_path = "/sse",
56-
std::string message_path = "/messages");
59+
std::string message_path = "/messages",
60+
std::string auth_token = "",
61+
std::string cors_origin = "");
5762

5863
~SseServerWrapper();
5964

@@ -118,29 +123,40 @@ class SseServerWrapper
118123
private:
119124
void run_server();
120125
void send_event_to_all_clients(const fastmcpp::Json& event);
126+
void send_event_to_session(const std::string& session_id, const fastmcpp::Json& event);
127+
std::string generate_session_id();
128+
bool check_auth(const std::string& auth_header) const;
121129

122130
McpHandler handler_;
123131
std::string host_;
124132
int port_;
125133
std::string sse_path_;
126134
std::string message_path_;
135+
std::string auth_token_; // Optional Bearer token for authentication
136+
std::string cors_origin_; // Optional CORS origin (empty = no CORS)
127137

128138
std::unique_ptr<httplib::Server> svr_;
129139
std::thread thread_;
130140
std::atomic<bool> running_{false};
131141

142+
// Security limits
143+
static constexpr size_t MAX_CONNECTIONS = 100;
144+
static constexpr size_t MAX_QUEUE_SIZE = 1000;
145+
132146
struct ConnectionState
133147
{
148+
std::string session_id;
134149
std::deque<fastmcpp::Json> queue;
135150
std::mutex m;
136151
std::condition_variable cv;
137152
bool alive{true};
138153
};
139154

140-
void handle_sse_connection(httplib::DataSink& sink, std::shared_ptr<ConnectionState> conn);
155+
void handle_sse_connection(httplib::DataSink& sink, std::shared_ptr<ConnectionState> conn,
156+
const std::string& session_id);
141157

142-
// Active SSE connections (per-connection queues)
143-
std::vector<std::shared_ptr<ConnectionState>> connections_;
158+
// Active SSE connections mapped by session ID
159+
std::unordered_map<std::string, std::shared_ptr<ConnectionState>> connections_;
144160
std::mutex conns_mutex_;
145161
};
146162

0 commit comments

Comments
 (0)