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:
+7
-1
@@ -112,6 +112,12 @@ class Service:
|
||||
return self._argv() if callable(self._argv) else self._argv
|
||||
|
||||
|
||||
# Bind loopback by default: the backend/memory REST APIs are unauthenticated, so
|
||||
# binding 0.0.0.0 handed the full admin+data plane to any host on the LAN. Set
|
||||
# NEXUS_BIND_HOST=0.0.0.0 to opt into LAN exposure once real auth is in place.
|
||||
BIND_HOST = os.environ.get("NEXUS_BIND_HOST", "127.0.0.1")
|
||||
|
||||
|
||||
def _uvicorn(app: str, port: int):
|
||||
# No --reload. It is a dev-loop flag: uvicorn's reloader runs a supervisor
|
||||
# that spawns the real server as a CHILD, so every service became two
|
||||
@@ -120,7 +126,7 @@ def _uvicorn(app: str, port: int):
|
||||
# terminals for what should have been a silent start. launch_nexus.sh still
|
||||
# passes --reload for the Linux dev loop, where a visible console is the
|
||||
# point; this launcher is the one users run.
|
||||
return [str(PYTHON), "-m", "uvicorn", app, "--host", "0.0.0.0",
|
||||
return [str(PYTHON), "-m", "uvicorn", app, "--host", BIND_HOST,
|
||||
"--port", str(port)]
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user