diff --git a/.env.example b/.env.example index c29bc915..46d5886c 100644 --- a/.env.example +++ b/.env.example @@ -7,6 +7,7 @@ # --- Gateway (required) --- GOCLAW_GATEWAY_TOKEN= GOCLAW_ENCRYPTION_KEY= +POSTGRES_PASSWORD= # --- Database (only for non-Docker deployments) --- # Docker Compose auto-builds this from POSTGRES_USER/PASSWORD/DB. diff --git a/cmd/gateway.go b/cmd/gateway.go index bf892adf..bfed853e 100644 --- a/cmd/gateway.go +++ b/cmd/gateway.go @@ -1059,6 +1059,14 @@ func runGateway() { slog.Info("Tailscale enabled. Consider setting GOCLAW_HOST=127.0.0.1 for localhost-only + Tailscale access") } + // Security warnings + if strings.Contains(cfg.Database.PostgresDSN, ":goclaw@") { + slog.Warn("security.default_db_password: using default Postgres password — run ./prepare-env.sh to generate a strong one") + } + if len(cfg.Gateway.AllowedOrigins) == 0 { + slog.Warn("security.cors_open: no allowed_origins configured — all WebSocket origins accepted. Set gateway.allowed_origins for production") + } + if err := server.Start(ctx); err != nil { slog.Error("gateway error", "error", err) os.Exit(1) diff --git a/internal/gateway/server.go b/internal/gateway/server.go index 16c60379..5e0c57b4 100644 --- a/internal/gateway/server.go +++ b/internal/gateway/server.go @@ -312,17 +312,22 @@ func (s *Server) BuildMux() *http.ServeMux { // MCP bridge: expose GoClaw tools to Claude CLI via streamable-http. // Only listens on localhost (CLI runs on the same machine). - // Protected by gateway token when configured. - // Agent context (X-Agent-ID, X-User-ID) is injected from request headers. + // Protected by gateway token; disabled when no token is configured to + // prevent unauthenticated tool invocations if port is exposed. if s.tools != nil { - bridgeHandler := mcpbridge.NewBridgeServer(s.tools, "1.0.0", s.msgBus) - var handler http.Handler = bridgeContextMiddleware(s.cfg.Gateway.Token, bridgeHandler) if s.cfg.Gateway.Token != "" { - handler = tokenAuthMiddleware(s.cfg.Gateway.Token, handler) + bridgeHandler := mcpbridge.NewBridgeServer(s.tools, "1.0.0", s.msgBus) + handler := tokenAuthMiddleware(s.cfg.Gateway.Token, + bridgeContextMiddleware(s.cfg.Gateway.Token, bridgeHandler)) + mux.Handle("/mcp/bridge", handler) } else { - slog.Warn("security.mcp_bridge: no gateway token configured, MCP bridge tools are unauthenticated") + slog.Warn("security.mcp_bridge_disabled: no gateway token configured, MCP bridge is disabled") + mux.HandleFunc("/mcp/bridge", func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusForbidden) + _, _ = w.Write([]byte(`{"error":"mcp bridge disabled: set GOCLAW_GATEWAY_TOKEN to enable"}`)) + }) } - mux.Handle("/mcp/bridge", handler) } s.mux = mux