[Security] CSRF Token Exposed in JSON Response & Missing Content-Security-Policy Header
Description
Two security issues exist in the backend response layer that weaken CSRF protection
and leave the application without a modern XSS defence header.
Finding 1 - GET /api/session/status Leaks the Full CSRF Token in JSON
File: finbot/main.py · Lines 226–238
@app.get("/api/session/status")
async def session_status(session_context: SessionContext = Depends(get_session_context)):
return {
"session_id": session_context.session_id[:8] + "...",
"user_id": session_context.user_id,
"is_temporary": session_context.is_temporary,
"namespace": session_context.namespace,
"security_status": session_context.get_security_status(),
"csrf_token": session_context.csrf_token, # ← Full token in plain JSON
}
CSRF tokens are meant to be injected into HTML (meta tags or hidden form fields)
so that only same-origin JavaScript can read them. Returning the token from a GET
JSON endpoint completely bypasses this protection:
- Any XSS payload can call
fetch('/api/session/status') and immediately obtain the
current CSRF token
- The attacker can then include that token in a forged state-changing request
(e.g. fund transfer, settings change) and pass the CSRF check
Steps to reproduce:
# No authentication needed - works on a fresh temp session
curl -s http://localhost:8000/api/session/status | python3 -m json.tool
# Output includes: "csrf_token": "eyJ..."
Proposed fix: Remove csrf_token from the JSON response entirely. Inject it only
via HTML <meta> tags or hidden form fields in Jinja2 templates:
return {
"session_id": session_context.session_id[:8] + "...",
"user_id": session_context.user_id,
"is_temporary": session_context.is_temporary,
"namespace": session_context.namespace,
"security_status": session_context.get_security_status(),
# csrf_token intentionally omitted — injected via HTML meta tag only
}
Finding 2 - No Content-Security-Policy Header in Security Middleware
File: finbot/core/auth/middleware.py · Lines 148-152
def _add_security_headers(self, response: Response):
"""Add security headers"""
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["X-Frame-Options"] = "DENY"
response.headers["X-XSS-Protection"] = "1; mode=block" # ← Deprecated
# ← No Content-Security-Policy header
Two problems here:
-
Content-Security-Policy (CSP) is missing - CSP is the primary modern defence
against XSS. Without it, any injected <script> tag executes without restriction.
-
X-XSS-Protection is deprecated - Chrome 78+ removed support for this header.
It no longer provides any protection in modern browsers and can even introduce
vulnerabilities in older ones. It should be removed to reduce header noise.
Observed response headers (current):
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
⚠️ Content-Security-Policy is absent.
Proposed fix: Replace the deprecated header and add a baseline CSP:
def _add_security_headers(self, response: Response):
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["X-Frame-Options"] = "DENY"
response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
response.headers["Content-Security-Policy"] = (
"default-src 'self'; "
"script-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com; "
"style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; "
"font-src 'self' https://fonts.gstatic.com; "
"img-src 'self' data:; "
"connect-src 'self';"
)
Affected Files
| File |
Line |
Issue |
finbot/main.py |
237 |
csrf_token exposed in GET JSON response |
finbot/core/auth/middleware.py |
148–152 |
No CSP header; deprecated X-XSS-Protection present |
[Security] CSRF Token Exposed in JSON Response & Missing Content-Security-Policy Header
Description
Two security issues exist in the backend response layer that weaken CSRF protection
and leave the application without a modern XSS defence header.
Finding 1 -
GET /api/session/statusLeaks the Full CSRF Token in JSONFile:
finbot/main.py· Lines 226–238CSRF tokens are meant to be injected into HTML (meta tags or hidden form fields)
so that only same-origin JavaScript can read them. Returning the token from a GET
JSON endpoint completely bypasses this protection:
fetch('/api/session/status')and immediately obtain thecurrent CSRF token
(e.g. fund transfer, settings change) and pass the CSRF check
Steps to reproduce:
Proposed fix: Remove
csrf_tokenfrom the JSON response entirely. Inject it onlyvia HTML
<meta>tags or hidden form fields in Jinja2 templates:Finding 2 - No
Content-Security-PolicyHeader in Security MiddlewareFile:
finbot/core/auth/middleware.py· Lines 148-152Two problems here:
Content-Security-Policy(CSP) is missing - CSP is the primary modern defenceagainst XSS. Without it, any injected
<script>tag executes without restriction.X-XSS-Protectionis deprecated - Chrome 78+ removed support for this header.It no longer provides any protection in modern browsers and can even introduce
vulnerabilities in older ones. It should be removed to reduce header noise.
Observed response headers (current):
Proposed fix: Replace the deprecated header and add a baseline CSP:
Affected Files
finbot/main.pycsrf_tokenexposed in GET JSON responsefinbot/core/auth/middleware.pyX-XSS-Protectionpresent