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:
@@ -4,6 +4,13 @@ server {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
# Security headers
|
||||
add_header X-Frame-Options "DENY" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
add_header X-Permitted-Cross-Domain-Policies "none" always;
|
||||
add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://cdn.plaid.com; connect-src 'self' wss:; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:;" always;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
@@ -237,7 +237,7 @@ async function loadConversations() {
|
||||
conversations.value = data
|
||||
conversationsLoaded.value = true
|
||||
} catch (err) {
|
||||
console.error('Failed to load conversations', err)
|
||||
console.error('Failed to load conversations:', err instanceof Error ? err.message : 'Unknown error')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,7 +248,7 @@ async function loadConversation(id: string) {
|
||||
messages.value = data.messages
|
||||
await scrollToBottom()
|
||||
} catch (err) {
|
||||
console.error('Failed to load conversation', err)
|
||||
console.error('Failed to load conversation:', err instanceof Error ? err.message : 'Unknown error')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -322,7 +322,7 @@ async function deleteConversation(id: string) {
|
||||
startNewChat()
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to delete conversation', err)
|
||||
console.error('Failed to delete conversation:', err instanceof Error ? err.message : 'Unknown error')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -162,7 +162,7 @@ async function loadConversations() {
|
||||
const { data } = await chatApi.getConversations()
|
||||
conversations.value = data
|
||||
} catch (err) {
|
||||
console.error('Failed to load conversations', err)
|
||||
console.error('Failed to load conversations:', err instanceof Error ? err.message : 'Unknown error')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,7 +173,7 @@ async function loadConversation(id: string) {
|
||||
messages.value = data.messages
|
||||
await scrollToBottom()
|
||||
} catch (err) {
|
||||
console.error('Failed to load conversation', err)
|
||||
console.error('Failed to load conversation:', err instanceof Error ? err.message : 'Unknown error')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,7 +244,7 @@ async function deleteConversation(id: string) {
|
||||
startNewChat()
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to delete conversation', err)
|
||||
console.error('Failed to delete conversation:', err instanceof Error ? err.message : 'Unknown error')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -553,7 +553,7 @@ async function fetchReport<T>(request: Promise<{ data: T }>): Promise<T | null>
|
||||
} catch (err: any) {
|
||||
const status = err?.response?.status
|
||||
if (status && status >= 500) {
|
||||
console.error('Report request failed:', err)
|
||||
console.error('Report request failed:', err instanceof Error ? err.message : 'Unknown error')
|
||||
return null
|
||||
}
|
||||
// 4xx (including 404) treated as no data
|
||||
|
||||
Reference in New Issue
Block a user