Skip to content

Commit 083fdd1

Browse files
committed
fix(desktop): harden startup against localhost port hijack
1 parent cd90abd commit 083fdd1

1 file changed

Lines changed: 16 additions & 5 deletions

File tree

apps/desktop/electron/desktop-runtime.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { app } from "electron";
2-
import { spawn } from "node:child_process";
2+
import { type ChildProcessWithoutNullStreams, spawn } from "node:child_process";
33
import crypto from "node:crypto";
44
import fs from "node:fs/promises";
55
import net from "node:net";
@@ -115,11 +115,22 @@ const createServerEnv = (port: number, dirs: DesktopDirs, serverUrl: string, lau
115115
DISABLE_RATE_LIMITING: "true",
116116
});
117117

118-
const waitForServer = async (serverUrl: string) => {
118+
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
119+
120+
const throwIfExited = (serverProcess: ChildProcessWithoutNullStreams) => {
121+
const exitStatus = serverProcess.exitCode ?? serverProcess.signalCode;
122+
if (exitStatus !== null) {
123+
throw new Error(`Zerobyte server exited before startup completed: ${exitStatus}`);
124+
}
125+
};
126+
127+
const waitForServer = async (serverUrl: string, serverProcess: ChildProcessWithoutNullStreams) => {
119128
const deadline = Date.now() + 60_000;
120129
let lastError = "";
121130

122131
while (Date.now() < deadline) {
132+
throwIfExited(serverProcess);
133+
123134
try {
124135
const response = await fetch(`${serverUrl}/api/healthcheck`, { signal: AbortSignal.timeout(5_000) });
125136
if (response.ok) {
@@ -130,7 +141,7 @@ const waitForServer = async (serverUrl: string) => {
130141
lastError = toMessage(error);
131142
}
132143

133-
await new Promise((resolve) => setTimeout(resolve, 500));
144+
await sleep(500);
134145
}
135146

136147
throw new Error(`Timed out waiting for Zerobyte server: ${lastError}`);
@@ -146,7 +157,7 @@ export const startDesktopRuntime = async (
146157
let stopped = false;
147158
let stopResticBridge: (() => void) | null = null;
148159
let command = "bunx";
149-
let args = ["--bun", "vite", "--host", "127.0.0.1", "--port", String(port)];
160+
let args = ["--bun", "vite", "--host", "127.0.0.1", "--port", String(port), "--strictPort"];
150161
let cwd = process.env.ZEROBYTE_REPO_ROOT ?? path.resolve(process.cwd(), "../..");
151162
const env = { ...createServerEnv(port, dirs, url, launchSecret), NODE_ENV: "development" };
152163

@@ -186,7 +197,7 @@ export const startDesktopRuntime = async (
186197
serverProcess.stderr.on("data", (data) => process.stderr.write(`[zerobyte] ${data}`));
187198
serverProcess.once("exit", handleServerExit);
188199

189-
await waitForServer(url);
200+
await waitForServer(url, serverProcess);
190201

191202
return {
192203
url,

0 commit comments

Comments
 (0)