Skip to content

[Security] CSRF Token Exposed in JSON Response & Missing Content-Security-Policy Header #503

@prince-shakyaa

Description

@prince-shakyaa

[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:

  1. Content-Security-Policy (CSP) is missing - CSP is the primary modern defence
    against XSS. Without it, any injected <script> tag executes without restriction.

  2. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions