UNPKG

@zaproxy/actions-common-scans

Version:
202 lines (201 loc) 9.83 kB
import fs from "fs"; import { Octokit as BaseOcto } from "@octokit/core"; import { throttling } from "@octokit/plugin-throttling"; import { retry } from "@octokit/plugin-retry"; import { requestLog } from "@octokit/plugin-request-log"; import actionHelper from "./action-helper.js"; const actionCommon = { processReport: async (token, workSpace, plugins, currentRunnerID, issueTitle, repoName, allowIssueWriting = true, artifactName = "zap_scan", fetcher = fetch) => { const jsonReportName = "report_json.json"; const mdReportName = "report_md.md"; const htmlReportName = "report_html.html"; if (!allowIssueWriting) { await actionHelper.uploadArtifacts(workSpace, mdReportName, jsonReportName, htmlReportName, artifactName); return; } let openIssue; let currentReport; let previousRunnerID; let previousReport = {}; let create_new_issue = false; const tmp = repoName.split("/"); const owner = tmp[0]; const repo = tmp[1]; const MyOctokit = BaseOcto.plugin(retry, throttling, requestLog); if (fetcher !== fetch) { MyOctokit.plugin(requestLog); } const octokit = new MyOctokit({ auth: token, baseUrl: process.env.GITHUB_API_URL ?? "https://api.github.com", throttle: { onRateLimit: (retryAfter, options) => { if (options?.request?.retryCount <= 2) { console.log(`Hit rate limit. Retrying after ${retryAfter} seconds!`); return true; } }, onSecondaryRateLimit: (retryAfter) => { console.log(`Hit secondary rate limit. Retrying after ${retryAfter} seconds!`); return true; }, }, request: { // Fetch is a type from @octokit/types, which is set to any.. // so we have to disable the eslint check for now. // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment fetch: fetcher, }, }); try { const jReportFile = fs.readFileSync(`${workSpace}/${jsonReportName}`); currentReport = JSON.parse(jReportFile.toString()); } catch (err) { console.error(`Failed to locate the json report generated by ZAP Scan! err: ${err.toString()}`); return; } const issues = await octokit.request("GET /search/issues", { q: encodeURI(`is:issue state:open repo:${owner}/${repo} ${issueTitle}`).replace(/%20/g, "+"), sort: "updated", advanced_search: "true", }); // If there is no existing open issue then create a new issue if (issues.data.items.length === 0) { create_new_issue = true; } else { let login = "github-actions[bot]"; try { login = (await octokit.request("GET /user")).data.login; } catch (_) { console.log(`Using ${login} to search for issues.`); } // Sometimes search API returns recently closed issue as an open issue for (const issue of issues.data.items) { if (issue.state === "open" && issue.user.login === login) { openIssue = issue; break; } } if (openIssue === undefined) { create_new_issue = true; } else { console.log(`Ongoing open issue has been identified #${openIssue.number}`); // If there is no comments then read the body if (openIssue.comments === 0) { previousRunnerID = actionHelper.getRunnerID(openIssue.body); } else { const comments = await octokit.request("GET /repos/{owner}/{repo}/issues/{issue_number}/comments", { owner: owner, repo: repo, issue_number: openIssue.number, }); let lastBotComment; const lastCommentIndex = comments.data.length - 1; for (let i = lastCommentIndex; i >= 0; i--) { if (comments.data[i].user.login === login) { lastBotComment = comments.data[i]; break; } } if (lastBotComment === undefined) { previousRunnerID = actionHelper.getRunnerID(openIssue.body); } else { previousRunnerID = actionHelper.getRunnerID(lastBotComment.body); } } if (previousRunnerID !== null) { previousReport = await actionHelper.readPreviousReport(octokit, owner, repo, workSpace, previousRunnerID, artifactName); if (previousReport === undefined) { create_new_issue = true; } } } } if (plugins.length !== 0) { console.log(`${plugins.length} plugins will be ignored according to the rules configuration`); currentReport = await actionHelper.filterReport(currentReport, plugins); // Update the newly filtered report fs.unlinkSync(`${workSpace}/${jsonReportName}`); fs.writeFileSync(`${workSpace}/${jsonReportName}`, JSON.stringify(currentReport)); console.log("The current report is updated with the ignored alerts!"); } const newAlertExits = actionHelper.checkIfAlertsExists(currentReport); console.log(`Alerts present in the current report: ${newAlertExits.toString()}`); if (!newAlertExits) { // If no new alerts have been found close the issue console.log("No new alerts have been identified by the ZAP Scan"); if (openIssue != null && openIssue.state === "open") { // close the issue with a comment console.log(`Starting to close the issue #${openIssue.number}`); try { await octokit.request("POST /repos/{owner}/{repo}/issues/{issue_number}/comments", { owner: owner, repo: repo, issue_number: openIssue.number, body: "All the alerts have been resolved during the last ZAP Scan!", }); await octokit.request("PATCH /repos/{owner}/{repo}/issues/{issue_number}", { owner: owner, repo: repo, issue_number: openIssue.number, state: "closed", }); console.log(`Successfully closed the issue #${openIssue.number}`); } catch (err) { console.error(`Error occurred while closing the issue with a comment! err: ${err.toString()}`); } } else if (openIssue != null && openIssue.state === "closed") { console.log("No alerts found by ZAP Scan and no active issue is found in the repository, exiting the program!"); } return; } const runnerInfo = `RunnerID:${currentRunnerID}`; const runnerLink = `View the [following link](${process.env.GITHUB_SERVER_URL}/${owner}/${repo}/actions/runs/${currentRunnerID})` + ` to download the report.`; if (create_new_issue) { const msg = actionHelper.createMessage(currentReport.site, runnerInfo, runnerLink) + "\n\n---\n[ZAP by Checkmarx](https://checkmarx.com/)"; const newIssue = await octokit.request("POST /repos/{owner}/{repo}/issues", { owner: owner, repo: repo, title: issueTitle, body: msg, }); console.log(`Process completed successfully and a new issue #${newIssue.data.number} has been created for the ZAP Scan.`); } else { const siteClone = actionHelper.generateDifference(currentReport, previousReport); if (currentReport.updated) { console.log("The current report has changes compared to the previous report"); try { const msg = actionHelper.createMessage(siteClone, runnerInfo, runnerLink); await octokit.request("POST /repos/{owner}/{repo}/issues/{issue_number}/comments", { owner: owner, repo: repo, issue_number: openIssue.number, body: msg, }); console.log(`The issue #${openIssue.number} has been updated with the latest ZAP scan results!`); console.log("ZAP Scan process completed successfully!"); } catch (err) { console.log(`Error occurred while updating the issue #${openIssue.number} with the latest ZAP scan: ${err.toString()}`); } } else { console.log("No changes have been observed from the previous scan and current scan!, exiting the program!"); } } await actionHelper.uploadArtifacts(workSpace, mdReportName, jsonReportName, htmlReportName, artifactName); }, }; export const main = actionCommon; export const helper = actionHelper;