import { createHash } from "node:crypto"; import { execFileSync, spawn } from "node:child_process"; import { once } from "node:events"; import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { request } from "node:http"; import { resolve } from "node:path"; const host = "127.0.0.1"; const port = 43121; function sha256(value) { return createHash("sha256").update(value).digest("hex").toUpperCase(); } function firewallSnapshot() { return execFileSync( "netsh", ["advfirewall", "firewall", "show", "rule", "name=all", "dir=in"], { encoding: "utf8", windowsHide: true }, ); } function httpRequest(headers = {}) { return new Promise((resolveRequest, reject) => { const outgoing = request({ headers, host, method: "GET", path: "/", port }, (response) => { response.resume(); response.once("end", () => resolveRequest({ headers: response.headers, status: response.statusCode })); }); outgoing.once("error", reject); outgoing.end(); }); } async function waitUntilReady() { for (let attempt = 0; attempt < 40; attempt += 1) { try { const response = await httpRequest(); if (response.status === 200) return response; } catch { // The fixed listener may still be starting. } await new Promise((resolveWait) => setTimeout(resolveWait, 125)); } throw new Error("The fixed loopback listener did not become ready."); } const firewallBefore = firewallSnapshot(); const child = spawn(process.execPath, ["apps/api/dist/main.js"], { stdio: ["ignore", "pipe", "pipe"], windowsHide: true, }); let stderr = ""; child.stderr.on("data", (chunk) => { stderr += chunk.toString(); }); let rootResponse; let rejectedResponse; let listeners; try { rootResponse = await waitUntilReady(); rejectedResponse = await httpRequest({ host: "192.168.1.10:43121" }); const netstat = execFileSync("netstat", ["-ano", "-p", "tcp"], { encoding: "utf8", windowsHide: true }); const matchingListeners = netstat .split(/\r?\n/) .map((line) => line.trim().split(/\s+/)) .filter((tokens) => tokens[0]?.toUpperCase() === "TCP" && tokens.at(-1) === String(child.pid) && tokens[1]?.endsWith(`:${port}`)) .map((tokens) => ({ local_address: tokens[1], pid: child.pid })); listeners = [...new Map(matchingListeners.map((listener) => [listener.local_address, listener])).values()]; } finally { child.kill(); await Promise.race([ once(child, "exit"), new Promise((_, reject) => setTimeout(() => reject(new Error("API process did not stop.")), 5000)), ]); } if (stderr.trim()) throw new Error("The API wrote to stderr during loopback smoke."); const firewallAfter = firewallSnapshot(); const expectedListener = `${host}:${port}`; const socketResult = { expected: expectedListener, listeners, status: listeners.length > 0 && listeners.every(({ local_address }) => local_address === expectedListener) ? "passed" : "failed", }; const responseResult = { invalid_host: rejectedResponse, localhost: rootResponse, status: rootResponse.status === 200 && rejectedResponse.status === 426 ? "passed" : "failed", }; const firewallResult = { after_sha256: sha256(firewallAfter), before_sha256: sha256(firewallBefore), status: firewallAfter === firewallBefore ? "passed" : "failed", unchanged: firewallAfter === firewallBefore, }; const evidenceDirectory = process.env.DADA_EVIDENCE_DIR_BND; if (evidenceDirectory) { mkdirSync(evidenceDirectory, { recursive: true }); writeFileSync(resolve(evidenceDirectory, "socket-listeners.json"), `${JSON.stringify(socketResult, null, 2)}\n`); writeFileSync(resolve(evidenceDirectory, "response.json"), `${JSON.stringify(responseResult, null, 2)}\n`); writeFileSync(resolve(evidenceDirectory, "firewall-diff.json"), `${JSON.stringify(firewallResult, null, 2)}\n`); } const result = { firewall: firewallResult.status, responses: responseResult.status, sockets: socketResult.status, status: [firewallResult.status, responseResult.status, socketResult.status].every((status) => status === "passed") ? "passed" : "failed", }; console.log(JSON.stringify(result, null, 2)); if (result.status !== "passed") process.exit(1);