From f85ef78beaab8023f6d2a6f25f73fc3addf24e6f Mon Sep 17 00:00:00 2001 From: atishj99 <141334503+cx-atish-jadhav@users.noreply.github.com> Date: Mon, 1 Jun 2026 19:16:50 +0530 Subject: [PATCH 1/2] Changes regarding sha verification --- .github/workflows/update-cli.yml | 53 +++++++++++++++++++++++++---- src/main/osinstaller/CxInstaller.ts | 40 ++++++++-------------- src/tests/CxInstallerTest.test.ts | 15 ++++---- 3 files changed, 70 insertions(+), 38 deletions(-) diff --git a/.github/workflows/update-cli.yml b/.github/workflows/update-cli.yml index baff8b33..fe4a9e22 100644 --- a/.github/workflows/update-cli.yml +++ b/.github/workflows/update-cli.yml @@ -40,15 +40,56 @@ jobs: run: | echo ${{ steps.checkmarx-ast-cli.outputs.release_tag }} > checkmarx-ast-cli.version - # Update the TypeScript file's cliDefaultVersion field - - name: Update cliDefaultVersion in CxInstaller.ts + # Download CLI binaries and generate checksums + - name: Download CLI and generate checksums if: steps.checkmarx-ast-cli.outputs.current_tag != steps.checkmarx-ast-cli.outputs.release_tag env: - NEW_CLI_VERSION: ${{ steps.checkmarx-ast-cli.outputs.release_tag }} + RELEASE_TAG: ${{ steps.checkmarx-ast-cli.outputs.release_tag }} run: | - FILE_PATH="src/main/osinstaller/CxInstaller.ts" - # Ensure that 'cliDefaultVersion' is updated correctly - sed -i "s/\(cliDefaultVersion = '\)[^']*\(';\)/\1${NEW_CLI_VERSION}\2/" $FILE_PATH + VERSION=$RELEASE_TAG + + # Initialize checksums object + CHECKSUMS='{}' + + # Platform configurations: platform_name,architecture,extension,os_platform + PLATFORMS=( + "windows,x64,zip,windows" + "darwin,x64,tar.gz,darwin" + "linux,x64,tar.gz,linux" + "linux,arm64,tar.gz,linux" + "linux,armv6,tar.gz,linux" + ) + + for PLATFORM_CONFIG in "${PLATFORMS[@]}"; do + IFS=',' read -r OS_TYPE ARCH EXT OS_PLATFORM <<< "$PLATFORM_CONFIG" + + KEY="${OS_PLATFORM}_${ARCH}" + URL="https://download.checkmarx.com/CxOne/CLI/${VERSION}/ast-cli_${VERSION}_${OS_PLATFORM}_${ARCH}.${EXT}" + + echo "Downloading checksum for ${KEY} from ${URL}..." + + # Download binary + TEMP_FILE="/tmp/ast-cli_${KEY}.${EXT}" + if curl -sL -o "$TEMP_FILE" "$URL"; then + # Calculate SHA-256 + CHECKSUM=$(sha256sum "$TEMP_FILE" | awk '{print $1}') + echo "✓ ${KEY}: ${CHECKSUM}" + + # Update checksums JSON + CHECKSUMS=$(echo "$CHECKSUMS" | jq --arg key "$KEY" --arg value "$CHECKSUM" '.[$key] = $value') + + # Cleanup + rm -f "$TEMP_FILE" + else + echo "✗ Failed to download ${KEY}" + exit 1 + fi + done + + # Write checksums to file + echo "$CHECKSUMS" | jq '.' > checkmarx-ast-cli.checksums + echo "Checksums updated:" + cat checkmarx-ast-cli.checksums # Create a Pull Request with the version changes - name: Create Pull Request diff --git a/src/main/osinstaller/CxInstaller.ts b/src/main/osinstaller/CxInstaller.ts index 9be6bdb5..6a0bffec 100644 --- a/src/main/osinstaller/CxInstaller.ts +++ b/src/main/osinstaller/CxInstaller.ts @@ -32,16 +32,6 @@ export class CxInstaller { linux: { platform: linuxOS, extension: 'tar.gz' } }; - // Default version and its paired SHA-256 checksums, keyed by "platform_architecture". - // Update both together when bumping the default CLI version. - private readonly cliDefaultVersion = '2.3.48'; - private static readonly cliDefaultChecksums: Record = { - 'windows_x64': '441ee8df46cc630ae000f8ba73925113aeed8c4d16cf274944aff3e7197e3470', - 'darwin_x64': 'b72f7e4ca14e5e56600b07d22c848a4b85e7c37d2e595424340cc699ea10006b', - 'linux_x64': 'eb3eb55add37f150188f5a8b36b2a659f902ad9569dcb7ee652531fe525022e2', - 'linux_arm64': '7df61689b3c2bbd4c27face5bdc0da97f63e4533229d6b53dd777f90d3904931', - 'linux_armv6': '99659f2e0804b197550efc6a9ddb6029babc980d32bdfeeb508199247ac95878' - }; constructor(platform: string, client: AstClient) { this.platform = platform as SupportedPlatforms; @@ -50,8 +40,7 @@ export class CxInstaller { } // Returns the CLI version and its platform-specific SHA-256 checksum. - // Tries the version file and checksums file first; falls back to the - // hardcoded defaults if the version file is absent or empty. + // Reads from version and checksums files. Throws CxError if version is absent or version file is empty. // Result is cached after the first read. async readASTCLIVersion(): Promise<{ version: string; checksum: string | null }> { if (this.cliVersion) { @@ -68,24 +57,23 @@ export class CxInstaller { const trimmed = content.trim(); if (trimmed) version = trimmed; } catch { - // version file absent — fall through to defaults + // version file absent — will throw error below } - let checksum: string | null; if (version === null) { - version = this.cliDefaultVersion; - checksum = CxInstaller.cliDefaultChecksums[key] ?? null; - } else { - try { - const content = await fsPromises.readFile(this.getChecksumsFilePath(), 'utf-8'); - checksum = (JSON.parse(content) as Record)[key] ?? null; - if (checksum === null) { - logger.warn(`No checksum found for ${key} in checksums file. Download will not be verified.`); - } - } catch { - logger.warn(`Checksums file not found. Download of version ${version} will not be verified.`); - checksum = null; + throw new CxError(`CLI version not found`); + } + + let checksum: string | null; + try { + const content = await fsPromises.readFile(this.getChecksumsFilePath(), 'utf-8'); + checksum = (JSON.parse(content) as Record)[key] ?? null; + if (checksum === null) { + logger.warn(`No checksum found for ${key} in checksums file. Download will not be verified.`); } + } catch { + logger.warn(`Checksums file not found. Download of version ${version} will not be verified.`); + checksum = null; } this.cliVersion = version; diff --git a/src/tests/CxInstallerTest.test.ts b/src/tests/CxInstallerTest.test.ts index 07954406..d1f80fe9 100644 --- a/src/tests/CxInstallerTest.test.ts +++ b/src/tests/CxInstallerTest.test.ts @@ -15,24 +15,27 @@ const cxInstallerWindows = new CxInstaller("win32", astClientInstance); describe("CxInstaller cases", () => { it('CxInstaller getDownloadURL Linux Successful case', async () => { + const testVersion = '2.3.48'; + jest.spyOn(cxInstallerLinux as any, 'readASTCLIVersion').mockResolvedValue({ version: testVersion, checksum: null }); const { url } = await cxInstallerLinux.getDownloadURL(); - const { version } = await cxInstallerLinux.readASTCLIVersion(); const architecture = getArchitecture(cxInstallerLinux.getPlatform()); - expect(url).toBe(`https://download.checkmarx.com/CxOne/CLI/${version}/ast-cli_${version}_linux_${architecture}.tar.gz`); + expect(url).toBe(`https://download.checkmarx.com/CxOne/CLI/${testVersion}/ast-cli_${testVersion}_linux_${architecture}.tar.gz`); }); it('CxInstaller getDownloadURL Mac Successful case', async () => { + const testVersion = '2.3.48'; + jest.spyOn(cxInstallerMac as any, 'readASTCLIVersion').mockResolvedValue({ version: testVersion, checksum: null }); const { url } = await cxInstallerMac.getDownloadURL(); - const { version } = await cxInstallerLinux.readASTCLIVersion(); const architecture = getArchitecture(cxInstallerMac.getPlatform()); - expect(url).toBe(`https://download.checkmarx.com/CxOne/CLI/${version}/ast-cli_${version}_darwin_${architecture}.tar.gz`); + expect(url).toBe(`https://download.checkmarx.com/CxOne/CLI/${testVersion}/ast-cli_${testVersion}_darwin_${architecture}.tar.gz`); }); it('CxInstaller getDownloadURL Windows Successful case', async () => { + const testVersion = '2.3.48'; + jest.spyOn(cxInstallerWindows as any, 'readASTCLIVersion').mockResolvedValue({ version: testVersion, checksum: null }); const { url } = await cxInstallerWindows.getDownloadURL(); - const { version } = await cxInstallerLinux.readASTCLIVersion(); const architecture = getArchitecture(cxInstallerWindows.getPlatform()); - expect(url).toBe(`https://download.checkmarx.com/CxOne/CLI/${version}/ast-cli_${version}_windows_${architecture}.zip`); + expect(url).toBe(`https://download.checkmarx.com/CxOne/CLI/${testVersion}/ast-cli_${testVersion}_windows_${architecture}.zip`); }); }); From d1c5f044ab33e5debc7cba42197dcbf6064b1326 Mon Sep 17 00:00:00 2001 From: atishj99 <141334503+cx-atish-jadhav@users.noreply.github.com> Date: Tue, 2 Jun 2026 15:45:38 +0530 Subject: [PATCH 2/2] Optimized the sha verification logic and test fixes --- src/main/osinstaller/CxInstaller.ts | 35 ++++++++++++++--------------- src/tests/CxInstallerTest.test.ts | 20 ++++++----------- 2 files changed, 24 insertions(+), 31 deletions(-) diff --git a/src/main/osinstaller/CxInstaller.ts b/src/main/osinstaller/CxInstaller.ts index 6a0bffec..20c24a7e 100644 --- a/src/main/osinstaller/CxInstaller.ts +++ b/src/main/osinstaller/CxInstaller.ts @@ -21,7 +21,7 @@ interface PlatformData { export class CxInstaller { private readonly platform: SupportedPlatforms; private cliVersion: string; - private cliChecksum: string | null; + private cliChecksum: string; private readonly resourceDirPath: string; private readonly installedCLIVersionFileName = 'cli-version'; private readonly client: AstClient; @@ -40,9 +40,9 @@ export class CxInstaller { } // Returns the CLI version and its platform-specific SHA-256 checksum. - // Reads from version and checksums files. Throws CxError if version is absent or version file is empty. + // Throws CxError if the version or checksums file is missing, empty, or has no entry for the current platform. // Result is cached after the first read. - async readASTCLIVersion(): Promise<{ version: string; checksum: string | null }> { + async readASTCLIVersion(): Promise<{ version: string; checksum: string }> { if (this.cliVersion) { return { version: this.cliVersion, checksum: this.cliChecksum }; } @@ -51,29 +51,28 @@ export class CxInstaller { const architecture = this.getArchitecture(); const key = `${platformData.platform}_${architecture}`; - let version: string | null = null; + let version: string; try { const content = await fsPromises.readFile(this.getVersionFilePath(), 'utf-8'); const trimmed = content.trim(); - if (trimmed) version = trimmed; - } catch { - // version file absent — will throw error below - } - - if (version === null) { - throw new CxError(`CLI version not found`); + if (!trimmed) throw new CxError('CLI version not found'); + version = trimmed; + } catch (error) { + if (error instanceof CxError) throw error; + throw new CxError('CLI version not found'); } - let checksum: string | null; + let checksum: string; try { const content = await fsPromises.readFile(this.getChecksumsFilePath(), 'utf-8'); - checksum = (JSON.parse(content) as Record)[key] ?? null; - if (checksum === null) { - logger.warn(`No checksum found for ${key} in checksums file. Download will not be verified.`); + const parsed = (JSON.parse(content) as Record); + if (!parsed[key]) { + throw new CxError(`No checksum found for ${key} in checksums file.`); } - } catch { - logger.warn(`Checksums file not found. Download of version ${version} will not be verified.`); - checksum = null; + checksum = parsed[key]; + } catch (error) { + if (error instanceof CxError) throw error; + throw new CxError(`Checksums file not found. Download of version ${version} will not be verified.`); } this.cliVersion = version; diff --git a/src/tests/CxInstallerTest.test.ts b/src/tests/CxInstallerTest.test.ts index d1f80fe9..505fe091 100644 --- a/src/tests/CxInstallerTest.test.ts +++ b/src/tests/CxInstallerTest.test.ts @@ -3,6 +3,7 @@ import { anyString, mock, instance, when, verify } from "ts-mockito"; import { AstClient } from "../main/client/AstClient"; import * as fs from "fs"; import * as crypto from "crypto"; +import * as path from "path"; // Mock AstClient and set up an instance from it const astClientMock = mock(AstClient); @@ -16,7 +17,7 @@ const cxInstallerWindows = new CxInstaller("win32", astClientInstance); describe("CxInstaller cases", () => { it('CxInstaller getDownloadURL Linux Successful case', async () => { const testVersion = '2.3.48'; - jest.spyOn(cxInstallerLinux as any, 'readASTCLIVersion').mockResolvedValue({ version: testVersion, checksum: null }); + jest.spyOn(cxInstallerLinux as any, 'readASTCLIVersion').mockResolvedValue({ version: testVersion, checksum: 'mock-checksum' }); const { url } = await cxInstallerLinux.getDownloadURL(); const architecture = getArchitecture(cxInstallerLinux.getPlatform()); expect(url).toBe(`https://download.checkmarx.com/CxOne/CLI/${testVersion}/ast-cli_${testVersion}_linux_${architecture}.tar.gz`); @@ -24,7 +25,7 @@ describe("CxInstaller cases", () => { it('CxInstaller getDownloadURL Mac Successful case', async () => { const testVersion = '2.3.48'; - jest.spyOn(cxInstallerMac as any, 'readASTCLIVersion').mockResolvedValue({ version: testVersion, checksum: null }); + jest.spyOn(cxInstallerMac as any, 'readASTCLIVersion').mockResolvedValue({ version: testVersion, checksum: 'mock-checksum' }); const { url } = await cxInstallerMac.getDownloadURL(); const architecture = getArchitecture(cxInstallerMac.getPlatform()); expect(url).toBe(`https://download.checkmarx.com/CxOne/CLI/${testVersion}/ast-cli_${testVersion}_darwin_${architecture}.tar.gz`); @@ -32,7 +33,7 @@ describe("CxInstaller cases", () => { it('CxInstaller getDownloadURL Windows Successful case', async () => { const testVersion = '2.3.48'; - jest.spyOn(cxInstallerWindows as any, 'readASTCLIVersion').mockResolvedValue({ version: testVersion, checksum: null }); + jest.spyOn(cxInstallerWindows as any, 'readASTCLIVersion').mockResolvedValue({ version: testVersion, checksum: 'mock-checksum' }); const { url } = await cxInstallerWindows.getDownloadURL(); const architecture = getArchitecture(cxInstallerWindows.getPlatform()); expect(url).toBe(`https://download.checkmarx.com/CxOne/CLI/${testVersion}/ast-cli_${testVersion}_windows_${architecture}.zip`); @@ -42,17 +43,17 @@ describe("CxInstaller cases", () => { describe("CxInstaller getExecutablePath cases", () => { it('CxInstaller getExecutablePath Linux Successful case', () => { const executablePath = cxInstallerLinux.getExecutablePath(); - expect(executablePath).toContain(`src/main/wrapper/resources/cx`); + expect(executablePath).toContain(path.join('src', 'main', 'wrapper', 'resources', 'cx')); }); it('CxInstaller getExecutablePath Mac Successful case', () => { const executablePath = cxInstallerMac.getExecutablePath(); - expect(executablePath).toContain(`src/main/wrapper/resources/cx`); + expect(executablePath).toContain(path.join('src', 'main', 'wrapper', 'resources', 'cx')); }); it('CxInstaller getExecutablePath Windows Successful case', () => { const executablePath = cxInstallerWindows.getExecutablePath(); - expect(executablePath).toContain(`src/main/wrapper/resources/cx.exe`); + expect(executablePath).toContain(path.join('src', 'main', 'wrapper', 'resources', 'cx.exe')); }); }); @@ -143,13 +144,6 @@ describe("CxInstaller checksum verification cases", () => { expect(exitSpy).toHaveBeenCalledWith(1); }); - it('CxInstaller null checksum skips verification', async () => { - jest.spyOn(localLinux as any, 'readASTCLIVersion').mockResolvedValue({ version: '9.9.99', checksum: null }); - when(localMock.downloadFile(anyString(), anyString())).thenResolve(); - await localLinux.downloadIfNotInstalledCLI(); - expect(exitSpy).not.toHaveBeenCalled(); - }); - it('CxInstaller CX_CLI_LOCATION skips checksum verification', async () => { process.env.CX_CLI_LOCATION = 'https://internal.example.com/cli'; jest.spyOn(localLinux as any, 'readASTCLIVersion').mockResolvedValue({ version: '2.3.48', checksum: 'irrelevant' });