Problem
backend/src/ledger_sync/api/main.py carefully builds a _cors_origins list from settings.cors_origins plus the parsed settings.frontend_url, then ignores it and passes allow_origins=["*"] to CORSMiddleware:
_cors_origins = list(settings.cors_origins)
if settings.frontend_url:
_parsed = urlparse(settings.frontend_url)
_frontend_origin = f"{_parsed.scheme}://{_parsed.netloc}"
if _frontend_origin and _frontend_origin not in _cors_origins:
_cors_origins.append(_frontend_origin)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # ← the careful build-up above is dead code
allow_credentials=False,
allow_methods=["*"],
allow_headers=["*"],
)
Why it matters
- The app is JWT-authenticated with the access token stored in
localStorage.
allow_origins=["*"] lets any website call this API.
allow_credentials=False blocks cookies, but a stolen JWT (e.g. XSS on any third-party origin that imports a compromised script) still works against the API because the script has access to localStorage.
_cors_origins is already being built — the fix is one line.
Suggested fix
Replace allow_origins=["*"] with allow_origins=_cors_origins.
Why this needs care (not a quick PR)
Tightening CORS without first verifying the production environment will break the live site. Before merging:
- Confirm
LEDGER_SYNC_FRONTEND_URL on Vercel is set to the canonical frontend URL (https://sagargupta.online/ledger-sync or similar).
- Confirm
LEDGER_SYNC_CORS_ORIGINS env var (JSON array) includes every domain the frontend is actually served from:
https://sagargupta.online
https://sagargupta16.github.io (GitHub Pages default)
- any other custom domains
- Test from each production origin after deploy.
- Consider whether
allow_credentials should be flipped on (currently False) once tokens move to httpOnly cookies — separate concern.
References
- File:
backend/src/ledger_sync/api/main.py (around lines 200-215)
- Found during a code audit, May 2026.
Acceptance criteria
Labels
security, backend, low-priority
Problem
backend/src/ledger_sync/api/main.pycarefully builds a_cors_originslist fromsettings.cors_originsplus the parsedsettings.frontend_url, then ignores it and passesallow_origins=["*"]toCORSMiddleware:Why it matters
localStorage.allow_origins=["*"]lets any website call this API.allow_credentials=Falseblocks cookies, but a stolen JWT (e.g. XSS on any third-party origin that imports a compromised script) still works against the API because the script has access tolocalStorage._cors_originsis already being built — the fix is one line.Suggested fix
Replace
allow_origins=["*"]withallow_origins=_cors_origins.Why this needs care (not a quick PR)
Tightening CORS without first verifying the production environment will break the live site. Before merging:
LEDGER_SYNC_FRONTEND_URLon Vercel is set to the canonical frontend URL (https://sagargupta.online/ledger-syncor similar).LEDGER_SYNC_CORS_ORIGINSenv var (JSON array) includes every domain the frontend is actually served from:https://sagargupta.onlinehttps://sagargupta16.github.io(GitHub Pages default)allow_credentialsshould be flipped on (currentlyFalse) once tokens move to httpOnly cookies — separate concern.References
backend/src/ledger_sync/api/main.py(around lines 200-215)Acceptance criteria
https://sagargupta.online/ledger-syncafter deploy.allow_credentialsand httpOnly-cookie auth should land in the same change or a follow-up.Labels
security, backend, low-priority