@@ -3,6 +3,7 @@ import * as fs from 'fs';
33import * as path from 'path' ;
44import * as tar from 'tar' ;
55import * as unzipper from 'unzipper' ;
6+ import * as crypto from 'crypto' ;
67import { logger } from "../wrapper/loggerConfig" ;
78import { AstClient } from "../client/AstClient" ;
89import { CxError } from "../errors/CxError" ;
@@ -20,9 +21,9 @@ interface PlatformData {
2021export class CxInstaller {
2122 private readonly platform : SupportedPlatforms ;
2223 private cliVersion : string ;
24+ private cliChecksum : string | null ;
2325 private readonly resourceDirPath : string ;
2426 private readonly installedCLIVersionFileName = 'cli-version' ;
25- private readonly cliDefaultVersion = '2.3.48' ; // Update this with the latest version.
2627 private readonly client : AstClient ;
2728
2829 private static readonly PLATFORMS : Record < SupportedPlatforms , PlatformData > = {
@@ -31,14 +32,69 @@ export class CxInstaller {
3132 linux : { platform : linuxOS , extension : 'tar.gz' }
3233 } ;
3334
35+ // Default version and its paired SHA-256 checksums, keyed by "platform_architecture".
36+ // Update both together when bumping the default CLI version.
37+ private readonly cliDefaultVersion = '2.3.48' ;
38+ private static readonly cliDefaultChecksums : Record < string , string > = {
39+ 'windows_x64' : '441ee8df46cc630ae000f8ba73925113aeed8c4d16cf274944aff3e7197e3470' ,
40+ 'darwin_x64' : 'b72f7e4ca14e5e56600b07d22c848a4b85e7c37d2e595424340cc699ea10006b' ,
41+ 'linux_x64' : 'eb3eb55add37f150188f5a8b36b2a659f902ad9569dcb7ee652531fe525022e2' ,
42+ 'linux_arm64' : '7df61689b3c2bbd4c27face5bdc0da97f63e4533229d6b53dd777f90d3904931' ,
43+ 'linux_armv6' : '99659f2e0804b197550efc6a9ddb6029babc980d32bdfeeb508199247ac95878'
44+ } ;
45+
3446 constructor ( platform : string , client : AstClient ) {
3547 this . platform = platform as SupportedPlatforms ;
3648 this . resourceDirPath = path . join ( __dirname , '../wrapper/resources' ) ;
3749 this . client = client ;
3850 }
3951
40- async getDownloadURL ( ) : Promise < string > {
41- const cliVersion = await this . readASTCLIVersion ( ) ;
52+ // Returns the CLI version and its platform-specific SHA-256 checksum.
53+ // Tries the version file and checksums file first; falls back to the
54+ // hardcoded defaults if the version file is absent or empty.
55+ // Result is cached after the first read.
56+ async readASTCLIVersion ( ) : Promise < { version : string ; checksum : string | null } > {
57+ if ( this . cliVersion ) {
58+ return { version : this . cliVersion , checksum : this . cliChecksum } ;
59+ }
60+
61+ const platformData = CxInstaller . PLATFORMS [ this . platform ] ;
62+ const architecture = this . getArchitecture ( ) ;
63+ const key = `${ platformData . platform } _${ architecture } ` ;
64+
65+ let version : string | null = null ;
66+ try {
67+ const content = await fsPromises . readFile ( this . getVersionFilePath ( ) , 'utf-8' ) ;
68+ const trimmed = content . trim ( ) ;
69+ if ( trimmed ) version = trimmed ;
70+ } catch {
71+ // version file absent — fall through to defaults
72+ }
73+
74+ let checksum : string | null ;
75+ if ( version === null ) {
76+ version = this . cliDefaultVersion ;
77+ checksum = CxInstaller . cliDefaultChecksums [ key ] ?? null ;
78+ } else {
79+ try {
80+ const content = await fsPromises . readFile ( this . getChecksumsFilePath ( ) , 'utf-8' ) ;
81+ checksum = ( JSON . parse ( content ) as Record < string , string > ) [ key ] ?? null ;
82+ if ( checksum === null ) {
83+ logger . warn ( `No checksum found for ${ key } in checksums file. Download will not be verified.` ) ;
84+ }
85+ } catch {
86+ logger . warn ( `Checksums file not found. Download of version ${ version } will not be verified.` ) ;
87+ checksum = null ;
88+ }
89+ }
90+
91+ this . cliVersion = version ;
92+ this . cliChecksum = checksum ;
93+ return { version, checksum } ;
94+ }
95+
96+ async getDownloadURL ( ) : Promise < { url : string ; checksum : string | null } > {
97+ const { version, checksum } = await this . readASTCLIVersion ( ) ;
4298 const platformData = CxInstaller . PLATFORMS [ this . platform ] ;
4399
44100 if ( ! platformData ) {
@@ -49,10 +105,16 @@ export class CxInstaller {
49105
50106 const envVar = process . env . CX_CLI_LOCATION ;
51107 if ( envVar !== undefined ) {
52- return `${ envVar } /ast-cli_${ cliVersion } _${ platformData . platform } _${ architecture } .${ platformData . extension } ` ;
108+ return {
109+ url : `${ envVar } /ast-cli_${ version } _${ platformData . platform } _${ architecture } .${ platformData . extension } ` ,
110+ checksum : null
111+ } ;
53112 }
54-
55- return `https://download.checkmarx.com/CxOne/CLI/${ cliVersion } /ast-cli_${ cliVersion } _${ platformData . platform } _${ architecture } .${ platformData . extension } ` ;
113+
114+ return {
115+ url : `https://download.checkmarx.com/CxOne/CLI/${ version } /ast-cli_${ version } _${ platformData . platform } _${ architecture } .${ platformData . extension } ` ,
116+ checksum
117+ } ;
56118 }
57119
58120 private getArchitecture ( ) : string {
@@ -78,7 +140,7 @@ export class CxInstaller {
78140 public async downloadIfNotInstalledCLI ( ) : Promise < void > {
79141 try {
80142 await fs . promises . mkdir ( this . resourceDirPath , { recursive : true } ) ;
81- const cliVersion = await this . readASTCLIVersion ( ) ;
143+ const { version : cliVersion } = await this . readASTCLIVersion ( ) ;
82144
83145 if ( this . checkExecutableExists ( ) ) {
84146 const installedVersion = await this . readInstalledVersionFile ( this . resourceDirPath ) ;
@@ -89,11 +151,15 @@ export class CxInstaller {
89151 }
90152
91153 await this . cleanDirectoryContents ( this . resourceDirPath ) ;
92- const url = await this . getDownloadURL ( ) ;
154+ const { url, checksum } = await this . getDownloadURL ( ) ;
93155 const zipPath = path . join ( this . resourceDirPath , this . getCompressFolderName ( ) ) ;
94156
95157 await this . client . downloadFile ( url , zipPath ) ;
96158
159+ if ( checksum ) {
160+ await this . verifyChecksum ( zipPath , checksum ) ;
161+ }
162+
97163 await this . extractArchive ( zipPath , this . resourceDirPath ) ;
98164 await this . saveVersionFile ( this . resourceDirPath , cliVersion ) ;
99165
@@ -183,28 +249,36 @@ export class CxInstaller {
183249 return fs . existsSync ( this . getExecutablePath ( ) ) ;
184250 }
185251
186- async readASTCLIVersion ( ) : Promise < string > {
187- if ( this . cliVersion ) {
188- return this . cliVersion ;
189- }
190- try {
191- const versionFilePath = this . getVersionFilePath ( ) ;
192- const versionContent = await fsPromises . readFile ( versionFilePath , 'utf-8' ) ;
193- return versionContent . trim ( ) ;
194- } catch ( error ) {
195- logger . warn ( 'Error reading AST CLI version: ' + error . message ) ;
196- return this . cliDefaultVersion ;
197- }
198- }
199-
200252 private getVersionFilePath ( ) : string {
201253 return path . join ( __dirname , '../../../checkmarx-ast-cli.version' ) ;
202254 }
203255
256+ private getChecksumsFilePath ( ) : string {
257+ return path . join ( __dirname , '../../../checkmarx-ast-cli.checksums' ) ;
258+ }
259+
204260 private getCompressFolderName ( ) : string {
205261 return `ast-cli.${ this . platform === winOS ? 'zip' : 'tar.gz' } ` ;
206262 }
207-
263+
264+ private async verifyChecksum ( zipPath : string , expected : string ) : Promise < void > {
265+ const actual = await this . computeSHA256 ( zipPath ) ;
266+ if ( actual !== expected ) {
267+ throw new CxError ( `Checksum mismatch for ${ path . basename ( zipPath ) } : expected ${ expected } , got ${ actual } ` ) ;
268+ }
269+ logger . info ( `Checksum verified for ${ path . basename ( zipPath ) } .` ) ;
270+ }
271+
272+ private computeSHA256 ( filePath : string ) : Promise < string > {
273+ return new Promise ( ( resolve , reject ) => {
274+ const hash = crypto . createHash ( 'sha256' ) ;
275+ fs . createReadStream ( filePath )
276+ . on ( 'data' , chunk => hash . update ( chunk ) )
277+ . on ( 'end' , ( ) => resolve ( hash . digest ( 'hex' ) ) )
278+ . on ( 'error' , reject ) ;
279+ } ) ;
280+ }
281+
208282 public getPlatform ( ) : SupportedPlatforms {
209283 return this . platform ;
210284 }
0 commit comments