diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ca65c8a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+.gitlab-ci.yml.bck
+node_modules/*
+imgs/.DS_Store
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index b3750f3..b727cf6 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -29,6 +29,7 @@ Cli Version Check:
script:
- npm i
- node ./cancelOldPipeline.js
+ - node ./utility/updateCommitStatus.js
- |
# Define installation directory and version file path
export CLI_FOLDER="$(pwd)/veracode-cli"
@@ -218,4 +219,4 @@ workflow:
PIPELINE_NAME: "${PROJECT_NAME} - SCA Scan"
- if: '$EXECUTE_IAC == "true"'
variables:
- PIPELINE_NAME: "${PROJECT_NAME} - IAC Scan"
+ PIPELINE_NAME: "${PROJECT_NAME} - IAC Scan"
\ No newline at end of file
diff --git a/cancelOldPipeline.js b/cancelOldPipeline.js
index 711423a..f227288 100644
--- a/cancelOldPipeline.js
+++ b/cancelOldPipeline.js
@@ -21,19 +21,43 @@ async function cancelOldPipeline() {
return;
}
+ // Find the current pipeline in the list to get its full timestamp with milliseconds
+ const currentPipeline = pipelines.find(p => p.id === Number(currentPipelineId));
+ const currentPipelineCreatedAtFull = currentPipeline ? currentPipeline.created_at : currentPipelineCreatedAt;
+
+ console.log('#### DEBUG - Current Pipeline Info ####');
+ console.log('currentPipelineId:', currentPipelineId);
+ console.log('currentPipelineCreatedAt (env):', currentPipelineCreatedAt);
+ console.log('currentPipelineCreatedAt (from API):', currentPipelineCreatedAtFull);
+ console.log('#### DEBUG - Current Pipeline Info ####');
+
for (const pipeline of pipelines) {
const pipelineId = pipeline.id;
+ console.log('#### DEBUG - Cancel Old Pipeline ####');
+ console.log('pipelineId:', pipelineId);
+ console.log('currentPipelineId:', currentPipelineId);
+ console.log('pipeline.created_at:', pipeline.created_at);
+ console.log('#### DEBUG - Cancel Old Pipeline ####');
+
// Skip current pipeline itself
if (pipelineId === Number(currentPipelineId)) {
console.log(`Skipping current pipeline ${pipelineId}`);
continue;
}
- // Convert current pipeline creation time to epoch milliseconds
- const currentEpoch = new Date(currentPipelineCreatedAt).getTime();
+ // Convert pipeline creation times to epoch milliseconds
+ // Use the full timestamp from API for accurate comparison
+ const currentEpoch = new Date(currentPipelineCreatedAtFull).getTime();
const createdEpoch = new Date(pipeline.created_at).getTime();
+ console.log('#### DEBUG - Time Comparison ####');
+ console.log('currentEpoch:', currentEpoch);
+ console.log('createdEpoch:', createdEpoch);
+ console.log('Difference (ms):', createdEpoch - currentEpoch);
+ console.log('Is createdEpoch > currentEpoch?', createdEpoch > currentEpoch);
+ console.log('#### DEBUG - Time Comparison ####');
+
// Skip newer pipelines
if (createdEpoch > currentEpoch) {
console.log(`Skipping newer pipeline ${pipelineId} created at ${pipeline.created_at}`);
diff --git a/displayScanResult.js b/displayScanResult.js
index 8a55af3..97f4a42 100644
--- a/displayScanResult.js
+++ b/displayScanResult.js
@@ -1,7 +1,7 @@
const { createWikiPage, createComment } = require('./utility/service');
const { scaResult, pipelineResult, policyResult, iacResult } = require('./utility/utils');
const { appConfig } = require('./config');
-async function displayScanResult(scanResult, warningMessage = "") {
+async function displayScanResult(scanResult, warningMessage = "", debug = null) {
const executePipeline = process.env.EXECUTE_PIPELINE;
const executeSca = process.env.EXECUTE_SCA;
const executeIac = process.env.EXECUTE_IAC;
@@ -13,17 +13,18 @@ async function displayScanResult(scanResult, warningMessage = "") {
if (eventName === appConfig().pullRequestEventName || eventName === appConfig().pushEventName) {
try {
if ((scanType === "IaC" && Object.entries(scanResult).length > 0) || scanResult.length > 0) {
- let formattedContent = scanType === 'SCA' ? scaResult(scanResult[0]) : scanType === 'Pipeline' ? pipelineResult(scanResult) : scanType === 'IaC' ? iacResult(scanResult) : policyResult(scanResult);
+ const sourceBranch = process.env.SOURCE_BRANCH;
+ let formattedContent = scanType === 'SCA' ? scaResult(scanResult[0]) : scanType === 'Pipeline' ? await pipelineResult(scanResult, sourceBranch, projectUrl, debug) : scanType === 'IaC' ? iacResult(scanResult) : policyResult(scanResult);
const wikiContent = `${scanType} Scan completed. :white_check_mark:\n\n` + formattedContent;
const createWikiResponse = await createWikiPage(scanType, projectUrl, wikiContent);
- const commentContent = createWikiResponse.hasOwnProperty('wikiUrl') && createWikiResponse?.wikiUrl !== "" ? `${scanType} Scan completed. :white_check_mark:\n\n` + formattedContent : `${scanType} Scan completed. :white_check_mark:\n\n` + formattedContent;
+ const commentContent = createWikiResponse.hasOwnProperty('wikiUrl') && createWikiResponse?.wikiUrl !== "" ? `${scanType} Scan completed.\n\n` + formattedContent : `${scanType} Scan completed.\n\n` + formattedContent;
await createComment(projectUrl, mergeRequestId, eventName, commitSha, commentContent);
} else {
let commentContent = "";
if (warningMessage) {
- commentContent = `${scanType} Scan completed. :warning:\n\n` + `${warningMessage}\n`;
+ commentContent = `${scanType} Scan completed.\n\n` + `${warningMessage}\n`;
} else {
- commentContent = `${scanType} Scan completed. :white_check_mark:\n\n` + 'No Vulnerability found.\n';
+ commentContent = `${scanType} Scan completed.\n\n` + 'No Vulnerability found.\n';
}
await createComment(projectUrl, mergeRequestId, eventName, commitSha, commentContent);
diff --git a/imgs/High.png b/imgs/High.png
new file mode 100644
index 0000000..72904da
Binary files /dev/null and b/imgs/High.png differ
diff --git a/imgs/Informational.png b/imgs/Informational.png
new file mode 100644
index 0000000..566d211
Binary files /dev/null and b/imgs/Informational.png differ
diff --git a/imgs/Low.png b/imgs/Low.png
new file mode 100644
index 0000000..dd3159c
Binary files /dev/null and b/imgs/Low.png differ
diff --git a/imgs/Medium.png b/imgs/Medium.png
new file mode 100644
index 0000000..a686240
Binary files /dev/null and b/imgs/Medium.png differ
diff --git a/imgs/Very_High.png b/imgs/Very_High.png
new file mode 100644
index 0000000..1088bae
Binary files /dev/null and b/imgs/Very_High.png differ
diff --git a/imgs/Very_Low.png b/imgs/Very_Low.png
new file mode 100644
index 0000000..37256f4
Binary files /dev/null and b/imgs/Very_Low.png differ
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..db81c46
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,521 @@
+{
+ "name": "veracode-gitlab-workflow-integration",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "veracode-gitlab-workflow-integration",
+ "version": "1.0.0",
+ "license": "ISC",
+ "dependencies": {
+ "axios": "^1.7.3",
+ "crypto": "^1.0.1",
+ "crypto-js": "^4.2.0",
+ "execa": "5.1.1",
+ "xml2js": "^0.6.2"
+ }
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "license": "MIT"
+ },
+ "node_modules/axios": {
+ "version": "1.13.5",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz",
+ "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==",
+ "license": "MIT",
+ "dependencies": {
+ "follow-redirects": "^1.15.11",
+ "form-data": "^4.0.5",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/crypto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz",
+ "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==",
+ "deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in.",
+ "license": "ISC"
+ },
+ "node_modules/crypto-js": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
+ "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
+ "license": "MIT"
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/execa": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
+ "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
+ "license": "MIT",
+ "dependencies": {
+ "cross-spawn": "^7.0.3",
+ "get-stream": "^6.0.0",
+ "human-signals": "^2.1.0",
+ "is-stream": "^2.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^4.0.1",
+ "onetime": "^5.1.2",
+ "signal-exit": "^3.0.3",
+ "strip-final-newline": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ }
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.15.11",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
+ "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
+ "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/get-stream": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/human-signals": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
+ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=10.17.0"
+ }
+ },
+ "node_modules/is-stream": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
+ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "license": "ISC"
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/merge-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+ "license": "MIT"
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/npm-run-path": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
+ "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/onetime": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "license": "MIT",
+ "dependencies": {
+ "mimic-fn": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+ "license": "MIT"
+ },
+ "node_modules/sax": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz",
+ "integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==",
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=11.0.0"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "license": "ISC"
+ },
+ "node_modules/strip-final-newline": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
+ "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/xml2js": {
+ "version": "0.6.2",
+ "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz",
+ "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==",
+ "license": "MIT",
+ "dependencies": {
+ "sax": ">=0.6.0",
+ "xmlbuilder": "~11.0.0"
+ },
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/xmlbuilder": {
+ "version": "11.0.1",
+ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
+ "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ }
+ }
+ }
+}
diff --git a/utility/common.js b/utility/common.js
index 835e970..8f9d67a 100644
--- a/utility/common.js
+++ b/utility/common.js
@@ -264,8 +264,8 @@ async function getVeracodePolicyByName(vid, vkey, policyName) {
async function getPolicyByName(vid, vkey, policyName) {
const resource = {
resourceUri: veracodeConfig().policyUri,
- queryAttribute: 'name',
- queryValue: encodeURIComponent(policyName)
+ queryAttribute1: 'name',
+ queryValue1: encodeURIComponent(policyName)
};
const response = await getResourceByAttribute(vid, vkey, resource);
return response;
@@ -305,7 +305,7 @@ async function getApplicationFindings(appGuid, vid, vkey) {
if (!findingsResponse._embedded) {
console.log('No Policy scan found, lets look for sandbox scan findings');
const getSandboxGUID = {
- resourceUri: `${veracodeConfig().findingsUri}/${appGuid}/sandboxes`,
+ resourceUri: `${veracodeConfig().applicationUri}/${appGuid}/sandboxes`,
queryAttribute1: '',
queryValue1: '',
};
@@ -527,6 +527,7 @@ module.exports = {
deleteResourceById,
getApplicationFindings,
veracodePolicyVerification,
+ getPolicyByName,
getLatestVersion,
downloadAndExtractCli,
setScanResultError,
diff --git a/utility/labels.js b/utility/labels.js
index 2b227a0..a4fffe6 100644
--- a/utility/labels.js
+++ b/utility/labels.js
@@ -1,32 +1,32 @@
const VERACODE_FLAW_LABELS = {
"Very High": {
'name': 'VeracodeFlaw: Very High',
- 'color': '#A90533',
+ 'color': '#D92B85',
'description': 'A Veracode Flaw, Very High severity',
},
High: {
'name': 'VeracodeFlaw: High',
- 'color': '#DD3B35',
+ 'color': '#E61F25',
'description': 'A Veracode Flaw, High severity'
},
Medium: {
'name': 'VeracodeFlaw: Medium',
- 'color': '#FF7D00',
+ 'color': '#FD7333',
'description': 'A Veracode Flaw, Medium severity'
},
Low: {
'name': 'VeracodeFlaw: Low',
- 'color': '#FFBE00',
+ 'color': '#FFCC33',
'description': 'A Veracode Flaw, Low severity'
},
"Very Low":{
'name': 'VeracodeFlaw: Very Low',
- 'color': '#33ADD2',
+ 'color': '#C9DA2C',
'description': 'A Veracode Flaw, Very Low severity',
},
Informational: {
'name': 'VeracodeFlaw: Informational',
- 'color': '#0270D3',
+ 'color': '#8DBD3E',
'description': 'A Veracode Flaw, Informational severity',
},
Unknown: {
diff --git a/utility/service.js b/utility/service.js
index 0b2eb59..2939393 100644
--- a/utility/service.js
+++ b/utility/service.js
@@ -106,7 +106,96 @@ async function createComment(projectUrl, mergeRequestId, eventName, commitSha, f
try {
let reqData;
let url;
+
+ // For merge requests, check if a matching comment already exists
if(eventName === appConfig().pullRequestEventName){
+ // Extract scan type and expected patterns from the new content
+ const contentLines = formattedContent.split('\n');
+ const firstLine = contentLines[0] || '';
+
+ // Extract scan type from first line (e.g., "Pipeline Scan completed." or "Pipeline Scan completed.")
+ const scanTypeMatch = firstLine.match(/(\w+)\s+Scan\s+completed/);
+ const scanType = scanTypeMatch ? scanTypeMatch[1] : '';
+
+ // Expected scan message patterns (these appear in the content)
+ const expectedPatterns = [
+ '**Veracode IaC/Secrets Scan found vulnerabilities/misconfigurations/secrets**',
+ '**Veracode SCA Scan found vulnerabilities**',
+ '**Veracode Static Scan found flaws**'
+ ];
+
+ // Check if content contains Veracode image and one of the expected patterns
+ const hasVeracodeImage = formattedContent.includes('![Veracode]') || formattedContent.includes('veracodePlatformLogoSmall.png');
+ const hasExpectedPattern = expectedPatterns.some(pattern => formattedContent.includes(pattern));
+
+ // Only check for existing comments if we have a valid pattern
+ if (scanType && hasVeracodeImage && hasExpectedPattern) {
+ try {
+ // Fetch all notes for this merge request
+ const notesUrl = `https://${hostName}/api/v4/projects/${projectId}/merge_requests/${mergeRequestId}/notes`;
+ let allNotes = [];
+ let page = 1;
+
+ while (true) {
+ const response = await axios.get(notesUrl, {
+ ...headers,
+ params: {
+ per_page: 100,
+ page: page
+ }
+ });
+
+ const notes = response.data;
+ if (notes.length === 0) break;
+
+ allNotes = [...allNotes, ...notes];
+
+ const totalPages = parseInt(response.headers['x-total-pages'] || '1');
+ if (page >= totalPages) break;
+ page++;
+ }
+
+ // Find matching comment by checking:
+ // 1. First line contains scan type + "Scan completed"
+ // 2. Contains Veracode image
+ // 3. Contains one of the expected scan message patterns
+ const matchingNote = allNotes.find(note => {
+ if (!note.body) return false;
+
+ const noteLines = note.body.split('\n');
+ const noteFirstLine = noteLines[0] || '';
+
+ // Check if first line matches scan type pattern
+ const noteScanTypeMatch = noteFirstLine.match(/(\w+)\s+Scan\s+completed/);
+ const noteScanType = noteScanTypeMatch ? noteScanTypeMatch[1] : '';
+
+ // Check if note contains Veracode image
+ const noteHasImage = note.body.includes('![Veracode]') || note.body.includes('veracodePlatformLogoSmall.png');
+
+ // Check if note contains one of the expected patterns
+ const noteMatchesPattern = expectedPatterns.some(pattern => note.body.includes(pattern));
+
+ // Match if scan type matches, has image, and matches pattern
+ return noteScanType === scanType && noteHasImage && noteMatchesPattern;
+ });
+
+ if (matchingNote) {
+ // Update existing comment
+ const updateUrl = `https://${hostName}/api/v4/projects/${projectId}/merge_requests/${mergeRequestId}/notes/${matchingNote.id}`;
+ reqData = {
+ body: formattedContent
+ };
+ await axios.put(updateUrl, reqData, headers);
+ console.log(`Updated existing comment (note ID: ${matchingNote.id}) under the ${projectUrl} project for ${infoText}`);
+ return;
+ }
+ } catch (error) {
+ console.log(`Error while checking for existing comments, will create new one:`, error.response?.data || error.message);
+ // Continue to create new comment if check fails
+ }
+ }
+
+ // Create new comment (either no match found or not a merge request with expected pattern)
url = `https://${hostName}/api/v4/projects/${projectId}/merge_requests/${mergeRequestId}/notes`
reqData = {
body: formattedContent
@@ -171,4 +260,238 @@ async function cancelPipeline(hostName, veracodeProjectId, pipelineId) {
}
}
-module.exports = {checkLabelExists, createLabels, createIssue, listExistingOpenIssues, createWikiPage, createComment, fetchAllPipelines, getPipelineVariables, cancelPipeline}
\ No newline at end of file
+async function updateCommitStatus(MR_SHA, STATE, PIPELINE_NAME, CI_PIPELINE_URL, DESCRIPTION, DEBUG) {
+ if (DEBUG === "true") {
+ console.log('#### DEBUG - Update Commit Status ####');
+ console.log('MR_SHA:', MR_SHA);
+ console.log('STATE:', STATE);
+ console.log('PIPELINE_NAME:', PIPELINE_NAME);
+ console.log('CI_PIPELINE_URL:', CI_PIPELINE_URL);
+ console.log('DESCRIPTION:', DESCRIPTION);
+ console.log('hostName:', hostName);
+ console.log('projectId:', projectId);
+ console.log('#### DEBUG - Update Commit Status ####');
+ }
+
+ try {
+ const url = `https://${hostName}/api/v4/projects/${projectId}/statuses/${MR_SHA}`;
+ const reqData = {
+ state: STATE,
+ name: PIPELINE_NAME,
+ target_url: CI_PIPELINE_URL,
+ description: DESCRIPTION
+ };
+
+ const response = await axios.post(url, reqData, headers);
+
+ if (response.status >= 200 && response.status < 300) {
+ console.log("Commit status updated successfully");
+ return response.data;
+ } else {
+ console.error("MR couldn't be updated");
+ return null;
+ }
+ } catch (error) {
+ console.error("MR couldn't be updated", error.response?.data || error.message);
+ return null;
+ }
+}
+
+// Helper function to calculate similarity between two strings (simple Levenshtein-like)
+function calculateSimilarity(str1, str2) {
+ const longer = str1.length > str2.length ? str1 : str2;
+ const shorter = str1.length > str2.length ? str2 : str1;
+ if (longer.length === 0) return 1.0;
+
+ const distance = levenshteinDistance(longer.toLowerCase(), shorter.toLowerCase());
+ return (longer.length - distance) / longer.length;
+}
+
+// Simple Levenshtein distance calculation
+function levenshteinDistance(str1, str2) {
+ const matrix = [];
+ for (let i = 0; i <= str2.length; i++) {
+ matrix[i] = [i];
+ }
+ for (let j = 0; j <= str1.length; j++) {
+ matrix[0][j] = j;
+ }
+ for (let i = 1; i <= str2.length; i++) {
+ for (let j = 1; j <= str1.length; j++) {
+ if (str2.charAt(i - 1) === str1.charAt(j - 1)) {
+ matrix[i][j] = matrix[i - 1][j - 1];
+ } else {
+ matrix[i][j] = Math.min(
+ matrix[i - 1][j - 1] + 1,
+ matrix[i][j - 1] + 1,
+ matrix[i - 1][j] + 1
+ );
+ }
+ }
+ }
+ return matrix[str2.length][str1.length];
+}
+
+async function getSourceFilePath(filePath, branch, projectUrl, lineNumber = null, debug = null) {
+ try {
+ if (!filePath || !branch || !projectUrl) {
+ console.log("Error: Missing required parameters for getSourceFilePath");
+ return null;
+ }
+
+ // Extract filename from the path
+ const fileName = filePath.split('/').pop() || filePath.split('\\').pop();
+ const normalizedFilePath = filePath.startsWith('/') ? filePath.substring(1) : filePath;
+
+ if (debug === "true") {
+ console.log('#### Debug - getSourceFilePath - Fuzzy Search ####');
+ console.log('Searching for file:', fileName);
+ console.log('Original path:', filePath);
+ console.log('Branch:', branch);
+ console.log('#### Debug - getSourceFilePath - Fuzzy Search ####');
+ }
+
+ // Get repository tree recursively to search for files
+ let foundFilePath = null;
+ try {
+ const treeUrl = `https://${hostName}/api/v4/projects/${encodeURIComponent(projectId)}/repository/tree`;
+ let allFiles = [];
+ let page = 1;
+ const perPage = 100;
+
+ // Fetch all files recursively
+ while (true) {
+ const response = await axios.get(treeUrl, {
+ ...headers,
+ params: {
+ ref: branch,
+ recursive: true,
+ per_page: perPage,
+ page: page
+ }
+ });
+
+ const files = response.data.filter(item => item.type === 'blob');
+ allFiles = allFiles.concat(files);
+
+ // Check if there are more pages
+ const totalPages = parseInt(response.headers['x-total-pages'] || '1');
+ if (page >= totalPages) break;
+ page++;
+ }
+
+ if (debug === "true") {
+ console.log(`Found ${allFiles.length} files in repository`);
+ }
+
+ // First, try exact match
+ let exactMatch = allFiles.find(file =>
+ file.path === normalizedFilePath ||
+ file.path.endsWith(normalizedFilePath) ||
+ file.path === filePath
+ );
+
+ if (exactMatch) {
+ foundFilePath = exactMatch.path;
+ if (debug === "true") {
+ console.log(`Exact match found: ${foundFilePath}`);
+ }
+ } else {
+ // Try to find by filename
+ let filenameMatches = allFiles.filter(file =>
+ file.name === fileName ||
+ file.name.toLowerCase() === fileName.toLowerCase()
+ );
+
+ if (filenameMatches.length === 1) {
+ foundFilePath = filenameMatches[0].path;
+ if (debug === "true") {
+ console.log(`Single filename match found: ${foundFilePath}`);
+ }
+ } else if (filenameMatches.length > 1) {
+ // Multiple files with same name - use fuzzy matching on path
+ let bestMatch = null;
+ let bestScore = 0;
+
+ for (const file of filenameMatches) {
+ // Calculate similarity between original path and found path
+ const pathSimilarity = calculateSimilarity(normalizedFilePath, file.path);
+ if (pathSimilarity > bestScore) {
+ bestScore = pathSimilarity;
+ bestMatch = file;
+ }
+ }
+
+ if (bestMatch && bestScore > 0.3) { // Threshold for similarity
+ foundFilePath = bestMatch.path;
+ if (debug === "true") {
+ console.log(`Fuzzy match found (score: ${bestScore.toFixed(2)}): ${foundFilePath}`);
+ }
+ } else {
+ // Use the first match if no good fuzzy match
+ foundFilePath = filenameMatches[0].path;
+ if (debug === "true") {
+ console.log(`Using first filename match: ${foundFilePath}`);
+ }
+ }
+ } else {
+ // No exact filename match - try fuzzy search on all files
+ let bestMatch = null;
+ let bestScore = 0;
+
+ for (const file of allFiles) {
+ const nameSimilarity = calculateSimilarity(fileName, file.name);
+ const pathSimilarity = calculateSimilarity(normalizedFilePath, file.path);
+ const combinedScore = (nameSimilarity * 0.7) + (pathSimilarity * 0.3);
+
+ if (combinedScore > bestScore) {
+ bestScore = combinedScore;
+ bestMatch = file;
+ }
+ }
+
+ if (bestMatch && bestScore > 0.5) { // Threshold for fuzzy match
+ foundFilePath = bestMatch.path;
+ if (debug === "true") {
+ console.log(`Fuzzy match found (score: ${bestScore.toFixed(2)}): ${foundFilePath}`);
+ }
+ }
+ }
+ }
+ } catch (error) {
+ if (debug === "true") {
+ console.log(`Error searching repository tree: ${error.response?.data || error.message}`);
+ }
+ // Fallback to original path if search fails
+ foundFilePath = normalizedFilePath;
+ }
+
+ // If no match found, use original path
+ if (!foundFilePath) {
+ if (debug === "true") {
+ console.log(`No match found, using original path: ${normalizedFilePath}`);
+ }
+ foundFilePath = normalizedFilePath;
+ }
+
+ // Encode the file path for URL
+ const encodedFilePath = foundFilePath.split('/').map(segment => encodeURIComponent(segment)).join('/');
+
+ // Construct the GitLab blob URL
+ let fileUrl = `${projectUrl}/-/blob/${encodeURIComponent(branch)}/${encodedFilePath}`;
+
+ if (lineNumber) {
+ fileUrl += `#L${lineNumber}`;
+ }
+
+ if (debug === "true") {
+ console.log(`Final file URL: ${fileUrl}`);
+ }
+ return fileUrl;
+ } catch (error) {
+ console.log("Error constructing source file path:", error.message);
+ return null;
+ }
+}
+
+module.exports = {checkLabelExists, createLabels, createIssue, listExistingOpenIssues, createWikiPage, createComment, fetchAllPipelines, getPipelineVariables, cancelPipeline, updateCommitStatus, getSourceFilePath}
\ No newline at end of file
diff --git a/utility/updateCommitStatus.js b/utility/updateCommitStatus.js
new file mode 100644
index 0000000..ae3231a
--- /dev/null
+++ b/utility/updateCommitStatus.js
@@ -0,0 +1,61 @@
+const { updateCommitStatus } = require("./service");
+
+const commitSha = process.env.COMMIT_SHA;
+const state = 'running';
+const pipelineName = process.env.PIPELINE_NAME ;
+const ciPipelineUrl = process.env.CI_PIPELINE_URL;
+const description = process.env.PIPELINE_NAME+' started';
+const debug = process.env.DEBUG;
+
+async function updateStatus() {
+ if (debug === "true") {
+ console.log('#### DEBUG - Update Commit Status ####');
+ console.log('commitSha:', commitSha);
+ console.log('state:', state);
+ console.log('pipelineName:', pipelineName);
+ console.log('ciPipelineUrl:', ciPipelineUrl);
+ console.log('description:', description);
+ console.log('#### DEBUG - Update Commit Status ####');
+ }
+ try {
+ if (!commitSha) {
+ console.log("Error: Commit SHA not found. Please set CI_COMMIT_SHA or COMMIT_SHA environment variable.");
+ process.exit(0);
+ }
+
+ if (!ciPipelineUrl) {
+ console.log("Error: CI_PIPELINE_URL not found.");
+ process.exit(0);
+ }
+
+ if (pipelineName && pipelineName.includes('Sandbox Scan')) {
+ console.log("No Need to update MR status for a Sandbox Scan");
+ process.exit(0);
+ }
+
+ if (pipelineName && pipelineName.includes('Policy Scan')) {
+ console.log("No Need to update MR status for a Policy Scan");
+ process.exit(0);
+ }
+
+ if (debug === "true") {
+ console.log(`Updating commit status for SHA: ${commitSha}`);
+ console.log(`State: ${state}`);
+ console.log(`Pipeline Name: ${pipelineName}`);
+ console.log(`Description: ${description}`);
+ console.log('#### DEBUG - Update Commit Status ####');
+ }
+
+ const result = await updateCommitStatus(commitSha, state, pipelineName, ciPipelineUrl, description, debug);
+
+ if (!result) {
+ console.log("MR couldn't be updated");
+ }
+
+ console.log("Commit status updated successfully");
+ } catch (error) {
+ console.log("MR couldn't be updated", error.message);
+ }
+}
+
+updateStatus();
diff --git a/utility/utils.js b/utility/utils.js
index 3f7b0ba..013a0c3 100644
--- a/utility/utils.js
+++ b/utility/utils.js
@@ -326,66 +326,387 @@ const severityRank = {
"INFORMATIONAL":0
};
-function initialScanInfo(){
- return '- :red_circle: **Critical**: Immediate attention required.\n' +
- '- :orange_circle: **High**: Serious but slightly lower priority than Critical.\n' +
- '- :yellow_circle: **Medium**: Moderate risk, to be fixed in a reasonable timeframe.\n' +
- '- :green_circle: **Low**: Minimal risk, can be addressed in the normal course of development.\n' +
- '- :blue_circle: **Very Low**: Recommendations or low-priority findings.\n' +
- '- :white_circle: **Informational**: No security impact, can be ignored.\n'
+/**
+ * Normalizes severity strings from different scan types to a standard format
+ * @param {string} severity - Severity string from scan results
+ * @returns {string} Normalized severity string
+ */
+function normalizeSeverity(severity) {
+ if (!severity) return '';
+
+ // Normalize to string and trim whitespace
+ const normalized = String(severity).trim();
+
+ // Handle uppercase (IaC uses CRITICAL, HIGH, etc.)
+ const upperSeverity = normalized.toUpperCase();
+
+ // Map to standard severity names
+ if (upperSeverity === 'CRITICAL' || normalized === 'Critical') {
+ return 'Critical';
+ } else if (upperSeverity === 'HIGH' || normalized === 'High' || normalized === ' High') {
+ return 'High';
+ } else if (upperSeverity === 'MEDIUM' || normalized === 'Medium') {
+ return 'Medium';
+ } else if (upperSeverity === 'LOW' || normalized === 'Low' || normalized === 'Low Risk') {
+ return 'Low';
+ } else if (upperSeverity === 'VERY_LOW' || normalized === 'Very Low') {
+ return 'Very Low';
+ } else if (upperSeverity === 'INFORMATIONAL' || normalized === 'Informational') {
+ return 'Informational';
+ }
+
+ // Return original if no match
+ return normalized;
+}
+
+/**
+ * Gets the severity image URL from the project repository
+ * Images are stored in the imgs folder in the same repository, always on 'main' branch
+ * @param {string} severity - Severity string (will be normalized)
+ * @param {string} projectUrl - Optional project URL (falls back to CI_PROJECT_URL)
+ * @returns {string} Image URL or empty string if construction fails
+ */
+function getSeverityImageUrl(severity, projectUrl = null) {
+ // Normalize severity first
+ const normalizedSeverity = normalizeSeverity(severity);
+
+ // Get project URL from environment variable (CI_PROJECT_URL) or fall back to passed parameter
+ const currentProjectUrl = process.env.CI_PROJECT_URL || projectUrl;
+ if (!currentProjectUrl) return '';
+
+ // Map severity to image filename
+ const imageMap = {
+ 'Critical': 'Very_High.png',
+ 'High': 'High.png',
+ 'Medium': 'Medium.png',
+ 'Low': 'Low.png',
+ 'Very Low': 'Very_Low.png',
+ 'Informational': 'Informational.png'
+ };
+
+ const imageFile = imageMap[normalizedSeverity];
+ if (!imageFile) return '';
+
+ // Construct GitLab raw file URL by appending the path to CI_PROJECT_URL
+ // Format: {CI_PROJECT_URL}/-/raw/main/imgs/{filename}
+ try {
+ // Remove trailing slash from project URL if present
+ const baseUrl = currentProjectUrl.replace(/\/$/, '');
+ return `${baseUrl}/-/raw/main/imgs/${encodeURIComponent(imageFile)}`;
+ } catch (error) {
+ console.log('Error constructing image URL:', error.message);
+ return '';
+ }
+}
+
+/**
+ * Creates a colored indicator using images for severity display
+ * @param {string} severity - Severity string (will be normalized)
+ * @param {string} projectUrl - Optional project URL (falls back to CI_PROJECT_URL)
+ * @returns {string} Markdown image indicator or empty string if image URL can't be constructed
+ */
+function getColoredIndicator(severity, projectUrl = null) {
+ const imageUrl = getSeverityImageUrl(severity, projectUrl);
+ if (imageUrl) {
+ const normalizedSeverity = normalizeSeverity(severity);
+ return ``;
+ }
+ // Fallback to empty string if image URL can't be constructed
+ return '';
+}
+
+function initialScanInfo(scanType = 'Pipeline', scanResults = null, severityColors = null, projectUrl = null, branch = null){
+ let scanMessage = '';
+
+ switch(scanType) {
+ case 'Pipeline':
+ scanMessage = ' ${vulnerability.title} ${lib.name}: ${lib.versions[0].version} ${vulnerability.overview} Latest Release: ${lib.latestRelease} Latest safe version: ${lib.recommendedVersion} CVSS Score: ${vulnerability.cvss3Score} CVSS Vector: ${vulnerability.cvss3Vector} EPSS Score: ${vulnerability.exploitability.epssScore} EPSS Percentile: ${vulnerability.exploitability.epssPercentile} EPSS Model Version: ${vulnerability.exploitability.epssModelVersion}
**Veracode Static Scan found flaws**';
+ break;
+ case 'Sandbox':
+ scanMessage = '
**Veracode Static Scan found flaws**';
+ break;
+ case 'Policy':
+ scanMessage = '
**Veracode Static Scan found flaws**';
+ break;
+ case 'SCA':
+ scanMessage = '
**Veracode SCA Scan found vulnerabilities**';
+ break;
+ case 'IaC':
+ scanMessage = '
**Veracode IaC/Secrets Scan found vulnerabilities/misconfigurations/secrets**';
+ break;
+ default:
+ scanMessage = '
**Veracode Scan found issues**';
+ }
+
+ let output = scanMessage + '\n\n';
+
+ // Add severity breakdown table for Pipeline scans
+ if (scanType === 'Pipeline' && scanResults && Array.isArray(scanResults) && scanResults.length > 0) {
+ // Count findings by severity
+ const severityCounts = {
+ 'Critical': 0,
+ 'High': 0,
+ 'Medium': 0,
+ 'Low': 0,
+ 'Very Low': 0,
+ 'Informational': 0
+ };
+
+ scanResults.forEach(result => {
+ const severityText = severityType(result.severity);
+ if (severityCounts.hasOwnProperty(severityText)) {
+ severityCounts[severityText]++;
+ }
+ });
+
+ // Create severity breakdown table
+ output += '### Findings by Severity\n\n';
+ output += '| Severity | Count |\n';
+ output += '|----------|-------|\n';
+ output += `| ${getColoredIndicator('Critical', projectUrl)} | ${severityCounts['Critical']} |\n`;
+ output += `| ${getColoredIndicator('High', projectUrl)} | ${severityCounts['High']} |\n`;
+ output += `| ${getColoredIndicator('Medium', projectUrl)} | ${severityCounts['Medium']} |\n`;
+ output += `| ${getColoredIndicator('Low', projectUrl)} | ${severityCounts['Low']} |\n`;
+ output += `| ${getColoredIndicator('Very Low', projectUrl)} | ${severityCounts['Very Low']} |\n`;
+ output += `| ${getColoredIndicator('Informational', projectUrl)} | ${severityCounts['Informational']} |\n`;
+ output += `| **Total** | **${scanResults.length}** |\n\n`;
+ }
+
+ // Add severity breakdown table for SCA scans
+ if (scanType === 'SCA' && scanResults && scanResults.vulnerabilities && Array.isArray(scanResults.vulnerabilities)) {
+ // Count findings by severity (each vulnerability-library combination counts as one)
+ const severityCounts = {
+ 'Critical': 0,
+ 'High': 0,
+ 'Medium': 0,
+ 'Low': 0,
+ 'Very Low': 0,
+ 'Informational': 0
+ };
+
+ let totalCount = 0;
+ scanResults.vulnerabilities.forEach(vulnerability => {
+ const severityText = scaSeverityType(vulnerability.cvss3Score);
+ // Count each library affected by this vulnerability
+ const libraryCount = vulnerability.libraries ? vulnerability.libraries.length : 1;
+ totalCount += libraryCount;
+
+ // Normalize severity text (handle "Low Risk" and " High" with space)
+ const normalizedSeverity = normalizeSeverity(severityText);
+ if (severityCounts.hasOwnProperty(normalizedSeverity)) {
+ severityCounts[normalizedSeverity] += libraryCount;
+ }
+ });
+
+ // Create severity breakdown table
+ output += '### Findings by Severity\n\n';
+ output += '| Severity | Count |\n';
+ output += '|----------|-------|\n';
+ output += `| ${getColoredIndicator('Critical', projectUrl)} | ${severityCounts['Critical']} |\n`;
+ output += `| ${getColoredIndicator('High', projectUrl)} | ${severityCounts['High']} |\n`;
+ output += `| ${getColoredIndicator('Medium', projectUrl)} | ${severityCounts['Medium']} |\n`;
+ output += `| ${getColoredIndicator('Low', projectUrl)} | ${severityCounts['Low']} |\n`;
+ output += `| ${getColoredIndicator('Very Low', projectUrl)} | ${severityCounts['Very Low']} |\n`;
+ output += `| ${getColoredIndicator('Informational', projectUrl)} | ${severityCounts['Informational']} |\n`;
+ output += `| **Total** | **${totalCount}** |\n\n`;
+ }
+
+ // Add severity breakdown table for IaC scans
+ if (scanType === 'IaC' && scanResults) {
+ // Initialize counts for all severities across all three types
+ const severityOrder = ['Critical', 'High', 'Medium', 'Low', 'Very Low', 'Informational'];
+ const counts = {
+ 'Critical': { vulnerabilities: 0, misconfigurations: 0, secrets: 0 },
+ 'High': { vulnerabilities: 0, misconfigurations: 0, secrets: 0 },
+ 'Medium': { vulnerabilities: 0, misconfigurations: 0, secrets: 0 },
+ 'Low': { vulnerabilities: 0, misconfigurations: 0, secrets: 0 },
+ 'Very Low': { vulnerabilities: 0, misconfigurations: 0, secrets: 0 },
+ 'Informational': { vulnerabilities: 0, misconfigurations: 0, secrets: 0 }
+ };
+
+ // Count vulnerabilities by severity
+ const vulnerabilityData = scanResults?.vulnerabilities?.matches || [];
+ vulnerabilityData.forEach(result => {
+ const severity = normalizeSeverity(result.vulnerability.severity);
+ if (counts.hasOwnProperty(severity)) {
+ counts[severity].vulnerabilities++;
+ }
+ });
+
+ // Count misconfigurations by severity
+ const misconfigurations = scanResults?.configs || [];
+ misconfigurations.forEach(result => {
+ const severity = normalizeSeverity(result.Severity);
+ if (counts.hasOwnProperty(severity)) {
+ counts[severity].misconfigurations++;
+ }
+ });
+
+ // Count secrets by severity
+ const secrets = scanResults?.secrets || [];
+ secrets.forEach(result => {
+ const severity = normalizeSeverity(result.Severity);
+ if (counts.hasOwnProperty(severity)) {
+ counts[severity].secrets++;
+ }
+ });
+
+ // Create severity breakdown table
+ output += '### Findings by Severity\n\n';
+ output += '| Severity | Vulnerability | Misconfiguration | Secrets |\n';
+ output += '|----------|---------------|------------------|--------|\n';
+ severityOrder.forEach(severity => {
+ output += `| ${getColoredIndicator(severity, projectUrl)} | ${counts[severity].vulnerabilities} | ${counts[severity].misconfigurations} | ${counts[severity].secrets} |\n`;
+ });
+ output += '\n';
+ }
+
+ return output;
}
function scaResult(scanResult){
- const vulnerabilities = scanResult.vulnerabilities.sort((a,b)=> b.cvss3Score - a.cvss3Score);
const libraries = scanResult.libraries;
- let output = initialScanInfo();
+ const projectUrl = process.env.CI_PROJECT_URL || process.env.PROJECT_URL;
+
+ // Sort vulnerabilities by severity (Very High/Critical first)
+ // Map normalized severity to severityRank keys
+ const getSeverityRank = (severity) => {
+ const normalized = normalizeSeverity(severity);
+ // Map normalized severity to severityRank keys
+ if (normalized === 'Critical') return severityRank.CRITICAL;
+ if (normalized === 'High') return severityRank.HIGH;
+ if (normalized === 'Medium') return severityRank.MEDIUM;
+ if (normalized === 'Low') return severityRank.LOW;
+ if (normalized === 'Very Low') return severityRank.VERY_LOW;
+ if (normalized === 'Informational') return severityRank.INFORMATIONAL;
+ return 0;
+ };
+
+ const vulnerabilities = scanResult.vulnerabilities.sort((a, b) => {
+ const severityA = scaSeverityType(a.cvss3Score);
+ const severityB = scaSeverityType(b.cvss3Score);
+ const rankA = getSeverityRank(severityA);
+ const rankB = getSeverityRank(severityB);
+ // If same rank, sort by CVSS score as tiebreaker
+ if (rankB === rankA) {
+ return b.cvss3Score - a.cvss3Score;
+ }
+ return rankB - rankA; // Descending order (highest severity first)
+ });
+
+ let output = initialScanInfo('SCA', scanResult, null, projectUrl);
output+= 'Scan Details
\n\n'+
- '| Vulnerability ID | Severity | Description | Library | Version |\n' +
- '| ---------------- | -------- | ----------- | ------- | ------- |\n';
+ ''+
+ ''+
+ '
'+
+ ' '+
+ ''+
+ '';
+
+
+
vulnerabilities.forEach((vulnerability) => {
vulnerability.libraries.forEach((library)=>{
const libId = library._links.ref.split('/')[4];
const lib = libraries[libId];
- output +=
- `| ${vulnerability.cve !== null ? `CVE-${vulnerability.cve}` : `NO-CVE`} `+
- `| ${scaSeverityType(vulnerability.cvss3Score)} ` +
- `| ${vulnerability.title} ` +
- `| ${lib.name} ` +
- `| ${lib.versions[0].version} |\n`;
+ const severityText = scaSeverityType(vulnerability.cvss3Score);
+ const severityDisplay = `${getColoredIndicator(severityText, projectUrl)}`;
+
+ output += ` Severity '+
+ ' Vulnerability ID '+
+ '
+
+ ${severityDisplay}
+ ${vulnerability.cve !== null ? `CVE-${vulnerability.cve}` : `NO-CVE`}
+
+ `;
+
});
});
- output += '\n'
+ output += '
+
+ Findings Details
+ Scan Details
\n\n'+
- '| CWE ID | Severity | Issue Type | Source File |\n' +
- '| ------ | -------- | ---------- | ----------- |\n';
- scanResult.forEach((result) => {
- output +=
- `| ${result.cwe_id} `+
- `| ${severityType(result.severity)} ` +
- `| ${result.issue_type} ` +
- `| Line ${result.files.source_file.line}: ${result.files.source_file.file} |\n`;
- });
- output += '
| Severity | '+ + 'CWE ID | '+ + ' Source File Issue Type Details | '+
+ '
|---|---|---|
| ${severityDisplay} | +${result.cwe_id} | + [${filePath}:${lineNumber}](${displayLink}) ${result.issue_type} Flaw Details${result.display_text} |
+