[Init] Initial commit - NetMesh terminal manager
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
This commit is contained in:
185
electron/bridges/proxyUtils.test.cjs
Normal file
185
electron/bridges/proxyUtils.test.cjs
Normal file
@@ -0,0 +1,185 @@
|
||||
const test = require("node:test");
|
||||
const assert = require("node:assert/strict");
|
||||
const { EventEmitter } = require("node:events");
|
||||
const { once } = require("node:events");
|
||||
const net = require("node:net");
|
||||
|
||||
const {
|
||||
createProxySocket,
|
||||
runWhenProxyConnectionReady,
|
||||
substituteProxyCommand,
|
||||
} = require("./proxyUtils.cjs");
|
||||
|
||||
test("substituteProxyCommand replaces OpenSSH-style host and port tokens for POSIX shells", () => {
|
||||
assert.equal(
|
||||
substituteProxyCommand(
|
||||
"cloudflared access ssh --hostname %h --port %p --literal %%",
|
||||
"server's.example.com",
|
||||
2222,
|
||||
{ platform: "linux" },
|
||||
),
|
||||
"cloudflared access ssh --hostname 'server'\\''s.example.com' --port '2222' --literal %",
|
||||
);
|
||||
});
|
||||
|
||||
test("substituteProxyCommand quotes safe OpenSSH-style host and port tokens for Windows cmd.exe", () => {
|
||||
assert.equal(
|
||||
substituteProxyCommand(
|
||||
"cloudflared access ssh --hostname %h --port %p --literal %%",
|
||||
"server.example.com",
|
||||
2222,
|
||||
{ platform: "win32" },
|
||||
),
|
||||
'cloudflared access ssh --hostname "server.example.com" --port "2222" --literal %',
|
||||
);
|
||||
});
|
||||
|
||||
test("substituteProxyCommand rejects unsafe Windows cmd.exe placeholder values", () => {
|
||||
assert.throws(
|
||||
() => substituteProxyCommand("proxy --host %h", 'server" & whoami & "', 22, { platform: "win32" }),
|
||||
/cannot be safely substituted/,
|
||||
);
|
||||
assert.throws(
|
||||
() => substituteProxyCommand("proxy --host %h", "%USERPROFILE%.example.com", 22, { platform: "win32" }),
|
||||
/cannot be safely substituted/,
|
||||
);
|
||||
});
|
||||
|
||||
test("createProxySocket exposes ProxyCommand stdout as socket data", async () => {
|
||||
const command = `${JSON.stringify(process.execPath)} -e ${JSON.stringify("process.stdout.write('hello')")}`;
|
||||
const socket = await createProxySocket(
|
||||
{ type: "command", host: "", port: 0, command },
|
||||
"server.example.com",
|
||||
22,
|
||||
);
|
||||
|
||||
const timeout = new Promise((_, reject) => {
|
||||
setTimeout(() => reject(new Error("Timed out waiting for ProxyCommand output")), 1000).unref();
|
||||
});
|
||||
|
||||
try {
|
||||
const data = await Promise.race([
|
||||
once(socket, "data").then(([chunk]) => chunk),
|
||||
timeout,
|
||||
]);
|
||||
|
||||
assert.equal(data.toString(), "hello");
|
||||
} finally {
|
||||
socket.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
test("createProxySocket times out a ProxyCommand that never produces a connection", async () => {
|
||||
const command = `${JSON.stringify(process.execPath)} -e ${JSON.stringify("setTimeout(() => {}, 1000)")}`;
|
||||
const socket = await createProxySocket(
|
||||
{ type: "command", host: "", port: 0, command },
|
||||
"server.example.com",
|
||||
22,
|
||||
{ timeoutMs: 20 },
|
||||
);
|
||||
|
||||
const [error] = await once(socket, "error");
|
||||
assert.match(error.message, /ProxyCommand connection timeout to server\.example\.com:22/);
|
||||
assert.equal(socket.destroyed, true);
|
||||
});
|
||||
|
||||
test("ProxyCommand reports network readiness only after it produces connection data", async () => {
|
||||
const command = `${JSON.stringify(process.execPath)} -e ${JSON.stringify(
|
||||
"setTimeout(() => process.stdout.write('ready'), 50)",
|
||||
)}`;
|
||||
const socket = await createProxySocket(
|
||||
{ type: "command", host: "", port: 0, command },
|
||||
"server.example.com",
|
||||
22,
|
||||
{ timeoutMs: 500 },
|
||||
);
|
||||
let ready = false;
|
||||
runWhenProxyConnectionReady(socket, () => {
|
||||
ready = true;
|
||||
});
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 15));
|
||||
assert.equal(ready, false);
|
||||
await once(socket, "data");
|
||||
assert.equal(ready, true);
|
||||
socket.destroy();
|
||||
});
|
||||
|
||||
test("ProxyCommand spawn restores launch-time proxy env under Direct mode", async () => {
|
||||
const {
|
||||
applyNodeProxyEnv,
|
||||
resetProxyEnvOwnershipForTests,
|
||||
} = require("./httpNetworkProxyBridge.cjs");
|
||||
resetProxyEnvOwnershipForTests();
|
||||
|
||||
const previous = {
|
||||
HTTP_PROXY: process.env.HTTP_PROXY,
|
||||
HTTPS_PROXY: process.env.HTTPS_PROXY,
|
||||
NO_PROXY: process.env.NO_PROXY,
|
||||
};
|
||||
process.env.HTTP_PROXY = "launch-proxy";
|
||||
process.env.HTTPS_PROXY = "launch-proxy";
|
||||
process.env.NO_PROXY = "localhost";
|
||||
applyNodeProxyEnv({ mode: "direct", url: "", bypass: "<local>" }, process.env);
|
||||
assert.equal(process.env.HTTP_PROXY, undefined);
|
||||
|
||||
const command = `${JSON.stringify(process.execPath)} -e ${JSON.stringify(
|
||||
"process.stdout.write(process.env.HTTP_PROXY || '')",
|
||||
)}`;
|
||||
const socket = await createProxySocket(
|
||||
{ type: "command", host: "", port: 0, command },
|
||||
"server.example.com",
|
||||
22,
|
||||
);
|
||||
|
||||
try {
|
||||
const data = await Promise.race([
|
||||
once(socket, "data").then(([chunk]) => chunk.toString()),
|
||||
new Promise((_, reject) => {
|
||||
setTimeout(() => reject(new Error("Timed out waiting for ProxyCommand env")), 1000).unref();
|
||||
}),
|
||||
]);
|
||||
assert.equal(data, "launch-proxy");
|
||||
} finally {
|
||||
socket.destroy();
|
||||
// Restore process.env ownership and prior values for later tests.
|
||||
applyNodeProxyEnv({ mode: "system", url: "", bypass: "<local>" }, process.env);
|
||||
for (const [key, value] of Object.entries(previous)) {
|
||||
if (value === undefined) delete process.env[key];
|
||||
else process.env[key] = value;
|
||||
}
|
||||
resetProxyEnvOwnershipForTests();
|
||||
}
|
||||
});
|
||||
|
||||
test("createProxySocket times out stalled HTTP proxy handshakes", async (t) => {
|
||||
const originalConnect = net.connect;
|
||||
let socketDestroyed = false;
|
||||
const keepAlive = setTimeout(() => {}, 100);
|
||||
t.after(() => {
|
||||
clearTimeout(keepAlive);
|
||||
net.connect = originalConnect;
|
||||
});
|
||||
net.connect = () => {
|
||||
const socket = new EventEmitter();
|
||||
socket.setNoDelay = () => socket;
|
||||
socket.destroy = () => {
|
||||
socketDestroyed = true;
|
||||
socket.emit("close");
|
||||
return socket;
|
||||
};
|
||||
socket.write = () => true;
|
||||
return socket;
|
||||
};
|
||||
|
||||
await assert.rejects(
|
||||
() => createProxySocket(
|
||||
{ type: "http", host: "127.0.0.1", port: 8080 },
|
||||
"server.example.com",
|
||||
22,
|
||||
{ timeoutMs: 20 },
|
||||
),
|
||||
/Proxy connection timeout to server\.example\.com:22/,
|
||||
);
|
||||
assert.equal(socketDestroyed, true);
|
||||
});
|
||||
Reference in New Issue
Block a user