feat(security): bind services to loopback with Host + CORS allowlists
The Synapse backend and memory service bound 0.0.0.0 with wildcard CORS and no auth, exposing the full unauthenticated admin/data API to the LAN. Default the uvicorn bind to 127.0.0.1 (NEXUS_BIND_HOST override), scope CORS to known local origins instead of "*", and add TrustedHostMiddleware to reject foreign Host headers (which defeats DNS-rebinding, something same-origin CORS cannot stop). NEXUS_ALLOWED_HOSTS / NEXUS_ALLOWED_ORIGINS allow opt-in LAN exposure, intended to be paired with real authentication. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+14
-6
@@ -14,9 +14,10 @@ import platform as _platform
|
||||
from pathlib import Path
|
||||
from fastapi import FastAPI, HTTPException, Body
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from starlette.middleware.trustedhost import TrustedHostMiddleware
|
||||
from fastapi.responses import StreamingResponse, FileResponse
|
||||
|
||||
from .nexus_config import settings, VERSION, DEFAULT_CHAT_MODEL
|
||||
from .nexus_config import settings, VERSION, DEFAULT_CHAT_MODEL, ALLOWED_HOSTS, ALLOWED_ORIGINS
|
||||
from .chat import generate_chat_response, stream_chat_response, _synapse_trace
|
||||
from . import chat as _chat
|
||||
from .ollama_manager import initialize_ollama, initialize_ollama_async, get_ollama_manager
|
||||
@@ -179,13 +180,20 @@ app = FastAPI(title="Synapse Backend", version=VERSION)
|
||||
# Alias for startup scripts
|
||||
sio_app = app
|
||||
|
||||
# --- CORS ---
|
||||
# allow_credentials=True is incompatible with allow_origins=["*"] — browsers
|
||||
# reject such responses. Since this is a local-only service with no cookies/auth,
|
||||
# wildcard origins without credentials is correct.
|
||||
# --- Local-access guard ---
|
||||
# These APIs are unauthenticated and meant for this machine only. Two layers:
|
||||
# 1. TrustedHostMiddleware rejects a foreign Host header, which is what defeats
|
||||
# DNS-rebinding — the browser treats a rebound attacker domain as
|
||||
# same-origin, so CORS alone can't stop it.
|
||||
# 2. CORS is scoped to known local origins (not "*"), so a malicious page can't
|
||||
# read responses cross-origin from the victim's browser.
|
||||
# NEXUS_ALLOWED_HOSTS=* / a custom NEXUS_ALLOWED_ORIGINS opts into LAN exposure,
|
||||
# and should only be used once real authentication is in front of these apps.
|
||||
if "*" not in ALLOWED_HOSTS:
|
||||
app.add_middleware(TrustedHostMiddleware, allowed_hosts=ALLOWED_HOSTS)
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_origins=ALLOWED_ORIGINS,
|
||||
allow_credentials=False,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
|
||||
Reference in New Issue
Block a user