diff --git a/src/main/secrets/CxSecrets.ts b/src/main/secrets/CxSecrets.ts new file mode 100644 index 00000000..3c1dd557 --- /dev/null +++ b/src/main/secrets/CxSecrets.ts @@ -0,0 +1,44 @@ +export default class CxSecretsResult { + title: string; + description: string; + filepath: string; + severity: string; + locations: { line: number, startIndex: number, endIndex: number }[]; + + static parseResult(resultObject: any): CxSecretsResult[] { + let secretsResults: CxSecretsResult[] = []; + if (resultObject instanceof Array) { + secretsResults = resultObject.map((member: any) => { + const secretsResult = new CxSecretsResult(); + secretsResult.title = member.Title; + secretsResult.description = member.Description; + secretsResult.filepath = member.FilePath; + secretsResult.severity = member.Severity; + secretsResult.locations = Array.isArray(member.Locations) + ? member.Locations.map((l: any) => ({ + line: l.Line, + startIndex: l.StartIndex, + endIndex: l.EndIndex, + })) + : []; + return secretsResult; + }); + } else { + const secretsResult = new CxSecretsResult(); + secretsResult.title = resultObject.Title; + secretsResult.description = resultObject.Description; + secretsResult.severity = resultObject.Severity; + secretsResult.filepath = resultObject.FilePath; + secretsResult.filepath = resultObject.FilePath; + secretsResult.locations = Array.isArray(resultObject.Locations) + ? resultObject.Locations.map((l: any) => ({ + line: l.Line, + startIndex: l.StartIndex, + endIndex: l.EndIndex, + })) + : []; + secretsResults.push(secretsResult); + } + return secretsResults; + } +} diff --git a/src/main/wrapper/CxConstants.ts b/src/main/wrapper/CxConstants.ts index 487723ff..9174ea4f 100644 --- a/src/main/wrapper/CxConstants.ts +++ b/src/main/wrapper/CxConstants.ts @@ -73,6 +73,7 @@ export enum CxConstants { SOURCE_FILE = "--file-source", ASCA_UPDATE_VERSION = "--asca-latest-version", CMD_OSS = "oss-realtime", + CMD_SECRETS = "secrets-realtime", PROJECT_ID = "--project-id", SIMILARITY_ID = "--similarity-id", QUERY_ID = "--query-id", @@ -89,6 +90,7 @@ export enum CxConstants { SCAN_TYPE = "CxScan", SCAN_ASCA = "CxAsca", SCAN_OSS = "CxOss", + SCAN_SECRETS = "CxSecrets", PROJECT_TYPE = "CxProject", PREDICATE_TYPE = "CxPredicate", CODE_BASHING_TYPE = "CxCodeBashing", diff --git a/src/main/wrapper/CxWrapper.ts b/src/main/wrapper/CxWrapper.ts index b8c645b2..2ab25729 100644 --- a/src/main/wrapper/CxWrapper.ts +++ b/src/main/wrapper/CxWrapper.ts @@ -156,6 +156,13 @@ export class CxWrapper { return await exec.executeCommands(this.config.pathToExecutable, commands, CxConstants.SCAN_OSS); } + async secretsScanResults(sourceFile: string): Promise { + const commands: string[] = [CxConstants.CMD_SCAN, CxConstants.CMD_SECRETS, CxConstants.SOURCE, sourceFile]; + commands.push(...this.initializeCommands(false)); + const exec = new ExecutionService(); + return await exec.executeCommands(this.config.pathToExecutable, commands, CxConstants.SCAN_SECRETS); + } + async scanCancel(id: string): Promise { const commands: string[] = [CxConstants.CMD_SCAN, CxConstants.SUB_CMD_CANCEL, CxConstants.SCAN_ID, id]; commands.push(...this.initializeCommands(false)); diff --git a/src/main/wrapper/ExecutionService.ts b/src/main/wrapper/ExecutionService.ts index f16e1bfd..a9ab197b 100644 --- a/src/main/wrapper/ExecutionService.ts +++ b/src/main/wrapper/ExecutionService.ts @@ -1,6 +1,6 @@ import {CxCommandOutput} from "./CxCommandOutput"; import CxScan from "../scan/CxScan"; -import { logger } from "./loggerConfig"; +import {logger} from "./loggerConfig"; import * as fs from "fs" import * as os from "os"; import * as path from "path"; @@ -24,6 +24,7 @@ import CxChat from "../chat/CxChat"; import CxMask from "../mask/CxMask"; import CxAsca from "../asca/CxAsca"; import CxOssResult from "../oss/CxOss"; +import CxSecretsResult from "../secrets/CxSecrets"; let skipValue = false; const fileSourceFlag = "--file-source" @@ -44,13 +45,13 @@ function transformation(commands: string[]): string[] { return result; } -function transform(n:string) { +function transform(n: string) { // in case the file name looks like this: 'var express require('express');.js' we won't delete "'" if (skipValue) { skipValue = false; let r = ""; - if(n) r = n.replace(/["]/g, "").replace("/[, ]/g",","); + if (n) r = n.replace(/["]/g, "").replace("/[, ]/g", ","); return r; } // If the current string is "--file-source", set the flag @@ -59,17 +60,17 @@ function transform(n:string) { } let r = ""; - if(n) r = n.replace(/["']/g, "").replace("/[, ]/g",","); + if (n) r = n.replace(/["']/g, "").replace("/[, ]/g", ","); return r; } export class ExecutionService { - private fsObject : any = undefined + private fsObject: any = undefined - executeCommands(pathToExecutable: string, commands: string[], output? : string ): Promise { - return (new Promise( (resolve, reject)=> { + executeCommands(pathToExecutable: string, commands: string[], output?: string): Promise { + return (new Promise((resolve, reject) => { let stderr = ""; - let stdout =""; + let stdout = ""; this.fsObject = spawner.spawn(pathToExecutable, transformation(commands)); this.fsObject.on('error', (data: { toString: () => string; }) => { @@ -79,33 +80,33 @@ export class ExecutionService { } reject() }); - this.fsObject.on('exit',(code: number) => { + this.fsObject.on('exit', (code: number) => { logger.info("Exit code received from AST-CLI: " + code); - if(code==1){ + if (code == 1) { stderr = stdout } resolve(ExecutionService.onCloseCommand(code, stderr, stdout, output)); }); this.fsObject.stdout.on('data', (data: { toString: () => string; }) => { if (data) { - logger.info(data.toString().replace('\n', '')); - stdout += data.toString(); + logger.info(data.toString().replace('\n', '')); + stdout += data.toString(); } }); this.fsObject.stderr.on('data', (data: { toString: () => string; }) => { - if (data) { - logger.error(data.toString().replace('\n', '')); - stderr += data.toString(); - } + if (data) { + logger.error(data.toString().replace('\n', '')); + stderr += data.toString(); + } }); })); } - executeKicsCommands(pathToExecutable: string, commands: string[], output? : string ): [Promise,any] { - return [new Promise( (resolve, reject)=> { + executeKicsCommands(pathToExecutable: string, commands: string[], output?: string): [Promise, any] { + return [new Promise((resolve, reject) => { let stderr = ""; - let stdout =""; + let stdout = ""; this.fsObject = spawner.spawn(pathToExecutable, transformation(commands)); this.fsObject.on('error', (data: { toString: () => string; }) => { @@ -115,9 +116,9 @@ export class ExecutionService { } reject() }); - this.fsObject.on('exit',(code: number) => { + this.fsObject.on('exit', (code: number) => { logger.info("Exit code received from AST-CLI: " + code); - if(code==1){ + if (code == 1) { stderr = stdout } resolve(ExecutionService.onCloseCommand(code, stderr, stdout, output)); @@ -137,10 +138,10 @@ export class ExecutionService { }), this.fsObject]; } - executeMapTenantOutputCommands(pathToExecutable: string, commands: string[]): Promise> { - return (new Promise( (resolve, reject)=> { + executeMapTenantOutputCommands(pathToExecutable: string, commands: string[]): Promise> { + return (new Promise((resolve, reject) => { let stderr = ""; - let stdout =""; + let stdout = ""; this.fsObject = spawner.spawn(pathToExecutable, transformation(commands)); this.fsObject.on('error', (data: { toString: () => string; }) => { @@ -150,9 +151,9 @@ export class ExecutionService { } reject() }); - this.fsObject.on('exit',(code: number) => { + this.fsObject.on('exit', (code: number) => { logger.info("Exit code received from AST-CLI: " + code); - if(code==1){ + if (code == 1) { stderr = stdout reject(stderr) } @@ -173,12 +174,12 @@ export class ExecutionService { })); } - private static onCloseMapTenantOutputCommand(code: number, stderr: string, stdout: string): Map { - const result = new Map(); + private static onCloseMapTenantOutputCommand(code: number, stderr: string, stdout: string): Map { + const result = new Map(); if (code == 0) { const tenantSettingsList = stdout.split('\n'); tenantSettingsList.forEach(tenantSetting => { - tenantSetting.includes('Key') ? result.set(tenantSetting.split(':')[1],tenantSettingsList[tenantSettingsList.indexOf(tenantSetting) +1].split(':')[1]) : null; + tenantSetting.includes('Key') ? result.set(tenantSetting.split(':')[1], tenantSettingsList[tenantSettingsList.indexOf(tenantSetting) + 1].split(':')[1]) : null; }); } else { logger.error("Error occurred while executing command: " + stderr); @@ -186,73 +187,77 @@ export class ExecutionService { return result; } - private static onCloseCommand(code: number, stderr: string, stdout: string, output: string) : CxCommandOutput { - const cxCommandOutput = new CxCommandOutput(); - cxCommandOutput.exitCode = code; - if (stderr) { - cxCommandOutput.status = stderr; - } - if (stdout) { + private static onCloseCommand(code: number, stderr: string, stdout: string, output: string): CxCommandOutput { + const cxCommandOutput = new CxCommandOutput(); + cxCommandOutput.exitCode = code; + if (stderr) { + cxCommandOutput.status = stderr; + } + if (stdout) { const stdoutSplit = stdout.split('\n'); const data = stdoutSplit.find(isJsonString); if (data) { - const resultObject = JSON.parse(data); - switch (output) { - case CxConstants.SCAN_TYPE: - const scans = CxScan.parseProject(resultObject); - cxCommandOutput.payload = scans; - break; - case CxConstants.SCAN_ASCA: - const asca = CxAsca.parseScan(resultObject); - cxCommandOutput.payload = [asca]; - break; - case CxConstants.SCAN_OSS: - const oss = CxOssResult.parseResult(resultObject); - cxCommandOutput.payload = [oss]; - break; - case CxConstants.PROJECT_TYPE: - const projects = CxProject.parseProject(resultObject); - cxCommandOutput.payload = projects; - break; - case CxConstants.CODE_BASHING_TYPE: - const codeBashing = CxCodeBashing.parseCodeBashing(resultObject); - cxCommandOutput.payload = codeBashing; - break; - case CxConstants.BFL_TYPE: - const bflNode = CxBFL.parseBFLResponse(resultObject); - cxCommandOutput.payload = bflNode; - break; - case CxConstants.KICS_REALTIME_TYPE: - const kicsResults = CxKicsRealTime.parseKicsRealTimeResponse(resultObject); - cxCommandOutput.payload = [kicsResults]; - break; - case CxConstants.SCA_REALTIME_TYPE: - const scaRealtimeResponse = CxScaRealTime.parseScaRealTimeResponse(resultObject); - cxCommandOutput.payload = [scaRealtimeResponse]; - break; - case CxConstants.LEARN_MORE_DESCRIPTIONS_TYPE: - const learnMore = CxLearnMoreDescriptions.parseLearnMoreDescriptionsResponse(resultObject); - cxCommandOutput.payload = learnMore; - break; - case CxConstants.KICS_REMEDIATION_TYPE: - const kicsRemediationOutput = CxKicsRemediation.parseKicsRemediation(resultObject); - cxCommandOutput.payload = [kicsRemediationOutput]; - break; - case CxConstants.CHAT_TYPE: - const chatOutput = CxChat.parseChat(resultObject); - cxCommandOutput.payload = [chatOutput]; - break; - case CxConstants.MASK_TYPE: - const maskOutput = CxMask.parseMask(resultObject); - cxCommandOutput.payload = [maskOutput]; - break; - default: - cxCommandOutput.payload = resultObject; - } + const resultObject = JSON.parse(data); + switch (output) { + case CxConstants.SCAN_TYPE: + const scans = CxScan.parseProject(resultObject); + cxCommandOutput.payload = scans; + break; + case CxConstants.SCAN_ASCA: + const asca = CxAsca.parseScan(resultObject); + cxCommandOutput.payload = [asca]; + break; + case CxConstants.SCAN_OSS: + const oss = CxOssResult.parseResult(resultObject); + cxCommandOutput.payload = [oss]; + break; + case CxConstants.SCAN_SECRETS: + const secrets = CxSecretsResult.parseResult(resultObject); + cxCommandOutput.payload = [secrets]; + break; + case CxConstants.PROJECT_TYPE: + const projects = CxProject.parseProject(resultObject); + cxCommandOutput.payload = projects; + break; + case CxConstants.CODE_BASHING_TYPE: + const codeBashing = CxCodeBashing.parseCodeBashing(resultObject); + cxCommandOutput.payload = codeBashing; + break; + case CxConstants.BFL_TYPE: + const bflNode = CxBFL.parseBFLResponse(resultObject); + cxCommandOutput.payload = bflNode; + break; + case CxConstants.KICS_REALTIME_TYPE: + const kicsResults = CxKicsRealTime.parseKicsRealTimeResponse(resultObject); + cxCommandOutput.payload = [kicsResults]; + break; + case CxConstants.SCA_REALTIME_TYPE: + const scaRealtimeResponse = CxScaRealTime.parseScaRealTimeResponse(resultObject); + cxCommandOutput.payload = [scaRealtimeResponse]; + break; + case CxConstants.LEARN_MORE_DESCRIPTIONS_TYPE: + const learnMore = CxLearnMoreDescriptions.parseLearnMoreDescriptionsResponse(resultObject); + cxCommandOutput.payload = learnMore; + break; + case CxConstants.KICS_REMEDIATION_TYPE: + const kicsRemediationOutput = CxKicsRemediation.parseKicsRemediation(resultObject); + cxCommandOutput.payload = [kicsRemediationOutput]; + break; + case CxConstants.CHAT_TYPE: + const chatOutput = CxChat.parseChat(resultObject); + cxCommandOutput.payload = [chatOutput]; + break; + case CxConstants.MASK_TYPE: + const maskOutput = CxMask.parseMask(resultObject); + cxCommandOutput.payload = [maskOutput]; + break; + default: + cxCommandOutput.payload = resultObject; + } } - } + } - return cxCommandOutput; + return cxCommandOutput; } executeResultsCommands(pathToExecutable: string, commands: string[]): Promise { @@ -278,37 +283,36 @@ export class ExecutionService { }); } - async executeResultsCommandsFile(scanId: string, resultType: string, fileExtension: string,commands: string[], pathToExecutable: string,fileName:string): Promise { + async executeResultsCommandsFile(scanId: string, resultType: string, fileExtension: string, commands: string[], pathToExecutable: string, fileName: string): Promise { const filePath = path.join(os.tmpdir(), fileName + fileExtension) - const read = fs.readFileSync(filePath,'utf8'); + const read = fs.readFileSync(filePath, 'utf8'); const cxCommandOutput = new CxCommandOutput(); // Need to check if file output is json or html - if(fileExtension.includes("json")){ + if (fileExtension.includes("json")) { const read_json = JSON.parse(read.replace(/:([0-9]{15,}),/g, ':"$1",')); - if (read_json.results){ - const r : CxResult[] = read_json.results.map((member:any)=>{ - const cxScaPackageData = new CxScaPackageData(member.data.scaPackageData?.id,member.data.scaPackageData?.locations,member.data.scaPackageData?.dependencyPaths,member.data.scaPackageData?.outdated,member.data.scaPackageData?.fixLink,member.data.scaPackageData?.supportsQuickFix,member.data.scaPackageData?.typeOfDependency); - const cvss = new CxCvss(member.vulnerabilityDetails.cvss.version,member.vulnerabilityDetails.cvss.attackVector,member.vulnerabilityDetails.cvss.availability,member.vulnerabilityDetails.cvss.confidentiality,member.vulnerabilityDetails.cvss.attackComplexity,member.vulnerabilityDetails.cvss.integrityImpact,member.vulnerabilityDetails.cvss.scope,member.vulnerabilityDetails.cvss.privilegesRequired,member.vulnerabilityDetails.cvss.userInteraction); - const cxVulnerabilityDetails = new CxVulnerabilityDetails(member.vulnerabilityDetails.cweId,cvss,member.vulnerabilityDetails.compliances,member.vulnerabilityDetails.cvssScore,member.vulnerabilityDetails.cveName); - const nodes:CxNode[]=member.data.nodes?.map((node:any)=>{ - return new CxNode(node.id,node.line,node.name,node.column,node.length,node.method,node.nodeID,node.domType,node.fileName,node.fullName,node.typeName,node.methodLine,node.definitions) + if (read_json.results) { + const r: CxResult[] = read_json.results.map((member: any) => { + const cxScaPackageData = new CxScaPackageData(member.data.scaPackageData?.id, member.data.scaPackageData?.locations, member.data.scaPackageData?.dependencyPaths, member.data.scaPackageData?.outdated, member.data.scaPackageData?.fixLink, member.data.scaPackageData?.supportsQuickFix, member.data.scaPackageData?.typeOfDependency); + const cvss = new CxCvss(member.vulnerabilityDetails.cvss.version, member.vulnerabilityDetails.cvss.attackVector, member.vulnerabilityDetails.cvss.availability, member.vulnerabilityDetails.cvss.confidentiality, member.vulnerabilityDetails.cvss.attackComplexity, member.vulnerabilityDetails.cvss.integrityImpact, member.vulnerabilityDetails.cvss.scope, member.vulnerabilityDetails.cvss.privilegesRequired, member.vulnerabilityDetails.cvss.userInteraction); + const cxVulnerabilityDetails = new CxVulnerabilityDetails(member.vulnerabilityDetails.cweId, cvss, member.vulnerabilityDetails.compliances, member.vulnerabilityDetails.cvssScore, member.vulnerabilityDetails.cveName); + const nodes: CxNode[] = member.data.nodes?.map((node: any) => { + return new CxNode(node.id, node.line, node.name, node.column, node.length, node.method, node.nodeID, node.domType, node.fileName, node.fullName, node.typeName, node.methodLine, node.definitions) }); - const cxPackageData:CxPackageData[]=member.data.packageData?.map((packages:any)=>{ - return new CxPackageData(packages.comment,packages.type,packages.url); + const cxPackageData: CxPackageData[] = member.data.packageData?.map((packages: any) => { + return new CxPackageData(packages.comment, packages.type, packages.url); }); - const data = new CxData(cxPackageData,member.data.packageIdentifier,cxScaPackageData,member.data.queryId,member.data.queryName,member.data.group,member.data.resultHash,member.data.languageName,nodes,member.data.recommendedVersion); - return new CxResult(member.type,member.label,member.id,member.status,member.alternateId,member.similarityId,member.state,member.severity,member.created,member.firstFoundAt,member.foundAt,member.firstScanId,member.description,data,member.comments,cxVulnerabilityDetails, member.descriptionHTML); + const data = new CxData(cxPackageData, member.data.packageIdentifier, cxScaPackageData, member.data.queryId, member.data.queryName, member.data.group, member.data.resultHash, member.data.languageName, nodes, member.data.recommendedVersion); + return new CxResult(member.type, member.label, member.id, member.status, member.alternateId, member.similarityId, member.state, member.severity, member.created, member.firstFoundAt, member.foundAt, member.firstScanId, member.description, data, member.comments, cxVulnerabilityDetails, member.descriptionHTML); }); cxCommandOutput.payload = r; - } - else{ + } else { cxCommandOutput.exitCode = 1; cxCommandOutput.status = "Error in the json file." } } // In case of html output - else{ - const html_arrray:string[] = [] + else { + const html_arrray: string[] = [] html_arrray.push(read) cxCommandOutput.payload = html_arrray; } diff --git a/src/tests/ScanTest.test.ts b/src/tests/ScanTest.test.ts index 6c6f8f51..0c9221d7 100644 --- a/src/tests/ScanTest.test.ts +++ b/src/tests/ScanTest.test.ts @@ -182,4 +182,12 @@ describe("ScanCreate cases", () => { expect(cxCommandOutput.exitCode).toBe(0); }); + it.skip('ScanSecrets Successful case', async () => { + const wrapper = new CxWrapper(cxScanConfig); + const cxCommandOutput: CxCommandOutput = await wrapper.secretsScanResults("tsc/tests/data/secret-exposed.txt"); + console.log("Json object from scanOSS successful case: " + JSON.stringify(cxCommandOutput)); + expect(cxCommandOutput.payload).toBeDefined(); + expect(cxCommandOutput.exitCode).toBe(0); + }); + }); diff --git a/src/tests/data/secret-exposed.txt b/src/tests/data/secret-exposed.txt new file mode 100644 index 00000000..95be79fb --- /dev/null +++ b/src/tests/data/secret-exposed.txt @@ -0,0 +1,5 @@ +package data + +const ( + pat = "ghp_1234567890abcdef1234567890abcdef12345678" +)