diff --git a/src/lib/actions/sandbox/doctor.ts b/src/lib/actions/sandbox/doctor.ts index c0787a1c471..58338f96889 100644 --- a/src/lib/actions/sandbox/doctor.ts +++ b/src/lib/actions/sandbox/doctor.ts @@ -202,6 +202,20 @@ function dockerInspectGateway(containerName: string): DoctorCheck[] { 5000, ); if (inspect.status !== 0) { + const processCheck = captureHostCommand("pgrep", ["-af", "openshell-gateway"], 5000); + const portCheck = captureHostCommand("ss", ["-ltn", `( sport = :${GATEWAY_PORT} )`], 5000); + const processRunning = processCheck.status === 0 && processCheck.stdout.trim().length > 0; + const portListening = portCheck.status === 0 && portCheck.stdout.includes(`:${GATEWAY_PORT}`); + if (processRunning && portListening) { + checks.push({ + group: "Gateway", + label: "Local gateway process", + status: "ok", + detail: `openshell-gateway is running and listening on port ${GATEWAY_PORT}`, + }); + return checks; + } + checks.push({ group: "Gateway", label: "Docker container", diff --git a/src/lib/state/sandbox.ts b/src/lib/state/sandbox.ts index 518d43e307e..5415b9e350a 100644 --- a/src/lib/state/sandbox.ts +++ b/src/lib/state/sandbox.ts @@ -242,6 +242,7 @@ export function validateTarEntries(tarBuffer: Buffer, targetDir: string): TarVal encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 60000, + maxBuffer: 256 * 1024 * 1024, }); if (result.status !== 0) { @@ -376,6 +377,7 @@ export function rejectHardLinks(tarBuffer: Buffer): string[] { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 60000, + maxBuffer: 256 * 1024 * 1024, }); if (result.status !== 0) { diff --git a/test/security-sandbox-tar-traversal.test.ts b/test/security-sandbox-tar-traversal.test.ts index 54e20f9ba81..f60faa4bae9 100644 --- a/test/security-sandbox-tar-traversal.test.ts +++ b/test/security-sandbox-tar-traversal.test.ts @@ -583,6 +583,18 @@ describe("Fix: rejectHardLinks blocks hard-link entries at validation time", () expect(violations.length).toBe(0); }); + it("accepts a large archive whose verbose listing exceeds Node's default spawn buffer", async () => { + const { rejectHardLinks } = await loadSandboxState(); + const entries = Array.from({ length: 20_000 }, (_, index) => ({ + path: `workspace/file-${index.toString().padStart(5, "0")}.txt`, + content: "x", + })); + + const violations = rejectHardLinks(buildTar(entries)); + + expect(violations).toEqual([]); + }); + it("safeTarExtract rejects archive containing hard links", async () => { const { safeTarExtract } = await loadSandboxState(); const workDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-hardlink-"));