forked from enderofwings/NexusOS
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:
+25
-1
@@ -143,6 +143,29 @@ class Settings:
|
||||
"ollama_timeout": self.ollama_timeout,
|
||||
}
|
||||
|
||||
# --- local-access allowlists (shared by the backend + memory FastAPI apps) ---
|
||||
# The REST APIs are unauthenticated, so they are meant to be reached only from
|
||||
# this machine. Two independent browser-side defenses depend on these lists:
|
||||
# * ALLOWED_ORIGINS drives CORS — blocks a malicious page from *reading*
|
||||
# responses cross-origin (was previously "*", which let any site read them).
|
||||
# * ALLOWED_HOSTS drives TrustedHostMiddleware — rejects a foreign Host header,
|
||||
# which is what stops DNS-rebinding (same-origin from the browser's view, so
|
||||
# CORS can't help there).
|
||||
# Both accept a comma-separated env override for the intentional-LAN case, to be
|
||||
# paired with real auth. NEXUS_ALLOWED_HOSTS=* disables the Host check.
|
||||
def _csv_env(name: str, default: list) -> list:
|
||||
raw = os.getenv(name, "").strip()
|
||||
return [p.strip() for p in raw.split(",") if p.strip()] if raw else list(default)
|
||||
|
||||
_LOCAL_HOSTS = ["localhost", "127.0.0.1", "[::1]", "::1", "testserver"]
|
||||
_LOCAL_ORIGINS = [
|
||||
f"http://{h}:{p}"
|
||||
for h in ("localhost", "127.0.0.1")
|
||||
for p in (8000, 8001, 5173)
|
||||
]
|
||||
ALLOWED_HOSTS = _csv_env("NEXUS_ALLOWED_HOSTS", _LOCAL_HOSTS)
|
||||
ALLOWED_ORIGINS = _csv_env("NEXUS_ALLOWED_ORIGINS", _LOCAL_ORIGINS)
|
||||
|
||||
# exported instance
|
||||
settings = Settings()
|
||||
|
||||
@@ -152,7 +175,8 @@ __all__ = ["Settings", "settings", "path", "VERSION",
|
||||
"PROJECT_ROOT", "DATA_DIR", "MODELS_DIR", "RUNTIME_DIR",
|
||||
"MEMORY_DIR", "LOGS_DIR", "PLAYBOOK_DIR", "UPLOADS_DIR",
|
||||
"EXPORTS_DIR", "MEMORY_DB",
|
||||
"BACKEND_LOG", "OLLAMA_LOG", "CHAT_LOG"]
|
||||
"BACKEND_LOG", "OLLAMA_LOG", "CHAT_LOG",
|
||||
"ALLOWED_HOSTS", "ALLOWED_ORIGINS"]
|
||||
|
||||
# --- quick runtime sanity check when run directly (no side effects on import) ---
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user