diff --git a/src/panels/KaitoManagePanel.ts b/src/panels/KaitoManagePanel.ts index c0fa4238f..6d9733490 100644 --- a/src/panels/KaitoManagePanel.ts +++ b/src/panels/KaitoManagePanel.ts @@ -14,6 +14,17 @@ import { filterPodImage } from "../commands/utils/clusters"; import { ReadyAzureSessionProvider } from "../auth/types"; import { getAksClient } from "../commands/utils/arm"; import { l10n } from "vscode"; + +export function parseServicePort(output: string): number | undefined { + const value = output.trim(); + if (!/^[0-9]+$/.test(value)) { + return undefined; + } + + const port = Number(value); + return Number.isSafeInteger(port) && port >= 1 && port <= 65535 ? port : undefined; +} + export class KaitoManagePanel extends BasePanel<"kaitoManage"> { constructor(extensionUri: vscode.Uri) { super(extensionUri, "kaitoManage", { @@ -300,7 +311,14 @@ export class KaitoManagePanelDataProvider implements PanelDataProvider<"kaitoMan vscode.window.showErrorMessage(l10n.t(`Error getting port: {0}`, kubectlresult.error)); return undefined; } - return kubectlresult.result.stdout; + + const port = parseServicePort(kubectlresult.result.stdout); + if (port === undefined) { + vscode.window.showErrorMessage(l10n.t(`Service '{0}' returned an invalid port.`, serviceName)); + return undefined; + } + + return port; } // prompt the user for port number diff --git a/src/tests/suite/KaitoManagePanel.test.ts b/src/tests/suite/KaitoManagePanel.test.ts new file mode 100644 index 000000000..6df1a4dea --- /dev/null +++ b/src/tests/suite/KaitoManagePanel.test.ts @@ -0,0 +1,76 @@ +import * as assert from "assert"; +import * as sinon from "sinon"; +import * as vscode from "vscode"; +import * as k8s from "vscode-kubernetes-tools-api"; +import { ReadyAzureSessionProvider } from "../../auth/types"; +import { KaitoManagePanelDataProvider, parseServicePort } from "../../panels/KaitoManagePanel"; +import { MessageSink } from "../../webview-contract/messaging"; +import { ToWebViewMsgDef } from "../../webview-contract/webviewDefinitions/kaitoManage"; + +describe("Kaito manage panel", () => { + afterEach(() => { + sinon.restore(); + }); + + describe("parseServicePort", () => { + it("accepts decimal ports in the valid range", () => { + assert.strictEqual(parseServicePort("1"), 1); + assert.strictEqual(parseServicePort("8080"), 8080); + assert.strictEqual(parseServicePort("65535"), 65535); + assert.strictEqual(parseServicePort(" 8080\n"), 8080); + }); + + it("rejects shell syntax and non-numeric output", () => { + const payloads = [ + "8080&echo AKSC1RT_P3>P3.marker", + "8080; touch /tmp/pwned", + "8080 | whoami", + "8080$(whoami)", + "8080`whoami`", + '8080" & whoami', + "8080\nwhoami", + "", + "-1", + "1.5", + "65536", + "999999999999999999999999", + ]; + + for (const payload of payloads) { + assert.strictEqual(parseServicePort(payload), undefined, `should reject: ${payload}`); + } + }); + }); + + it("does not create a terminal when the cluster returns the reported service-port payload", async () => { + const invokeCommand = sinon.stub(); + invokeCommand.onFirstCall().resolves({ code: 0, stdout: "10.0.0.1", stderr: "" }); + invokeCommand.onSecondCall().resolves({ code: 0, stdout: "8080&echo AKSC1RT_P3>P3.marker", stderr: "" }); + + const kubectl = { + available: true, + api: { invokeCommand, portForward: sinon.stub() }, + } as k8s.APIAvailable; + const provider = new KaitoManagePanelDataProvider( + "cluster", + "subscription", + "resource-group", + kubectl, + "/tmp/kubeconfig", + [], + undefined, + {} as ReadyAzureSessionProvider, + ); + + sinon.stub(vscode.window, "showInputBox").resolves(undefined); + const showErrorMessage = sinon.stub(vscode.window, "showErrorMessage"); + const createTerminal = sinon.stub(vscode.window, "createTerminal"); + const handler = provider.getMessageHandler({} as MessageSink); + + handler.portForwardRequest({ modelName: "workspace-benign", namespace: "default" }, "portForwardRequest"); + await new Promise((resolve) => setImmediate(resolve)); + + assert.ok(showErrorMessage.calledOnceWith("Service 'workspace-benign' returned an invalid port.")); + assert.ok(createTerminal.notCalled, "hostile service-port output must not reach a terminal"); + }); +});