diff --git a/CHANGELOG.md b/CHANGELOG.md index 2459c6a3..5c3da108 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **Fixed:** `SettingsModal` Notifications tab is no longer hidden when a remote node is active — users can now configure Discord/Slack/Webhook channels directly on any remote node. The section header shows the remote node name and a "Remote" badge with a tooltip explaining that channels are saved on the remote instance. - **Fixed:** `StackAlertSheet` form error handling now surfaces the actual server error message (`err.error`) instead of a generic "Failed to add alert rule." string; console error logging added for all failure paths to aid debugging. - **Fixed:** `POST /api/alerts` now validates the request body with a Zod schema — rejects unknown metric/operator values, negative thresholds, and missing required fields with a structured 400 response instead of passing raw input to SQLite. +- **Fixed:** Remote node HTTP proxy (`remoteNodeProxy` in `index.ts`) now re-streams the parsed request body for POST/PUT/PATCH requests — `express.json()` drains the incoming stream into `req.body`, leaving the raw stream empty; the proxy then forwarded a request with a `Content-Length` header but zero body bytes, causing the remote Express server to stall waiting for the body indefinitely (the "Add Rule spins forever" bug on remote nodes). The `proxyReq` handler now writes `JSON.stringify(req.body)` with correct `Content-Type` and `Content-Length` headers before forwarding. - **Fixed:** WebSocket notification reconnect in `EditorLayout` upgraded to exponential backoff (1 s → 2 s → 4 s → 8 s → 16 s → 30 s max) instead of a flat 5-second retry; `ws.onerror` now logs the event rather than silently calling `close()`; cleanup correctly guards against closing a WebSocket that is already in CLOSING/CLOSED state, eliminating the "WebSocket is closed before the connection is established" console error on React StrictMode double-mount. - **Security:** Host Console and container exec WebSocket endpoints now reject `node_proxy` scoped JWT tokens with HTTP 403 — machine-to-machine proxy credentials can no longer be used to open interactive terminals. - **Security:** `stackParam` query parameter on `/api/system/host-console` is now validated against `path.resolve` + `startsWith(baseDir)` to prevent directory traversal when setting the PTY working directory. diff --git a/backend/src/index.ts b/backend/src/index.ts index 1a96535f..30fa14fc 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -370,6 +370,18 @@ const remoteNodeProxy = createProxyMiddleware({ const newQs = params.toString(); proxyReq.path = pathname + (newQs ? `?${newQs}` : ''); } + // Re-stream the request body for POST/PUT/PATCH requests. + // express.json() fully drains the incoming stream and stores the parsed + // object in req.body. http-proxy-middleware then pipes an already-consumed + // stream to the remote, which causes the remote server to stall waiting for + // body bytes that never arrive (the Content-Length header says N bytes but + // 0 are forwarded), hanging the request indefinitely. + if (req.body && Object.keys(req.body).length > 0) { + const bodyData = JSON.stringify(req.body); + proxyReq.setHeader('Content-Type', 'application/json'); + proxyReq.setHeader('Content-Length', Buffer.byteLength(bodyData)); + proxyReq.write(bodyData); + } }, error: (err, _req, proxyRes) => { console.error('[Proxy] Remote node error:', (err as Error).message);