Security hardening: fix IDOR vulnerabilities, add rate limiting, SSRF protection, and encryption upgrades
- Fix ReconciliationController and BankSync LinkAccount/UpdateLinkedAccount IDOR (verify account ownership) - Add ASP.NET Core rate limiting: strict on auth endpoints, global on all API routes - Harden SSRF validation on Ollama URL (IPv6-mapped bypass, link-local, 0.0.0.0 blocking) - Upgrade encryption from AES-CBC to AES-GCM with HKDF key derivation (backward-compatible decrypt) - Add startup validation that rejects default JWT/encryption keys in Production - Add security headers (X-Frame-Options, CSP, HSTS, Cache-Control, nosniff) to API and nginx - Mask account numbers in API responses (show last 4 digits only) - Add password strength validation (min 8 chars) and BCrypt work factor 12 - Hash refresh tokens (SHA-256) before database storage - Set JWT ClockSkew to zero for immediate expiry enforcement - Add file upload size limit (10 MB) and filename sanitization - Cap transaction search PageSize to 200 - Fix FileWatcherService cross-user account matching in multi-user deployments - Sanitize console.error calls to prevent token leakage in browser logs - Parameterize raw SQL in SimpleFinConnectionMigration - Make AuthService.GenerateAuthResponse fully async Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -112,6 +112,7 @@ public class ChatService : IChatService
|
||||
// Load AI settings
|
||||
var settings = await _db.AiCategorizationSettings.FirstOrDefaultAsync(s => s.UserId == userId);
|
||||
var ollamaUrl = settings?.OllamaUrl ?? "http://localhost:11434";
|
||||
ValidateOllamaUrl(ollamaUrl);
|
||||
var modelName = settings?.ModelName ?? "llama3.1:8b";
|
||||
var chatContextSize = settings?.ChatContextSize ?? 16384;
|
||||
var chatBotName = settings?.ChatBotName;
|
||||
@@ -1281,4 +1282,53 @@ public class ChatService : IChatService
|
||||
}
|
||||
return int.TryParse(value?.ToString(), out var result) ? result : null;
|
||||
}
|
||||
|
||||
private static void ValidateOllamaUrl(string url)
|
||||
{
|
||||
if (!Uri.TryCreate(url, UriKind.Absolute, out var uri))
|
||||
throw new InvalidOperationException("Invalid Ollama URL");
|
||||
|
||||
if (uri.Scheme != "http" && uri.Scheme != "https")
|
||||
throw new InvalidOperationException("Ollama URL must use http or https");
|
||||
|
||||
var host = uri.Host.ToLowerInvariant();
|
||||
|
||||
if (host == "localhost" || host == "host.docker.internal")
|
||||
return;
|
||||
|
||||
if (host == "metadata.google.internal")
|
||||
throw new InvalidOperationException("Ollama URL points to a blocked address");
|
||||
|
||||
if (System.Net.IPAddress.TryParse(host, out var ip))
|
||||
{
|
||||
if (ip.IsIPv4MappedToIPv6)
|
||||
ip = ip.MapToIPv4();
|
||||
|
||||
if (!IsAllowedIp(ip))
|
||||
throw new InvalidOperationException("Ollama URL must point to a local or private network address");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!host.Contains('.'))
|
||||
return;
|
||||
|
||||
throw new InvalidOperationException("Ollama URL must point to a local or private network address");
|
||||
}
|
||||
|
||||
private static bool IsAllowedIp(System.Net.IPAddress ip)
|
||||
{
|
||||
if (ip.AddressFamily != System.Net.Sockets.AddressFamily.InterNetwork)
|
||||
return System.Net.IPAddress.IsLoopback(ip);
|
||||
|
||||
var bytes = ip.GetAddressBytes();
|
||||
|
||||
if (bytes[0] == 0) return false;
|
||||
if (bytes[0] == 169 && bytes[1] == 254) return false;
|
||||
if (bytes[0] == 127) return true;
|
||||
if (bytes[0] == 10) return true;
|
||||
if (bytes[0] == 172 && bytes[1] >= 16 && bytes[1] <= 31) return true;
|
||||
if (bytes[0] == 192 && bytes[1] == 168) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user