-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Action Items
Immediate (P0)
- Add request size limits and timeouts
- Implement input validation for JSON parsing
- Fix HTTP parsing vulnerabilities
- Remove hardcoded credentials
- Add basic authentication mechanism
High Priority (P1)
- Fix path traversal vulnerability
- Add security headers
- Implement rate limiting
- Add connection limits
- Fix CORS configuration
1. HTTP Server Security Analysis
1.1 CRITICAL: Buffer Overflow Vulnerabilities
Location: src/server.cpp:390-428 (handle_client function)
Issue: The HTTP request parsing uses a fixed-size buffer with dynamic resizing, but several vulnerabilities exist:
std::vector<char> buffer(65536);
int total_read = 0;
int valread;
while ((valread = read(client_socket, buffer.data() + total_read, buffer.size() - total_read - 1)) > 0) {
total_read += valread;
buffer[total_read] = '\0';Vulnerabilities:
- Unbounded read loop: An attacker can send unlimited data causing memory exhaustion
- Integer overflow:
total_readcan overflow if attacker sends > INT_MAX bytes - No timeout on read: Slowloris-style attacks can exhaust server resources
- Buffer growth without limits:
buffer.resize(buffer.size() * 2)can grow indefinitely
Impact:
- Denial of Service (DoS) through memory exhaustion
- Potential crashes from integer overflow
- Resource exhaustion from hanging connections
Recommendation:
- Implement maximum request size limit (e.g., 10MB)
- Add read timeout using
setsockopt(SO_RCVTIMEO) - Add bounds checking on total_read before operations
- Implement connection limits per client IP
Example Fix:
const size_t MAX_REQUEST_SIZE = 10 * 1024 * 1024; // 10MB
const int READ_TIMEOUT_SEC = 30;
// Set socket timeout
struct timeval tv;
tv.tv_sec = READ_TIMEOUT_SEC;
tv.tv_usec = 0;
setsockopt(client_socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
// Check size limits
if (total_read >= MAX_REQUEST_SIZE) {
std::cerr << "Request too large" << std::endl;
send_error_response(client_socket, 413, "Request Entity Too Large");
close(client_socket);
return;
}1.2 CRITICAL: HTTP Request Parsing Vulnerabilities
Location: src/server.cpp:57-87 (parse_http_request function)
Issue: Multiple vulnerabilities in HTTP header parsing:
// Parse body based on Content-Length
if (req.headers.find("Content-Length") != req.headers.end()) {
int content_length = std::stoi(req.headers["Content-Length"]);
req.body.resize(content_length);
stream.read(&req.body[0], content_length);
}Vulnerabilities:
- No validation of Content-Length value: Accepts negative or extremely large values
- No exception handling:
std::stoithrows on invalid input, causing crash - Direct trust of header: No sanity checking before memory allocation
- Missing bounds checking: Can allocate arbitrary amounts of memory
Impact:
- Application crash from invalid Content-Length headers
- Memory exhaustion from malicious Content-Length values
- DoS attacks
Recommendation:
try {
if (req.headers.find("Content-Length") != req.headers.end()) {
long content_length = std::stol(req.headers["Content-Length"]);
// Validate Content-Length
if (content_length < 0 || content_length > MAX_REQUEST_SIZE) {
std::cerr << "Invalid Content-Length: " << content_length << std::endl;
return req; // Return empty request
}
req.body.resize(static_cast<size_t>(content_length));
stream.read(&req.body[0], content_length);
}
} catch (const std::exception& e) {
std::cerr << "Error parsing Content-Length: " << e.what() << std::endl;
// Return request with empty body
}1.3 HIGH: Path Traversal Vulnerability
Location: src/server.cpp:126-165 (serve_static_file function)
Issue: Weak path traversal protection:
// Security check - prevent directory traversal
if (file_path.find("..") != std::string::npos) {
return build_http_response(403, "text/plain", "Forbidden");
}Vulnerabilities:
- Bypassable with URL encoding:
%2e%2ebypasses the check - Bypassable with alternate encodings:
..%2f,%2e%2e/, etc. - No canonical path validation: Doesn't resolve symlinks or normalize paths
- Missing null byte checks: Can be exploited on some filesystems
Impact:
- Read arbitrary files on the server
- Information disclosure
- Potential access to sensitive configuration files
Recommendation:
#include <filesystem>
std::string serve_static_file(const std::string& path) {
namespace fs = std::filesystem;
try {
std::string file_path = path;
if (file_path == "/") {
file_path = "/index.html";
}
// Remove leading slash
if (!file_path.empty() && file_path[0] == '/') {
file_path = file_path.substr(1);
}
// Construct full path
fs::path requested_path = fs::path("public") / file_path;
fs::path base_path = fs::canonical("public");
// Resolve to canonical path and validate it's within public/
fs::path canonical_path = fs::canonical(requested_path);
// Check if canonical path starts with base_path
auto [base_end, path_end] = std::mismatch(
base_path.begin(), base_path.end(),
canonical_path.begin(), canonical_path.end()
);
if (base_end != base_path.end()) {
return build_http_response(403, "text/plain", "Forbidden");
}
// ... rest of file serving logic
} catch (const fs::filesystem_error& e) {
return build_http_response(404, "text/plain", "Not Found");
}
}1.4 HIGH: Missing HTTP Security Headers
Location: src/server.cpp:36-47 (build_http_response function)
Issue: Critical security headers are missing:
std::string build_http_response(int status_code, const std::string& content_type, const std::string& body) {
std::ostringstream response;
response << "HTTP/1.1 " << status_code << " OK\r\n";
response << "Content-Type: " << content_type << "\r\n";
response << "Content-Length: " << body.length() << "\r\n";
response << "Access-Control-Allow-Origin: *\r\n";
// Missing critical security headersMissing Headers:
- X-Frame-Options: Allows clickjacking attacks
- X-Content-Type-Options: Allows MIME sniffing attacks
- Content-Security-Policy: No CSP protection
- X-XSS-Protection: No XSS protection header
- Strict-Transport-Security: No HSTS (if served over HTTPS via nginx)
Impact:
- Clickjacking attacks
- Cross-site scripting (XSS) vulnerabilities
- MIME sniffing attacks
- Mixed content issues
Recommendation:
std::string build_http_response(int status_code, const std::string& content_type, const std::string& body) {
std::ostringstream response;
response << "HTTP/1.1 " << status_code << " OK\r\n";
response << "Content-Type: " << content_type << "\r\n";
response << "Content-Length: " << body.length() << "\r\n";
// Security headers
response << "X-Frame-Options: DENY\r\n";
response << "X-Content-Type-Options: nosniff\r\n";
response << "X-XSS-Protection: 1; mode=block\r\n";
response << "Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'\r\n";
response << "Referrer-Policy: strict-origin-when-cross-origin\r\n";
// CORS (keep restrictive or configure based on needs)
response << "Access-Control-Allow-Origin: *\r\n";
response << "Access-Control-Allow-Methods: GET, POST, OPTIONS\r\n";
response << "Access-Control-Allow-Headers: Content-Type\r\n";
response << "\r\n";
response << body;
return response.str();
}1.5 CRITICAL: Race Condition in Conversation History
Location: src/server.cpp:96-101, 195-210, 293-308 (Conversation history management)
Issue: Potential race conditions in conversation history cleanup:
std::map<std::string, ConversationHistory> conversations;
std::mutex conversations_mutex;
// Later in code:
{
std::lock_guard<std::mutex> lock(conversations_mutex);
auto& conv = conversations[conversation_id];
history = conv.messages;
// Clean up old conversations (older than 1 hour)
auto now = std::chrono::system_clock::now();
for (auto it = conversations.begin(); it != conversations.end(); ) {
if (std::chrono::duration_cast<std::chrono::hours>(now - it->second.last_activity).count() > 1) {
it = conversations.erase(it);
} else {
++it;
}
}
}Vulnerabilities:
- Iterator invalidation during concurrent access: If multiple threads iterate and erase
- Use-after-free risk: History copied while cleanup may erase the same conversation
- Memory leak: Unlimited conversations can accumulate before cleanup
- No maximum conversation limit: Attackers can exhaust memory by creating many conversations
Impact:
- Application crash from iterator invalidation
- Memory exhaustion from conversation flooding
- Undefined behavior
Recommendation:
// Add maximum conversation limit
const size_t MAX_CONVERSATIONS = 1000;
// Separate cleanup function
void cleanup_old_conversations() {
std::lock_guard<std::mutex> lock(conversations_mutex);
auto now = std::chrono::system_clock::now();
for (auto it = conversations.begin(); it != conversations.end(); ) {
if (std::chrono::duration_cast<std::chrono::hours>(now - it->second.last_activity).count() > 1) {
it = conversations.erase(it);
} else {
++it;
}
}
// Enforce maximum limit by removing oldest if needed
while (conversations.size() > MAX_CONVERSATIONS) {
auto oldest = std::min_element(conversations.begin(), conversations.end(),
[](const auto& a, const auto& b) {
return a.second.last_activity < b.second.last_activity;
});
conversations.erase(oldest);
}
}
// Call cleanup in a separate thread or less frequently2. Authentication and Authorization
2.1 CRITICAL: No Authentication Mechanism
Location: All endpoints in src/server.cpp
Issue: The server has no authentication or authorization:
/queryendpoint is completely open/statusendpoint is open- Static file serving is open
- No API keys, tokens, or user authentication
Impact:
- Unrestricted access to LLM queries
- Resource exhaustion from abuse
- Potential for malicious queries
- No rate limiting or abuse prevention
Recommendation:
- Implement API key authentication for
/queryendpoint - Add rate limiting per IP address
- Consider implementing basic HTTP authentication for production
- Add request throttling
Example Implementation:
// Add to config.h
const std::string API_KEY_HEADER = "X-API-Key";
const size_t MAX_REQUESTS_PER_MINUTE = 60;
// Add rate limiting map
std::map<std::string, std::pair<std::chrono::system_clock::time_point, int>> rate_limits;
std::mutex rate_limit_mutex;
bool check_rate_limit(const std::string& client_ip) {
std::lock_guard<std::mutex> lock(rate_limit_mutex);
auto now = std::chrono::system_clock::now();
auto& [last_reset, count] = rate_limits[client_ip];
// Reset counter every minute
if (std::chrono::duration_cast<std::chrono::minutes>(now - last_reset).count() >= 1) {
last_reset = now;
count = 0;
}
if (count >= MAX_REQUESTS_PER_MINUTE) {
return false;
}
count++;
return true;
}
// In handle_client, add authentication check:
if (request.method == "POST" && request.path == "/query") {
// Check API key if configured
const char* required_key = getenv("API_KEY");
if (required_key && required_key[0] != '\0') {
auto key_header = request.headers.find(API_KEY_HEADER);
if (key_header == request.headers.end() || key_header->second != required_key) {
response = build_http_response(401, "application/json",
"{\"error\":\"Unauthorized\"}");
send(client_socket, response.c_str(), response.length(), 0);
close(client_socket);
return;
}
}
// Check rate limit
if (!check_rate_limit(client_ip)) {
response = build_http_response(429, "application/json",
"{\"error\":\"Too Many Requests\"}");
send(client_socket, response.c_str(), response.length(), 0);
close(client_socket);
return;
}
response = handle_query(request.body);
}2.2 HIGH: CORS Configuration Too Permissive
Location: src/server.cpp:41
Issue:
response << "Access-Control-Allow-Origin: *\r\n";Impact:
- Any website can make requests to the server
- Potential for cross-site request forgery (CSRF) attacks
- Data exfiltration from malicious sites
Recommendation:
// Configure allowed origins from environment
std::string get_allowed_origin() {
const char* origin = getenv("ALLOWED_ORIGIN");
return origin ? origin : "http://localhost:8080";
}
// In build_http_response:
response << "Access-Control-Allow-Origin: " << get_allowed_origin() << "\r\n";
response << "Access-Control-Allow-Credentials: true\r\n";3. Data Handling and Input Validation
3.1 HIGH: JSON Parsing Without Validation
Location: src/server.cpp:168-189 (handle_query function)
Issue: Direct parsing of user JSON input without validation:
auto request_json = json::parse(body);
std::string query = request_json["query"];Vulnerabilities:
- No exception handling for malformed JSON
- No validation of query field existence
- No validation of query length or content
- Potential for JSON injection
Impact:
- Application crash from malformed JSON
- DoS from extremely large queries
- Injection attacks
Recommendation:
const size_t MAX_QUERY_LENGTH = 10000;
std::string handle_query(const std::string& body) {
try {
// Validate body size
if (body.length() > MAX_QUERY_LENGTH * 2) {
json error;
error["error"] = "Request body too large";
return build_http_response(413, "application/json", error.dump());
}
auto request_json = json::parse(body);
// Validate required fields
if (!request_json.contains("query") || !request_json["query"].is_string()) {
json error;
error["error"] = "Missing or invalid 'query' field";
return build_http_response(400, "application/json", error.dump());
}
std::string query = request_json["query"];
// Validate query length
if (query.empty() || query.length() > MAX_QUERY_LENGTH) {
json error;
error["error"] = "Query too long or empty";
return build_http_response(400, "application/json", error.dump());
}
// Sanitize query (remove control characters)
query.erase(std::remove_if(query.begin(), query.end(),
[](char c) { return c < 32 && c != '\n' && c != '\r' && c != '\t'; }),
query.end());
// ... rest of processing
} catch (const json::parse_error& e) {
json error;
error["error"] = "Invalid JSON format";
return build_http_response(400, "application/json", error.dump());
} catch (const std::exception& e) {
// ... existing error handling
}
}3.2 MEDIUM: Unsafe String Operations
Location: src/text_utils.h:26-118 (extract_text_with_tika function)
Issue: Potential issues with file handling:
std::stringstream buffer;
buffer << file.rdbuf();
std::string file_content = buffer.str();Vulnerabilities:
- No memory limits on file reading
- Entire file loaded into memory
- Can cause OOM with malicious files
Impact:
- Memory exhaustion
- DoS attacks
Recommendation:
- Implement streaming for large files
- Add memory monitoring
- Implement file size limits (already present but can be improved)
3.3 MEDIUM: Command Injection via File Paths
Location: src/text_utils.h:27-59, src/server.cpp:146-157
Issue: File paths from user input used without sanitization
Recommendation:
- Validate all file paths against allowed characters
- Use allowlist of allowed file extensions
- Implement strict path validation
4. Memory Management
4.1 HIGH: Resource Leaks in Error Paths
Location: src/server.cpp:528-535 (handle_client thread creation)
Issue:
try {
std::thread client_thread(handle_client, client_socket);
client_thread.detach();
} catch (const std::exception& e) {
std::cerr << "Error creating client thread: " << e.what() << std::endl;
close(client_socket);
}Vulnerabilities:
- Socket not closed on thread creation failure
- Unlimited thread creation can exhaust resources
- No thread pool or connection limits
Impact:
- Resource exhaustion
- DoS attacks
- File descriptor leaks
Recommendation:
const size_t MAX_CONCURRENT_CONNECTIONS = 100;
std::atomic<size_t> active_connections{0};
// In main loop:
if (active_connections >= MAX_CONCURRENT_CONNECTIONS) {
std::cerr << "Too many concurrent connections" << std::endl;
close(client_socket);
continue;
}
active_connections++;
try {
std::thread client_thread([client_socket]() {
try {
handle_client(client_socket);
} catch (...) {
close(client_socket);
}
active_connections--;
});
client_thread.detach();
} catch (const std::exception& e) {
std::cerr << "Error creating client thread: " << e.what() << std::endl;
close(client_socket);
active_connections--;
}4.2 MEDIUM: Memory Safety in LLM Context
Location: src/llm.h:49-214 (LLMGenerator class)
Issue: Context recreation on every request can fail
llama_free(ctx);
ctx = llama_init_from_model(model, ctx_params);Vulnerabilities:
- No rollback if context creation fails
- Old context freed before new one created
- Can leave generator in broken state
Recommendation:
// Create new context first, then swap
llama_context* new_ctx = llama_init_from_model(model, ctx_params);
if (!new_ctx) {
std::cerr << "Failed to create new context, keeping old one" << std::endl;
// Keep old context and return error
return "Error: Failed to create LLM context";
}
// Swap contexts
llama_context* old_ctx = ctx;
ctx = new_ctx;
llama_free(old_ctx);5. Configuration and Secrets Management
5.1 CRITICAL: Hardcoded Credentials
Location: src/config.h:60-68 (PostgreSQL configuration)
Issue:
inline std::string get_pg_password() {
const char* p = getenv("POSTGRES_PASSWORD");
return p ? p : "jic_password";
}Vulnerabilities:
- Default password hardcoded in source
- Credentials in plaintext
- No secrets rotation capability
Impact:
- Unauthorized database access if defaults used
- Credentials in version control
- Compliance violations
Recommendation:
inline std::string get_pg_password() {
const char* p = getenv("POSTGRES_PASSWORD");
if (!p || p[0] == '\0') {
std::cerr << "ERROR: POSTGRES_PASSWORD environment variable must be set" << std::endl;
std::cerr << "No default password available for security reasons" << std::endl;
exit(1);
}
return p;
}5.2 HIGH: Insecure Model Path Handling
Location: src/config.h:14-41
Issue: Model paths constructed from environment variables without validation
Recommendation:
- Validate model paths exist before use
- Restrict to specific directories
- Use absolute paths only
5.3 MEDIUM: Missing TLS/HTTPS Support
Issue: The C++ server doesn't support HTTPS natively, relies on nginx proxy
Recommendation:
- Document that TLS must be handled by nginx
- Add headers to indicate when behind reverse proxy
- Consider adding native TLS support using OpenSSL
6. Error Handling and Logging
6.1 MEDIUM: Information Disclosure in Error Messages
Location: Multiple locations with std::cerr logging
Issue: Detailed error messages logged to stderr can expose system information
Recommendation:
- Implement proper logging levels
- Don't expose internal paths or system info in production
- Use structured logging
6.2 LOW: No Request Logging for Security Auditing
Issue: No logging of requests for security monitoring
Recommendation:
void log_request(const std::string& client_ip, const HttpRequest& req) {
auto now = std::chrono::system_clock::now();
auto time = std::chrono::system_clock::to_time_t(now);
std::cout << "[" << std::put_time(std::localtime(&time), "%Y-%m-%d %H:%M:%S") << "] "
<< client_ip << " "
<< req.method << " "
<< req.path << std::endl;
}7. Denial of Service Vulnerabilities
7.1 HIGH: No Connection Limits
Issue: Server accepts unlimited connections
Impact: DoS through connection exhaustion
Recommendation: Implement connection pooling and limits (see 4.1)
7.2 HIGH: No Query Complexity Limits
Location: src/server.cpp:168-371 (handle_query)
Issue:
- No limits on context size for LLM
- Can exhaust GPU/CPU resources
- No timeout on LLM generation
Recommendation:
- Implement query timeout
- Limit LLM generation time
- Add resource monitoring
7.3 MEDIUM: Vector Index Load Without Validation
Location: src/server.cpp:104-123 (load_index function)
Issue: Index loaded without size or integrity validation
Recommendation:
- Validate index file size
- Check file integrity
- Implement corruption detection
8. Third-Party Dependencies
8.1 MEDIUM: Tika Service Communication
Location: src/text_utils.h:27-118
Issue: Communication with Tika service over HTTP without authentication
Recommendation:
- Use HTTPS for Tika communication
- Implement authentication between services
- Validate Tika responses
8.2 LOW: Dependency Version Management
Issue: No explicit version pinning for llama.cpp in Dockerfile
Recommendation:
- Pin specific commits or tags
- Implement dependency scanning
- Regular security updates
9. Summary of Findings
Critical Issues (Immediate Action Required):
- ✅ Buffer overflow vulnerabilities in HTTP parsing
- ✅ HTTP request parsing without validation
- ✅ No authentication mechanism
- ✅ Race condition in conversation history
- ✅ Hardcoded database credentials
- ✅ Missing input validation on JSON parsing
- ✅ Path traversal vulnerability
High Priority Issues:
- ✅ Path traversal protection weaknesses
- ✅ Missing HTTP security headers
- ✅ CORS configuration too permissive
- ✅ Resource leaks in error paths
- ✅ No connection limits or rate limiting
Medium Priority Issues:
- ✅ Unsafe string operations
- ✅ Memory safety in LLM context
- ✅ Missing TLS/HTTPS support
- ✅ Information disclosure in errors
10. Remediation Roadmap
Phase 1: Critical Fixes (Week 1)
- Implement request size limits
- Add input validation for all user inputs
- Fix HTTP parsing vulnerabilities
- Add basic authentication
- Remove hardcoded credentials
Phase 2: High Priority (Week 2-3)
- Implement proper path validation
- Add security headers
- Fix CORS configuration
- Add rate limiting
- Implement connection limits
Phase 3: Hardening (Week 4+)
- Add comprehensive logging
- Implement resource monitoring
- Add health checks
- Security testing and penetration testing
- Documentation updates
11. Secure Coding Practices Recommendations
11.1 Input Validation
- Always validate and sanitize all inputs
- Use allowlists rather than denylists
- Validate data types, lengths, and formats
- Implement proper error handling
11.2 Memory Safety
- Use RAII and smart pointers where possible
- Avoid raw pointers and manual memory management
- Implement bounds checking
- Use safe string operations
11.3 Error Handling
- Never trust user input
- Fail securely (deny by default)
- Don't expose internal details in errors
- Log security-relevant events
11.4 Authentication & Authorization
- Implement strong authentication
- Use API keys or tokens
- Rate limit all endpoints
- Implement proper session management
11.5 Network Security
- Use TLS for all communications
- Implement proper timeout handling
- Use secure defaults
- Validate all network inputs
12. nginx Configuration Recommendations
Since the application will be deployed behind nginx, add the following nginx configuration:
# Rate limiting
limit_req_zone $binary_remote_addr zone=jic:10m rate=10r/s;
limit_req zone=jic burst=20 nodelay;
# Connection limits
limit_conn_zone $binary_remote_addr zone=addr:10m;
limit_conn addr 10;
server {
listen 443 ssl http2;
server_name your-domain.com;
# TLS configuration
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Content-Security-Policy "default-src 'self'" always;
# Request size limits
client_max_body_size 10M;
client_body_timeout 30s;
client_header_timeout 30s;
# Proxy settings
location / {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Timeouts
proxy_connect_timeout 30s;
proxy_send_timeout 30s;
proxy_read_timeout 90s;
}
# Block common attack patterns
location ~ /\.(?!well-known) {
deny all;
}
}