fix(security): stop granting every origin credentialed access to the API (#5)

`AURUM_CORS_ORIGINS` defaults to `*`, and the middleware was configured with
`allow_credentials=True`. Starlette does not answer that combination with a
literal `*` — it echoes back whatever `Origin` the caller sent and adds
`Access-Control-Allow-Credentials: true`, so any page open in the same
browser can call the instance, with the user's Basic Auth attached, and read
the response:

    GET /api/accounts   Origin: https://evil.example
    -> Access-Control-Allow-Origin: https://evil.example
       Access-Control-Allow-Credentials: true

That undercuts the README's advice that leaving Basic Auth off is fine while
the instance is only reachable from localhost: reachable-from-localhost
includes every website the user visits.

Nothing in the shipped setup is cross-origin. Compose serves the UI and the
API from the same nginx, and the Vite dev server proxies `/api`
(frontend/vite.config.ts), so neither deployment nor development needs CORS
at all. The default is therefore empty — no cross-origin access — and the
middleware is only installed when origins are configured deliberately.

An explicit `*` is still honoured for a genuinely public read-only setup, but
never together with credentials, which is what the spec intends `*` to mean
anyway.
This commit is contained in:
technosexual
2026-08-30 18:11:09 +03:00
committed by GitHub
parent 3013466c05
commit b045d378f0
5 changed files with 65 additions and 12 deletions

View File

@@ -40,13 +40,21 @@ async def lifespan(app: FastAPI):
app = FastAPI(title="Aurum API", version=APP_VERSION, lifespan=lifespan)
app.add_middleware(
CORSMiddleware,
allow_origins=settings.cors_origins_list,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# CORS stays off unless someone deliberately opens it, and credentials are
# only granted to a pinned list. Starlette answers a credentialed "*" by
# echoing back whatever Origin asked instead of a literal "*" — so the two
# together turn any page the user happens to have open into an authenticated
# client of their instance, which is exactly what the README's "fine if it's
# only reachable from localhost" advice assumes can't happen.
cors_origins = settings.cors_origins_list
if cors_origins:
app.add_middleware(
CORSMiddleware,
allow_origins=cors_origins,
allow_credentials="*" not in cors_origins,
allow_methods=["*"],
allow_headers=["*"],
)
app.include_router(dashboard.router, prefix="/api")
app.include_router(accounts.router, prefix="/api")