diff --git a/extensions/matrix/src/cli.test.ts b/extensions/matrix/src/cli.test.ts index 69d89bfab562..7f748d4160af 100644 --- a/extensions/matrix/src/cli.test.ts +++ b/extensions/matrix/src/cli.test.ts @@ -401,6 +401,20 @@ describe("matrix CLI verification commands", () => { expect(runMatrixSelfVerificationMock).not.toHaveBeenCalled(); }); + it("rejects non-positive Matrix self-verification timeout values", async () => { + const program = buildProgram(); + + await program.parseAsync(["matrix", "verify", "self", "--timeout-ms", "-1"], { + from: "user", + }); + + expect(process.exitCode).toBe(1); + expect(consoleErrorMock).toHaveBeenCalledWith( + "Self-verification failed: --timeout-ms must be a positive integer", + ); + expect(runMatrixSelfVerificationMock).not.toHaveBeenCalled(); + }); + it("requests Matrix self-verification and prints the follow-up SAS commands", async () => { requestMatrixVerificationMock.mockResolvedValue( mockMatrixVerificationSummary({ @@ -1076,6 +1090,34 @@ describe("matrix CLI verification commands", () => { ); }); + it("rejects negative Matrix initial sync limits at the CLI boundary", async () => { + const program = buildProgram(); + + await program.parseAsync( + [ + "matrix", + "account", + "add", + "--homeserver", + "https://matrix.example.org", + "--user-id", + "@ops:example.org", + "--password", + "secret", + "--initial-sync-limit", + "-1", + ], + { from: "user" }, + ); + + expect(process.exitCode).toBe(1); + expect(consoleErrorMock).toHaveBeenCalledWith( + "Account setup failed: --initial-sync-limit must be a non-negative integer", + ); + expect(matrixSetupValidateInputMock).not.toHaveBeenCalled(); + expect(matrixRuntimeReplaceConfigFileMock).not.toHaveBeenCalled(); + }); + it("enables E2EE and bootstraps verification from matrix account add", async () => { matrixRuntimeLoadConfigMock.mockReturnValue({ channels: {} }); matrixSetupApplyAccountConfigMock.mockImplementation( diff --git a/extensions/matrix/src/cli.ts b/extensions/matrix/src/cli.ts index 37dfd7ded3d7..f9abd5a3664f 100644 --- a/extensions/matrix/src/cli.ts +++ b/extensions/matrix/src/cli.ts @@ -232,7 +232,11 @@ function configureCliLogMode(verbose: boolean): void { setMatrixSdkConsoleLogging(verbose); } -function parseOptionalInt(value: string | undefined, fieldName: string): number | undefined { +function parseOptionalInt( + value: string | undefined, + fieldName: string, + opts: { min?: number } = {}, +): number | undefined { const trimmed = value?.trim(); if (!trimmed) { return undefined; @@ -244,6 +248,13 @@ function parseOptionalInt(value: string | undefined, fieldName: string): number if (parsed === undefined) { throw new Error(`${fieldName} must be an integer`); } + if (opts.min !== undefined && parsed < opts.min) { + throw new Error( + opts.min === 1 + ? `${fieldName} must be a positive integer` + : `${fieldName} must be a non-negative integer`, + ); + } return parsed; } @@ -289,6 +300,9 @@ async function addMatrixAccount(params: { useEnv?: boolean; enableEncryption?: boolean; }): Promise { + const initialSyncLimit = parseOptionalInt(params.initialSyncLimit, "--initial-sync-limit", { + min: 0, + }); const runtime = getMatrixRuntime(); const cfg = runtime.config.current() as CoreConfig; if (!matrixSetupAdapter.applyAccountConfig) { @@ -305,7 +319,7 @@ async function addMatrixAccount(params: { accessToken: params.accessToken, password: params.password, deviceName: params.deviceName, - initialSyncLimit: parseOptionalInt(params.initialSyncLimit, "--initial-sync-limit"), + initialSyncLimit, useEnv: params.useEnv === true, }; const accountId = @@ -1159,15 +1173,18 @@ async function runMatrixCliVerificationSummaryCommand(params: { async function runMatrixCliSelfVerificationCommand( options: MatrixCliSelfVerificationCommandOptions, ): Promise { - const { accountId, cfg } = resolveMatrixCliAccountContext(options.account); + let resolvedAccountId: string | undefined; await runMatrixCliCommand({ verbose: options.verbose === true, json: false, - run: async () => - await runMatrixSelfVerification({ + run: async () => { + const timeoutMs = parseOptionalInt(options.timeoutMs, "--timeout-ms", { min: 1 }); + const { accountId, cfg } = resolveMatrixCliAccountContext(options.account); + resolvedAccountId = accountId; + return await runMatrixSelfVerification({ accountId, cfg, - timeoutMs: parseOptionalInt(options.timeoutMs, "--timeout-ms"), + timeoutMs, onRequested: (summary) => { printAccountLabel(accountId); printMatrixVerificationSummary(summary); @@ -1184,7 +1201,8 @@ async function runMatrixCliSelfVerificationCommand( console.log("Compare this SAS with the other Matrix client."); }, confirmSas: async () => await promptMatrixVerificationSasMatch(), - }), + }); + }, onText: (summary, verbose) => { printMatrixVerificationSummary(summary); console.log(`Device verified by owner: ${summary.deviceOwnerVerified ? "yes" : "no"}`); @@ -1196,6 +1214,7 @@ async function runMatrixCliSelfVerificationCommand( console.log("Self-verification complete."); }, onTextError: () => { + const accountId = resolvedAccountId ?? options.account; printGuidance([ `Run ${formatMatrixCliCommand("verify self", accountId)} again and accept the request in another verified Matrix client for this account.`, `Then run ${formatMatrixCliCommand("verify status --verbose", accountId)} to confirm Cross-signing verified: yes and Signed by owner: yes.`,