Compare commits

...
5 changed files with 48 additions and 4 deletions

No files matched your search

+11
View File
@@ -772,3 +772,14 @@ test("getHostAddressForClipboard omits synthesized plugin hostnames", () => {
"",
);
});
test("CloudLinker skips the cwd probe without restricting generic Go servers", () => {
assert.equal(
shouldProbeSessionCwd({ isNetworkDevice: false, remoteSshVersion: "SSH-2.0-CloudLinker" }),
false,
);
assert.equal(
shouldProbeSessionCwd({ isNetworkDevice: false, remoteSshVersion: "SSH-2.0-Go" }),
true,
);
});
+5 -1
View File
@@ -13,16 +13,20 @@
// CLOUDBILITY-4.14 — PR #3458 reproduction note: SSH-2.0-CLOUDBILITY-4.14
// drops the interactive shell when another session channel opens. No other
// public packet was found; do not add aliases.
// CloudLinker — issue #3592: the vendor identifies a single-channel gateway
// and proposes this distinctive banner. Older "Go" banners cannot identify it
// safely; this compatibility entry requires the vendor banner change.
// JumpServer KoKo advertises Version "JumpServer" and multiplexes. Omit it.
const SINGLE_CHANNEL_SOFTWARE_TOKENS = Object.freeze([
"BHostSSH",
"TERM-SSHD",
"CLOUDBILITY",
"CLOUDLINKER",
]);
// #3146 also drops multi-character writes. That evidence is only for these
// two tokens, not for CLOUDBILITY.
// two tokens, not for CLOUDBILITY or CLOUDLINKER.
const CHUNKED_WRITE_SOFTWARE_TOKENS = Object.freeze([
"BHostSSH",
"TERM-SSHD",
+5 -1
View File
@@ -13,16 +13,20 @@
// CLOUDBILITY-4.14 — PR #3458 reproduction note: SSH-2.0-CLOUDBILITY-4.14
// drops the interactive shell when another session channel opens. No other
// public packet was found; do not add aliases.
// CloudLinker — issue #3592: the vendor identifies a single-channel gateway
// and proposes this distinctive banner. Older "Go" banners cannot identify it
// safely; this compatibility entry requires the vendor banner change.
// JumpServer KoKo advertises Version "JumpServer" and multiplexes. Omit it.
const SINGLE_CHANNEL_SOFTWARE_TOKENS = Object.freeze([
"BHostSSH",
"TERM-SSHD",
"CLOUDBILITY",
"CLOUDLINKER",
]);
// #3146 also drops multi-character writes. That evidence is only for these
// two tokens, not for CLOUDBILITY.
// two tokens, not for CLOUDBILITY or CLOUDLINKER.
const CHUNKED_WRITE_SOFTWARE_TOKENS = Object.freeze([
"BHostSSH",
"TERM-SSHD",
+10 -2
View File
@@ -8,7 +8,7 @@ const {
optionsForPeerSingleChannel,
} = require("./singleChannelSshBanner.shared.cjs");
test("single-channel banners match only the observed software tokens", () => {
test("single-channel banners match only the supported software tokens", () => {
for (const version of [
"BHostSSH_7.0",
"SSH-2.0-BHostSSH_7.0",
@@ -17,6 +17,9 @@ test("single-channel banners match only the observed software tokens", () => {
"term-sshd",
"CLOUDBILITY-4.14",
"SSH-2.0-CLOUDBILITY-4.14",
"CloudLinker",
"SSH-2.0-CloudLinker",
"cloudlinker-1.0",
]) {
assert.equal(remoteSoftwareRequiresSingleChannel(version), true, version);
}
@@ -42,6 +45,10 @@ test("single-channel banner match ignores OpenSSH, JumpServer, and lookalike tok
"CmdbHostSSH",
"BHost",
"notcloudbility",
"Go",
"SSH-2.0-Go",
"notcloudlinker",
"CloudLinkerProxy",
]) {
assert.equal(remoteSoftwareRequiresSingleChannel(version), false, String(version));
}
@@ -51,6 +58,7 @@ test("chunked-write limit stays on BHostSSH and TERM-SSHD", () => {
assert.equal(remoteDisallowsChunkedChannelWrite("BHostSSH_7.0"), true);
assert.equal(remoteDisallowsChunkedChannelWrite("SSH-2.0-TERM-SSHD"), true);
assert.equal(remoteDisallowsChunkedChannelWrite("CLOUDBILITY-4.14"), false);
assert.equal(remoteDisallowsChunkedChannelWrite("SSH-2.0-CloudLinker"), false);
assert.equal(remoteDisallowsChunkedChannelWrite("OpenSSH_9.6"), false);
assert.equal(remoteDisallowsChunkedChannelWrite("superterm-sshd"), false);
});
@@ -74,7 +82,7 @@ test("CommonJS and renderer modules agree on SSH banner behavior", async () => {
const browserModule = await import("./singleChannelSshBanner.shared.mjs");
const nodeModule = require("./singleChannelSshBanner.shared.cjs");
for (const version of [
"BHostSSH_7.0", "TERM-SSHD", "CLOUDBILITY-4.14", "OpenSSH_9.6", "JumpServer", "",
"BHostSSH_7.0", "TERM-SSHD", "CLOUDBILITY-4.14", "CloudLinker", "SSH-2.0-Go", "OpenSSH_9.6", "JumpServer", "",
]) {
assert.equal(browserModule.remoteSoftwareRequiresSingleChannel(version), nodeModule.remoteSoftwareRequiresSingleChannel(version));
assert.equal(browserModule.remoteDisallowsChunkedChannelWrite(version), nodeModule.remoteDisallowsChunkedChannelWrite(version));
@@ -6,6 +6,7 @@ const os = require("node:os");
const path = require("node:path");
const { createSessionOpsApi, decodeLsofFileName } = require("./sessionOps.cjs");
const { optionsForPeerSingleChannel } = require("../../../domain/singleChannelSshBanner.shared.cjs");
function quoteShellArg(value) {
return "'" + String(value).replace(/'/g, "'\\''") + "'";
@@ -880,3 +881,19 @@ test("listSessionDir skips extra exec when singleChannelSsh is set", async () =>
assert.deepEqual(result.entries, []);
assert.equal(execCalls, 0);
});
test("getSessionPwd skips exec after the CloudLinker banner stamps the runtime capability", async () => {
let execCalls = 0;
const api = makeApi({
...optionsForPeerSingleChannel({}, { _remoteVer: "CloudLinker" }),
remoteSshVersion: "CloudLinker",
stream: {},
conn: { exec() { execCalls += 1; } },
});
const result = await api.getSessionPwd(null, { sessionId: "session-1" });
assert.equal(result.success, false);
assert.match(result.error, /extra exec channels/);
assert.equal(execCalls, 0);
});