From 4785bf5dfaf09a68674c9773e0f3c84a3b2d2c48 Mon Sep 17 00:00:00 2001 From: ZacharyZcR Date: Sat, 3 Oct 2026 21:02:21 +0800 Subject: [PATCH] fix(docker): retain stored SSH credential association (#1543) --- plugins/docker/src/backend/routes.ts | 12 ++++--- plugins/docker/tests/backend/routes.test.ts | 37 ++++++++++++++++++++- 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/plugins/docker/src/backend/routes.ts b/plugins/docker/src/backend/routes.ts index b031d6f99..672d241d8 100644 --- a/plugins/docker/src/backend/routes.ts +++ b/plugins/docker/src/backend/routes.ts @@ -210,11 +210,13 @@ export function registerRoutes(router: Router, { ctx, sessions, log }: Deps) { ), ); - const target: PluginSshHost = { - ...host, - id: numericHostId, - port: host.port || 22, - }; + const hasAuthOverride = [ + userProvidedPassword, + userProvidedSshKey, + userProvidedKeyPassword, + ].some((value) => typeof value === "string" && value.length > 0); + // Core associates the redacted host object with its stored credentials. + const target: PluginSshHost = hasAuthOverride ? { ...host } : host; if (typeof userProvidedPassword === "string" && userProvidedPassword) { target.password = userProvidedPassword; target.authType = "password"; diff --git a/plugins/docker/tests/backend/routes.test.ts b/plugins/docker/tests/backend/routes.test.ts index 5a76e4754..f317be6f3 100644 --- a/plugins/docker/tests/backend/routes.test.ts +++ b/plugins/docker/tests/backend/routes.test.ts @@ -3,7 +3,7 @@ import type { PluginSshConnectOptions, PluginSshHost, } from "@termix/plugin-sdk/backend"; -import { startServer, type TestServer } from "./server"; +import { startServer, sshHost, type TestServer } from "./server"; import { FakeClient } from "./fake-ssh"; let server: TestServer | null = null; @@ -76,6 +76,41 @@ describe("docker routes", () => { expect((await connect(server, "s1", 99)).status).toBe(404); }); + it("keeps the core-resolved host identity when using stored credentials", async () => { + server = await startServer(); + const host = sshHost(7, { authType: "key", password: undefined }); + const originalConnect = server.mock.ctx.ssh.connect; + server.mock.ctx.ssh.resolveHost = async () => host; + server.mock.ctx.ssh.connect = (async (target, options) => { + if (target !== host) + throw new Error("Stored credential association lost"); + return originalConnect(target, options); + }) as typeof originalConnect; + const response = await connect(server); + expect(response.status).toBe(200); + expect(response.body.success).toBe(true); + }); + + it("applies explicit authentication overrides without changing the resolved host", async () => { + server = await startServer(); + const host = sshHost(7, { authType: "key", password: undefined }); + server.mock.ctx.ssh.resolveHost = async () => host; + const response = await server.request("POST", "/ssh/connect", { + body: { + sessionId: "override", + hostId: 7, + userProvidedPassword: "replacement", + }, + }); + expect(response.status).toBe(200); + expect(server.mock.sshConnections.at(-1)?.host).toMatchObject({ + authType: "password", + password: "replacement", + }); + expect(host.authType).toBe("key"); + expect(host.password).toBeUndefined(); + }); + it("connects, validates and manages containers on the session", async () => { server = await startServer(); const connected = await connect(server);