diff --git a/src/github/client/github-client.types.ts b/src/github/client/github-client.types.ts index ddb187ea8c..a154476d98 100644 --- a/src/github/client/github-client.types.ts +++ b/src/github/client/github-client.types.ts @@ -101,10 +101,17 @@ export type DependabotAlertResponseItem = { summary: string, description: string, identifiers: { type: string, value: string }[], - references: { url: string }[] + references: { url: string }[], + severity: string, + cvss: { + score?: number + } }, security_vulnerability: { - severity: string + severity: string, + first_patched_version: { + identifier: number + } }, dependency: { scope: string, @@ -192,3 +199,11 @@ type CodeScanningAlertResponseItemMostRecentInstance = { commit_sha: string; html_url: string; } + +export type CodeScanningAlertInstanceResponseItem = { + ref: string; + environment: string; + category: string; + state: string; + commit_sha: string; +} diff --git a/src/github/client/github-installation-client.ts b/src/github/client/github-installation-client.ts index 99b9a89421..29ba422279 100644 --- a/src/github/client/github-installation-client.ts +++ b/src/github/client/github-installation-client.ts @@ -33,7 +33,8 @@ import { PaginatedAxiosResponse, ReposGetContentsResponse, SecretScanningAlertResponseItem, - CodeScanningAlertResponseItem + CodeScanningAlertResponseItem, + CodeScanningAlertInstanceResponseItem } from "./github-client.types"; import { GITHUB_ACCEPT_HEADER } from "./github-client-constants"; import { GitHubClient, GitHubConfig, Metrics } from "./github-client"; @@ -106,6 +107,14 @@ export class GitHubInstallationClient extends GitHubClient { }); } + public async getCodeScanningAlertInstances(owner: string, repo: string, alertNumber: number): Promise> { + return await this.get(`/repos/{owner}/{repo}/code-scanning/alerts/{alertNumber}/instances`, { }, { + owner, + repo, + alertNumber + }); + } + /** * Lists pull requests for the given repository. */ diff --git a/src/github/dependabot-alert.test.ts b/src/github/dependabot-alert.test.ts index e5f5472ea2..9b7e3b4caa 100644 --- a/src/github/dependabot-alert.test.ts +++ b/src/github/dependabot-alert.test.ts @@ -54,7 +54,7 @@ describe("DependabotAlertWebhookHandler", () => { beforeEach(() => { jiraClient = { baseURL: jiraHost, - security: { submitVulnerabilities: jest.fn(() =>({ status: 200 })) } + security: { submitVulnerabilities: jest.fn(() => ({ status: 200 })) } } as unknown as JiraClient; when(booleanFlag).calledWith(BooleanFlags.ENABLE_GITHUB_SECURITY_IN_JIRA, expect.anything()).mockResolvedValue(true); }); @@ -109,14 +109,57 @@ describe("DependabotAlertWebhookHandler", () => { security_advisory: { summary: SAMPLE_SECURITY_ADVISORY_SUMMARY, description: SAMPLE_SECURITY_ADVISORY_DESCRIPTION, - identifiers: [], - references: [] + severity: HIGH, + cvss: { + score: "7.4" + }, + identifiers: [{ + value: "GHSA-jf85-cpcp-j695", + type: "GHSA" + }, { + value: "CVE-2019-10744", + type: "CVE" + }], + references: [{ + url: "https://github.com/lodash/lodash/pull/4336" + }, + { + url: "https://nvd.nist.gov/vuln/detail/CVE-2019-10744" + }, + { + url: "https://snyk.io/vuln/SNYK-JS-LODASH-450202" + }, + { + url: "https://www.npmjs.com/advisories/1065" + }, + { + url: "https://access.redhat.com/errata/RHSA-2019:3024" + }, + { + url: "https://security.netapp.com/advisory/ntap-20191004-0005/" + }, + { + url: "https://support.f5.com/csp/article/K47105354?utm_source=f5support&utm_medium=RSS" + }, + { + url: "https://www.oracle.com/security-alerts/cpujan2021.html" + }, + { + url: "https://www.oracle.com/security-alerts/cpuoct2020.html" + }, + { + url: "https://github.com/advisories/GHSA-jf85-cpcp-j695" + } + ] }, html_url: SAMPLE_SECURITY_URL, created_at: SAMPLE_SECURITY_CREATED_DATE, updated_at: SAMPLE_SECURITY_UPDATED_DATE, security_vulnerability: { - severity: HIGH + severity: HIGH, + first_patched_version: { + identifier: "4.17.12" + } }, dependency: { manifest_path: PATH_TO_MANIFEST @@ -157,7 +200,7 @@ describe("DependabotAlertWebhookHandler", () => { updateSequenceNumber: Date.now(), containerId: "456", displayName: SAMPLE_SECURITY_ADVISORY_SUMMARY, - description: SAMPLE_SECURITY_ADVISORY_DESCRIPTION, + description: "**Vulnerability:** Sample security advisory summary\n\n**Impact:** Sample security advisory description\n\n**Severity:** High - 7.4\n\nGitHub uses [Common Vulnerability Scoring System (CVSS)](https://www.atlassian.com/trust/security/security-severity-levels) data to calculate security severity.\n\n**State:** Open\n\n**Patched version:** 4.17.12\n\n**Identifiers:**\n\n- [GHSA-jf85-cpcp-j695](https://github.com/advisories/GHSA-jf85-cpcp-j695)\n- [CVE-2019-10744](https://nvd.nist.gov/vuln/detail/CVE-2019-10744)\n\nVisit the vulnerability’s [dependabot alert page](https://github.com/user/repo/security/advisories/123) in GitHub to learn more about and see remediation options.", url: SAMPLE_SECURITY_URL, type: "sca", introducedDate: SAMPLE_SECURITY_CREATED_DATE, @@ -165,7 +208,13 @@ describe("DependabotAlertWebhookHandler", () => { severity: { level: HIGH }, - identifiers: [], + identifiers: [{ + "displayName": "GHSA-jf85-cpcp-j695", + "url": "https://github.com/advisories/GHSA-jf85-cpcp-j695" + }, { + "displayName": "CVE-2019-10744", + "url": "https://nvd.nist.gov/vuln/detail/CVE-2019-10744" + }], status: JIRA_VULNERABILITY_STATUS_ENUM_OPEN, additionalInfo: { content: PATH_TO_MANIFEST diff --git a/src/github/secret-scanning-alert.test.ts b/src/github/secret-scanning-alert.test.ts index 99295dc319..f899432223 100644 --- a/src/github/secret-scanning-alert.test.ts +++ b/src/github/secret-scanning-alert.test.ts @@ -154,7 +154,7 @@ describe("SecretScanningAlertWebhookHandler", () => { updateSequenceNumber: Date.now(), containerId: "456", displayName: "GitHub Personal Access Token", - description: "Secret scanning alert", + description: "**Vulnerability:** Fix GitHub Personal Access Token\n\n**State:** Open\n\n**Secret type:** github_personal_access_token\n\nVisit the vulnerability’s [secret scanning alert page](https://github.com/user/repo/security/advisories/123) in GitHub to learn more about the potential active secret and remediation steps.", url: SAMPLE_SECURITY_URL, type: "sast", introducedDate: SAMPLE_SECURITY_CREATED_DATE, diff --git a/src/sync/code-scanning-alerts.test.ts b/src/sync/code-scanning-alerts.test.ts index 8a45de2ab0..b330d3eb2c 100644 --- a/src/sync/code-scanning-alerts.test.ts +++ b/src/sync/code-scanning-alerts.test.ts @@ -50,7 +50,12 @@ describe("sync/code-scanning-alerts", () => { githubNock .get("/repos/integrations/test-repo-name/code-scanning/alerts?per_page=20&page=1&sort=created&direction=desc") .reply(200, codeScanningAlerts); - githubUserTokenNock(DatabaseStateCreator.GITHUB_INSTALLATION_ID); + githubUserTokenNock(DatabaseStateCreator.GITHUB_INSTALLATION_ID).persist(); + for (const codeScanningAlert of codeScanningAlerts) { + githubNock + .get(`/repos/integrations/test-repo-name/code-scanning/alerts/${codeScanningAlert.number}/instances`) + .reply(200, [{ "ref": "refs/heads/main" }, { "ref": "refs/pull/123" }, { "ref": "refs/heads/dev" }]); + } jiraNock .post("/rest/security/1.0/bulk", expectedResponseCloudServer(subscription)) .reply(200); @@ -124,14 +129,18 @@ describe("sync/code-scanning-alerts", () => { gitHubApiUrl: gitHubServerApp.gitHubBaseUrl } }; - gheUserTokenNock(DatabaseStateCreator.GITHUB_INSTALLATION_ID); + gheUserTokenNock(DatabaseStateCreator.GITHUB_INSTALLATION_ID).persist(); gheNock .get("/api/v3/repos/integrations/test-repo-name/code-scanning/alerts?per_page=20&page=1&sort=created&direction=desc") .reply(200, codeScanningAlerts); + for (const codeScanningAlert of codeScanningAlerts) { + gheNock + .get(`/api/v3/repos/integrations/test-repo-name/code-scanning/alerts/${codeScanningAlert.number}/instances`) + .reply(200, [{ "ref": "refs/heads/main" }, { "ref": "refs/pull/123" }, { "ref": "refs/heads/dev" }]); + } jiraNock .post("/rest/security/1.0/bulk", expectedResponseGHEServer(subscription)) .reply(200); - await expect(processInstallation(mockBackfillQueueSendMessage)(data, sentry, getLogger("test"))).toResolve(); await verifyMessageSent(data); }); @@ -174,8 +183,8 @@ const expectedResponseCloudServer = (subscription: Subscription) => ({ "id": "c-1-9", "updateSequenceNumber": 12345678, "containerId": "1", - "displayName": "js/reflected-xss", - "description": "Reflected cross-site scripting", + "displayName": "Reflected cross-site scripting", + "description": "**Vulnerability:** Reflected cross-site scripting\n\n**Impact:** The vulnerability in CodeQL impacts main branch and dev branch.\n\n**Severity:** Medium\n\nGitHub uses [Common Vulnerability Scoring System (CVSS)](https://www.atlassian.com/trust/security/security-severity-levels) data to calculate security severity.\n\n**Status:** Open\n\n**Weaknesses:** [CWE-79](https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=79), [CWE-116](https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=116)\n\nVisit the vulnerability’s [code scanning alert page](https://github.com/auzwang/sequelize-playground/security/code-scanning/9) in GitHub for a recommendation and relevant example.", "url": "https://github.com/auzwang/sequelize-playground/security/code-scanning/9", "type": "sast", "introducedDate": "2023-08-18T04:33:51Z", @@ -203,8 +212,8 @@ const expectedResponseCloudServer = (subscription: Subscription) => ({ "id": "c-1-8", "updateSequenceNumber": 12345678, "containerId": "1", - "displayName": "js/hardcoded-credentials", - "description": "Hard-coded credentials", + "displayName": "Hard-coded credentials", + "description": "**Vulnerability:** Hard-coded credentials\n\n**Impact:** The vulnerability in CodeQL impacts main branch and dev branch.\n\n**Severity:** Critical\n\nGitHub uses [Common Vulnerability Scoring System (CVSS)](https://www.atlassian.com/trust/security/security-severity-levels) data to calculate security severity.\n\n**Status:** Fixed\n\n**Weaknesses:** [CWE-259](https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=259), [CWE-321](https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=321), [CWE-798](https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=798)\n\nVisit the vulnerability’s [code scanning alert page](https://github.com/auzwang/sequelize-playground/security/code-scanning/8) in GitHub for a recommendation and relevant example.", "url": "https://github.com/auzwang/sequelize-playground/security/code-scanning/8", "type": "sast", "introducedDate": "2023-08-18T04:15:14Z", @@ -236,8 +245,8 @@ const expectedResponseCloudServer = (subscription: Subscription) => ({ "id": "c-1-7", "updateSequenceNumber": 12345678, "containerId": "1", - "displayName": "js/sql-injection", - "description": "Database query built from user-controlled sources", + "displayName": "Database query built from user-controlled sources", + "description": "**Vulnerability:** Database query built from user-controlled sources\n\n**Impact:** The vulnerability in CodeQL impacts main branch and dev branch.\n\n**Severity:** High\n\nGitHub uses [Common Vulnerability Scoring System (CVSS)](https://www.atlassian.com/trust/security/security-severity-levels) data to calculate security severity.\n\n**Status:** Open\n\n**Weaknesses:** [CWE-89](https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=89), [CWE-90](https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=90), [CWE-943](https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=943)\n\nVisit the vulnerability’s [code scanning alert page](https://github.com/auzwang/sequelize-playground/security/code-scanning/7) in GitHub for a recommendation and relevant example.", "url": "https://github.com/auzwang/sequelize-playground/security/code-scanning/7", "type": "sast", "introducedDate": "2023-08-09T04:26:41Z", @@ -269,8 +278,8 @@ const expectedResponseCloudServer = (subscription: Subscription) => ({ "id": "c-1-6", "updateSequenceNumber": 12345678, "containerId": "1", - "displayName": "js/reflected-xss", - "description": "Reflected cross-site scripting", + "displayName": "Reflected cross-site scripting", + "description": "**Vulnerability:** Reflected cross-site scripting\n\n**Impact:** The vulnerability in CodeQL impacts main branch and dev branch.\n\n**Severity:** Medium\n\nGitHub uses [Common Vulnerability Scoring System (CVSS)](https://www.atlassian.com/trust/security/security-severity-levels) data to calculate security severity.\n\n**Status:** Fixed\n\n**Weaknesses:** [CWE-79](https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=79), [CWE-116](https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=116)\n\nVisit the vulnerability’s [code scanning alert page](https://github.com/auzwang/sequelize-playground/security/code-scanning/6) in GitHub for a recommendation and relevant example.", "url": "https://github.com/auzwang/sequelize-playground/security/code-scanning/6", "type": "sast", "introducedDate": "2023-08-09T04:26:41Z", @@ -298,8 +307,8 @@ const expectedResponseCloudServer = (subscription: Subscription) => ({ "id": "c-1-3", "updateSequenceNumber": 12345678, "containerId": "1", - "displayName": "js/hardcoded-credentials", - "description": "Hard-coded credentials", + "displayName": "Hard-coded credentials", + "description": "**Vulnerability:** Hard-coded credentials\n\n**Impact:** The vulnerability in CodeQL impacts main branch and dev branch.\n\n**Severity:** Critical\n\nGitHub uses [Common Vulnerability Scoring System (CVSS)](https://www.atlassian.com/trust/security/security-severity-levels) data to calculate security severity.\n\n**Status:** Fixed\n\n**Weaknesses:** [CWE-259](https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=259), [CWE-321](https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=321), [CWE-798](https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=798)\n\nVisit the vulnerability’s [code scanning alert page](https://github.com/auzwang/sequelize-playground/security/code-scanning/3) in GitHub for a recommendation and relevant example.", "url": "https://github.com/auzwang/sequelize-playground/security/code-scanning/3", "type": "sast", "introducedDate": "2023-08-03T05:47:19Z", @@ -331,8 +340,8 @@ const expectedResponseCloudServer = (subscription: Subscription) => ({ "id": "c-1-2", "updateSequenceNumber": 12345678, "containerId": "1", - "displayName": "js/hardcoded-credentials", - "description": "Hard-coded credentials", + "displayName": "Hard-coded credentials", + "description": "**Vulnerability:** Hard-coded credentials\n\n**Impact:** The vulnerability in CodeQL impacts main branch and dev branch.\n\n**Severity:** Critical\n\nGitHub uses [Common Vulnerability Scoring System (CVSS)](https://www.atlassian.com/trust/security/security-severity-levels) data to calculate security severity.\n\n**Status:** Fixed\n\n**Weaknesses:** [CWE-259](https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=259), [CWE-321](https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=321), [CWE-798](https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=798)\n\nVisit the vulnerability’s [code scanning alert page](https://github.com/auzwang/sequelize-playground/security/code-scanning/2) in GitHub for a recommendation and relevant example.", "url": "https://github.com/auzwang/sequelize-playground/security/code-scanning/2", "type": "sast", "introducedDate": "2023-08-01T00:25:22Z", @@ -364,8 +373,8 @@ const expectedResponseCloudServer = (subscription: Subscription) => ({ "id": "c-1-1", "updateSequenceNumber": 12345678, "containerId": "1", - "displayName": "js/hardcoded-credentials", - "description": "Hard-coded credentials", + "displayName": "Hard-coded credentials", + "description": "**Vulnerability:** Hard-coded credentials\n\n**Impact:** The vulnerability in CodeQL impacts main branch and dev branch.\n\n**Severity:** Critical\n\nGitHub uses [Common Vulnerability Scoring System (CVSS)](https://www.atlassian.com/trust/security/security-severity-levels) data to calculate security severity.\n\n**Status:** Fixed\n\n**Weaknesses:** [CWE-259](https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=259), [CWE-321](https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=321), [CWE-798](https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=798)\n\nVisit the vulnerability’s [code scanning alert page](https://github.com/auzwang/sequelize-playground/security/code-scanning/1) in GitHub for a recommendation and relevant example.", "url": "https://github.com/auzwang/sequelize-playground/security/code-scanning/1", "type": "sast", "introducedDate": "2023-07-31T06:37:26Z", @@ -402,8 +411,8 @@ const expectedResponseGHEServer = (subscription: Subscription) => ({ "id": "c-6769746875626d79646f6d61696e636f6d-1-9", "updateSequenceNumber": 12345678, "containerId": "6769746875626d79646f6d61696e636f6d-1", - "displayName": "js/reflected-xss", - "description": "Reflected cross-site scripting", + "displayName": "Reflected cross-site scripting", + "description": "**Vulnerability:** Reflected cross-site scripting\n\n**Impact:** The vulnerability in CodeQL impacts main branch and dev branch.\n\n**Severity:** Medium\n\nGitHub uses [Common Vulnerability Scoring System (CVSS)](https://www.atlassian.com/trust/security/security-severity-levels) data to calculate security severity.\n\n**Status:** Open\n\n**Weaknesses:** [CWE-79](https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=79), [CWE-116](https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=116)\n\nVisit the vulnerability’s [code scanning alert page](https://github.com/auzwang/sequelize-playground/security/code-scanning/9) in GitHub for a recommendation and relevant example.", "url": "https://github.com/auzwang/sequelize-playground/security/code-scanning/9", "type": "sast", "introducedDate": "2023-08-18T04:33:51Z", @@ -431,8 +440,8 @@ const expectedResponseGHEServer = (subscription: Subscription) => ({ "id": "c-6769746875626d79646f6d61696e636f6d-1-8", "updateSequenceNumber": 12345678, "containerId": "6769746875626d79646f6d61696e636f6d-1", - "displayName": "js/hardcoded-credentials", - "description": "Hard-coded credentials", + "displayName": "Hard-coded credentials", + "description": "**Vulnerability:** Hard-coded credentials\n\n**Impact:** The vulnerability in CodeQL impacts main branch and dev branch.\n\n**Severity:** Critical\n\nGitHub uses [Common Vulnerability Scoring System (CVSS)](https://www.atlassian.com/trust/security/security-severity-levels) data to calculate security severity.\n\n**Status:** Fixed\n\n**Weaknesses:** [CWE-259](https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=259), [CWE-321](https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=321), [CWE-798](https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=798)\n\nVisit the vulnerability’s [code scanning alert page](https://github.com/auzwang/sequelize-playground/security/code-scanning/8) in GitHub for a recommendation and relevant example.", "url": "https://github.com/auzwang/sequelize-playground/security/code-scanning/8", "type": "sast", "introducedDate": "2023-08-18T04:15:14Z", @@ -464,8 +473,8 @@ const expectedResponseGHEServer = (subscription: Subscription) => ({ "id": "c-6769746875626d79646f6d61696e636f6d-1-7", "updateSequenceNumber": 12345678, "containerId": "6769746875626d79646f6d61696e636f6d-1", - "displayName": "js/sql-injection", - "description": "Database query built from user-controlled sources", + "displayName": "Database query built from user-controlled sources", + "description": "**Vulnerability:** Database query built from user-controlled sources\n\n**Impact:** The vulnerability in CodeQL impacts main branch and dev branch.\n\n**Severity:** High\n\nGitHub uses [Common Vulnerability Scoring System (CVSS)](https://www.atlassian.com/trust/security/security-severity-levels) data to calculate security severity.\n\n**Status:** Open\n\n**Weaknesses:** [CWE-89](https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=89), [CWE-90](https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=90), [CWE-943](https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=943)\n\nVisit the vulnerability’s [code scanning alert page](https://github.com/auzwang/sequelize-playground/security/code-scanning/7) in GitHub for a recommendation and relevant example.", "url": "https://github.com/auzwang/sequelize-playground/security/code-scanning/7", "type": "sast", "introducedDate": "2023-08-09T04:26:41Z", @@ -497,8 +506,8 @@ const expectedResponseGHEServer = (subscription: Subscription) => ({ "id": "c-6769746875626d79646f6d61696e636f6d-1-6", "updateSequenceNumber": 12345678, "containerId": "6769746875626d79646f6d61696e636f6d-1", - "displayName": "js/reflected-xss", - "description": "Reflected cross-site scripting", + "displayName": "Reflected cross-site scripting", + "description": "**Vulnerability:** Reflected cross-site scripting\n\n**Impact:** The vulnerability in CodeQL impacts main branch and dev branch.\n\n**Severity:** Medium\n\nGitHub uses [Common Vulnerability Scoring System (CVSS)](https://www.atlassian.com/trust/security/security-severity-levels) data to calculate security severity.\n\n**Status:** Fixed\n\n**Weaknesses:** [CWE-79](https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=79), [CWE-116](https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=116)\n\nVisit the vulnerability’s [code scanning alert page](https://github.com/auzwang/sequelize-playground/security/code-scanning/6) in GitHub for a recommendation and relevant example.", "url": "https://github.com/auzwang/sequelize-playground/security/code-scanning/6", "type": "sast", "introducedDate": "2023-08-09T04:26:41Z", @@ -526,8 +535,8 @@ const expectedResponseGHEServer = (subscription: Subscription) => ({ "id": "c-6769746875626d79646f6d61696e636f6d-1-3", "updateSequenceNumber": 12345678, "containerId": "6769746875626d79646f6d61696e636f6d-1", - "displayName": "js/hardcoded-credentials", - "description": "Hard-coded credentials", + "displayName": "Hard-coded credentials", + "description": "**Vulnerability:** Hard-coded credentials\n\n**Impact:** The vulnerability in CodeQL impacts main branch and dev branch.\n\n**Severity:** Critical\n\nGitHub uses [Common Vulnerability Scoring System (CVSS)](https://www.atlassian.com/trust/security/security-severity-levels) data to calculate security severity.\n\n**Status:** Fixed\n\n**Weaknesses:** [CWE-259](https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=259), [CWE-321](https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=321), [CWE-798](https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=798)\n\nVisit the vulnerability’s [code scanning alert page](https://github.com/auzwang/sequelize-playground/security/code-scanning/3) in GitHub for a recommendation and relevant example.", "url": "https://github.com/auzwang/sequelize-playground/security/code-scanning/3", "type": "sast", "introducedDate": "2023-08-03T05:47:19Z", @@ -559,8 +568,8 @@ const expectedResponseGHEServer = (subscription: Subscription) => ({ "id": "c-6769746875626d79646f6d61696e636f6d-1-2", "updateSequenceNumber": 12345678, "containerId": "6769746875626d79646f6d61696e636f6d-1", - "displayName": "js/hardcoded-credentials", - "description": "Hard-coded credentials", + "displayName": "Hard-coded credentials", + "description": "**Vulnerability:** Hard-coded credentials\n\n**Impact:** The vulnerability in CodeQL impacts main branch and dev branch.\n\n**Severity:** Critical\n\nGitHub uses [Common Vulnerability Scoring System (CVSS)](https://www.atlassian.com/trust/security/security-severity-levels) data to calculate security severity.\n\n**Status:** Fixed\n\n**Weaknesses:** [CWE-259](https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=259), [CWE-321](https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=321), [CWE-798](https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=798)\n\nVisit the vulnerability’s [code scanning alert page](https://github.com/auzwang/sequelize-playground/security/code-scanning/2) in GitHub for a recommendation and relevant example.", "url": "https://github.com/auzwang/sequelize-playground/security/code-scanning/2", "type": "sast", "introducedDate": "2023-08-01T00:25:22Z", @@ -592,8 +601,8 @@ const expectedResponseGHEServer = (subscription: Subscription) => ({ "id": "c-6769746875626d79646f6d61696e636f6d-1-1", "updateSequenceNumber": 12345678, "containerId": "6769746875626d79646f6d61696e636f6d-1", - "displayName": "js/hardcoded-credentials", - "description": "Hard-coded credentials", + "displayName": "Hard-coded credentials", + "description": "**Vulnerability:** Hard-coded credentials\n\n**Impact:** The vulnerability in CodeQL impacts main branch and dev branch.\n\n**Severity:** Critical\n\nGitHub uses [Common Vulnerability Scoring System (CVSS)](https://www.atlassian.com/trust/security/security-severity-levels) data to calculate security severity.\n\n**Status:** Fixed\n\n**Weaknesses:** [CWE-259](https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=259), [CWE-321](https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=321), [CWE-798](https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=798)\n\nVisit the vulnerability’s [code scanning alert page](https://github.com/auzwang/sequelize-playground/security/code-scanning/1) in GitHub for a recommendation and relevant example.", "url": "https://github.com/auzwang/sequelize-playground/security/code-scanning/1", "type": "sast", "introducedDate": "2023-07-31T06:37:26Z", diff --git a/src/sync/code-scanning-alerts.ts b/src/sync/code-scanning-alerts.ts index 0b64a08ebb..fa53651ac4 100644 --- a/src/sync/code-scanning-alerts.ts +++ b/src/sync/code-scanning-alerts.ts @@ -12,6 +12,7 @@ import { transformRuleTagsToIdentifiers, transformGitHubStateToJiraStatus } from "~/src/transforms/util/github-security-alerts"; +import { getCodeScanningVulnDescription } from "../transforms/transform-code-scanning-alert"; export const getCodeScanningAlertTask = async ( parentLogger: Logger, @@ -55,7 +56,7 @@ export const getCodeScanningAlertTask = async ( const nextPageCursorStr = smartCursor.copyWithPageNo(smartCursor.pageNo + 1).serialise(); const edgesWithCursor = [{ codeScanningAlerts: codeScanningAlerts, cursor: nextPageCursorStr }]; - const jiraPayload = await transformCodeScanningAlert(codeScanningAlerts, repository, jiraHost, logger, messagePayload.gitHubAppConfig?.gitHubAppId); + const jiraPayload = await transformCodeScanningAlert(codeScanningAlerts, repository, jiraHost, logger, messagePayload.gitHubAppConfig?.gitHubAppId, gitHubClient); logger.info({ processingTime: Date.now() - startTime, jiraPayloadLength: jiraPayload?.vulnerabilities?.length }, "Backfill task complete"); return { edges: edgesWithCursor, @@ -80,7 +81,8 @@ const transformCodeScanningAlert = async ( repository: Repository, jiraHost: string, logger: Logger, - gitHubAppId: number | undefined + gitHubAppId: number | undefined, + gitHubClient: GitHubInstallationClient ): Promise => { const gitHubClientConfig = await getGitHubClientConfigFromAppId(gitHubAppId, jiraHost); @@ -88,7 +90,8 @@ const transformCodeScanningAlert = async ( const handleUnmappedState = (state: string) => logger.info(`Received unmapped state from code_scanning_alert sync: ${state}`); const handleUnmappedSeverity = (severity: string | null) => logger.info(`Received unmapped severity from code_scanning_alert sync: ${severity ?? "Missing Severity"}`); - const vulnerabilities = alerts.map((alert) => { + const vulnerabilities = await Promise.all(alerts.map(async (alert) => { + const { data: alertInstances } = await gitHubClient.getCodeScanningAlertInstances(repository.owner.login, repository.name, alert.number); const identifiers = transformRuleTagsToIdentifiers(alert.rule.tags); return { @@ -96,8 +99,8 @@ const transformCodeScanningAlert = async ( id: `c-${transformRepositoryId(repository.id, gitHubClientConfig.baseUrl)}-${alert.number}`, updateSequenceNumber: Date.now(), containerId: transformRepositoryId(repository.id, gitHubClientConfig.baseUrl), - displayName: alert.rule.name, - description: alert.rule.full_description || alert.rule.description, + displayName: alert.rule.description || alert.rule.name, + description: getCodeScanningVulnDescription(alert, identifiers, alertInstances, logger), url: alert.html_url, type: "sast", introducedDate: alert.created_at, @@ -111,7 +114,7 @@ const transformCodeScanningAlert = async ( content: alert.tool.name } }; - }); + })); return { vulnerabilities }; }; diff --git a/src/sync/dependabot-alerts.test.ts b/src/sync/dependabot-alerts.test.ts index c928d1e39a..a4cddf1592 100644 --- a/src/sync/dependabot-alerts.test.ts +++ b/src/sync/dependabot-alerts.test.ts @@ -183,7 +183,7 @@ const expectedResponseCloudServer = (subscription: Subscription) => ({ "updateSequenceNumber": 12345678, "containerId": "1", "displayName": "semver vulnerable to Regular Expression Denial of Service", - "description": "Versions of the package semver before 7.5.2 on the 7.x branch, before 6.3.1 on the 6.x branch, and all other versions before 5.7.2 are vulnerable to Regular Expression Denial of Service (ReDoS) via the function new Range, when untrusted user data is provided as a range.", + "description": "**Vulnerability:** semver vulnerable to Regular Expression Denial of Service\n\n**Impact:** Versions of the package semver before 7.5.2 on the 7.x branch, before 6.3.1 on the 6.x branch, and all other versions before 5.7.2 are vulnerable to Regular Expression Denial of Service (ReDoS) via the function new Range, when untrusted user data is provided as a range.\n\n**Severity:** - undefined\n\nGitHub uses [Common Vulnerability Scoring System (CVSS)](https://www.atlassian.com/trust/security/security-severity-levels) data to calculate security severity.\n\n**State:** Open\n\n**Patched version:** undefined\n\n**Identifiers:**\n\n- [GHSA-c2qf-rxjj-qqgw](https://github.com/advisories/GHSA-c2qf-rxjj-qqgw)\n- [CVE-2022-25883](https://nvd.nist.gov/vuln/detail/CVE-2022-25883)\n\nVisit the vulnerability’s [dependabot alert page](undefined) in GitHub to learn more about and see remediation options.", "type": "sca", "introducedDate": "2023-07-13T06:24:50Z", "lastUpdated": "2023-07-13T06:24:50Z", @@ -211,7 +211,7 @@ const expectedResponseCloudServer = (subscription: Subscription) => ({ "updateSequenceNumber": 12345678, "containerId": "1", "displayName": "semver vulnerable to Regular Expression Denial of Service", - "description": "Versions of the package semver before 7.5.2 on the 7.x branch, before 6.3.1 on the 6.x branch, and all other versions before 5.7.2 are vulnerable to Regular Expression Denial of Service (ReDoS) via the function new Range, when untrusted user data is provided as a range.", + "description": "**Vulnerability:** semver vulnerable to Regular Expression Denial of Service\n\n**Impact:** Versions of the package semver before 7.5.2 on the 7.x branch, before 6.3.1 on the 6.x branch, and all other versions before 5.7.2 are vulnerable to Regular Expression Denial of Service (ReDoS) via the function new Range, when untrusted user data is provided as a range.\n\n**Severity:** - undefined\n\nGitHub uses [Common Vulnerability Scoring System (CVSS)](https://www.atlassian.com/trust/security/security-severity-levels) data to calculate security severity.\n\n**State:** Open\n\n**Patched version:** undefined\n\n**Identifiers:**\n\n- [GHSA-c2qf-rxjj-qqgw](https://github.com/advisories/GHSA-c2qf-rxjj-qqgw)\n- [CVE-2022-25883](https://nvd.nist.gov/vuln/detail/CVE-2022-25883)\n\nVisit the vulnerability’s [dependabot alert page](undefined) in GitHub to learn more about and see remediation options.", "type": "sca", "introducedDate": "2023-07-13T06:24:50Z", "lastUpdated": "2023-07-13T06:24:50Z", @@ -249,7 +249,7 @@ const expectedResponseGHEServer = (subscription: Subscription) => ({ "updateSequenceNumber": 12345678, "containerId": "6769746875626d79646f6d61696e636f6d-1", "displayName": "semver vulnerable to Regular Expression Denial of Service", - "description": "Versions of the package semver before 7.5.2 on the 7.x branch, before 6.3.1 on the 6.x branch, and all other versions before 5.7.2 are vulnerable to Regular Expression Denial of Service (ReDoS) via the function new Range, when untrusted user data is provided as a range.", + "description": "**Vulnerability:** semver vulnerable to Regular Expression Denial of Service\n\n**Impact:** Versions of the package semver before 7.5.2 on the 7.x branch, before 6.3.1 on the 6.x branch, and all other versions before 5.7.2 are vulnerable to Regular Expression Denial of Service (ReDoS) via the function new Range, when untrusted user data is provided as a range.\n\n**Severity:** - undefined\n\nGitHub uses [Common Vulnerability Scoring System (CVSS)](https://www.atlassian.com/trust/security/security-severity-levels) data to calculate security severity.\n\n**State:** Open\n\n**Patched version:** undefined\n\n**Identifiers:**\n\n- [GHSA-c2qf-rxjj-qqgw](https://github.com/advisories/GHSA-c2qf-rxjj-qqgw)\n- [CVE-2022-25883](https://nvd.nist.gov/vuln/detail/CVE-2022-25883)\n\nVisit the vulnerability’s [dependabot alert page](undefined) in GitHub to learn more about and see remediation options.", "type": "sca", "introducedDate": "2023-07-13T06:24:50Z", "lastUpdated": "2023-07-13T06:24:50Z", @@ -277,7 +277,7 @@ const expectedResponseGHEServer = (subscription: Subscription) => ({ "updateSequenceNumber": 12345678, "containerId": "6769746875626d79646f6d61696e636f6d-1", "displayName": "semver vulnerable to Regular Expression Denial of Service", - "description": "Versions of the package semver before 7.5.2 on the 7.x branch, before 6.3.1 on the 6.x branch, and all other versions before 5.7.2 are vulnerable to Regular Expression Denial of Service (ReDoS) via the function new Range, when untrusted user data is provided as a range.", + "description": "**Vulnerability:** semver vulnerable to Regular Expression Denial of Service\n\n**Impact:** Versions of the package semver before 7.5.2 on the 7.x branch, before 6.3.1 on the 6.x branch, and all other versions before 5.7.2 are vulnerable to Regular Expression Denial of Service (ReDoS) via the function new Range, when untrusted user data is provided as a range.\n\n**Severity:** - undefined\n\nGitHub uses [Common Vulnerability Scoring System (CVSS)](https://www.atlassian.com/trust/security/security-severity-levels) data to calculate security severity.\n\n**State:** Open\n\n**Patched version:** undefined\n\n**Identifiers:**\n\n- [GHSA-c2qf-rxjj-qqgw](https://github.com/advisories/GHSA-c2qf-rxjj-qqgw)\n- [CVE-2022-25883](https://nvd.nist.gov/vuln/detail/CVE-2022-25883)\n\nVisit the vulnerability’s [dependabot alert page](undefined) in GitHub to learn more about and see remediation options.", "type": "sca", "introducedDate": "2023-07-13T06:24:50Z", "lastUpdated": "2023-07-13T06:24:50Z", diff --git a/src/sync/dependabot-alerts.ts b/src/sync/dependabot-alerts.ts index e135edf142..dc74afe4dc 100644 --- a/src/sync/dependabot-alerts.ts +++ b/src/sync/dependabot-alerts.ts @@ -5,7 +5,7 @@ import { BackfillMessagePayload } from "~/src/sqs/sqs.types"; import { transformRepositoryId } from "../transforms/transform-repository-id"; import { getGitHubClientConfigFromAppId } from "../util/get-github-client-config"; import { JiraVulnerabilityBulkSubmitData } from "../interfaces/jira"; -import { mapVulnIdentifiers } from "../transforms/transform-dependabot-alert"; +import { getDependabotScanningVulnDescription, mapVulnIdentifiers } from "../transforms/transform-dependabot-alert"; import { PageSizeAwareCounterCursor } from "./page-counter-cursor"; import { DependabotAlertResponseItem, SortDirection } from "../github/client/github-client.types"; import { @@ -90,13 +90,14 @@ const transformDependabotAlerts = async ( const handleUnmappedSeverity = (severity: string | null) => logger.info(`Received unmapped severity from dependabot_alerts sync: ${severity ?? "Missing Severity"}`); const vulnerabilities = alerts.map((alert) => { + const identifiers = mapVulnIdentifiers(alert.security_advisory.identifiers, alert.security_advisory.references); return { schemaVersion: "1.0", id: `d-${transformRepositoryId(repository.id, gitHubClientConfig.baseUrl)}-${alert.number}`, updateSequenceNumber: Date.now(), containerId: transformRepositoryId(repository.id, gitHubClientConfig.baseUrl), displayName: alert.security_advisory.summary, - description: alert.security_advisory.description, + description: getDependabotScanningVulnDescription(alert, identifiers,logger), url: alert.html_url, type: "sca", introducedDate: alert.created_at, @@ -104,7 +105,7 @@ const transformDependabotAlerts = async ( severity: { level: transformGitHubSeverityToJiraSeverity(alert.security_vulnerability?.severity?.toLowerCase(), handleUnmappedSeverity) }, - identifiers: mapVulnIdentifiers(alert.security_advisory.identifiers, alert.security_advisory.references), + identifiers, status: transformGitHubStateToJiraStatus(alert.state?.toLowerCase(), handleUnmappedState), additionalInfo: { content: alert.dependency.manifest_path diff --git a/src/sync/secret-scanning-alerts.test.ts b/src/sync/secret-scanning-alerts.test.ts index 28a0b573d1..bfb87f5900 100644 --- a/src/sync/secret-scanning-alerts.test.ts +++ b/src/sync/secret-scanning-alerts.test.ts @@ -172,7 +172,7 @@ const expectedResponseCloudServer = (subscription: Subscription) => ({ "updateSequenceNumber": 12345678, "containerId": "1", "displayName": "GitHub Personal Access Token", - "description": "Secret scanning alert", + "description": "**Vulnerability:** Fix GitHub Personal Access Token\n\n**State:** Open\n\n**Secret type:** github_personal_access_token\n\nVisit the vulnerability’s [secret scanning alert page](https://github.com/test-owner/sample-repo/security/secret-scanning/12) in GitHub to learn more about the potential active secret and remediation steps.", "url": "https://github.com/test-owner/sample-repo/security/secret-scanning/12", "type": "sast", "introducedDate": "2023-08-04T04:33:44Z", @@ -205,7 +205,7 @@ const expectedResponseGHEServer = (subscription: Subscription) => ({ "updateSequenceNumber": 12345678, "containerId": "6769746875626d79646f6d61696e636f6d-1", "displayName": "GitHub Personal Access Token", - "description": "Secret scanning alert", + "description": "**Vulnerability:** Fix GitHub Personal Access Token\n\n**State:** Open\n\n**Secret type:** github_personal_access_token\n\nVisit the vulnerability’s [secret scanning alert page](https://github.com/test-owner/sample-repo/security/secret-scanning/12) in GitHub to learn more about the potential active secret and remediation steps.", "url": "https://github.com/test-owner/sample-repo/security/secret-scanning/12", "type": "sast", "introducedDate": "2023-08-04T04:33:44Z", diff --git a/src/sync/secret-scanning-alerts.ts b/src/sync/secret-scanning-alerts.ts index 9377330e7f..d3bfc79ff8 100644 --- a/src/sync/secret-scanning-alerts.ts +++ b/src/sync/secret-scanning-alerts.ts @@ -7,7 +7,7 @@ import { getGitHubClientConfigFromAppId } from "../util/get-github-client-config import { JiraVulnerabilityBulkSubmitData, JiraVulnerabilitySeverityEnum } from "../interfaces/jira"; import { PageSizeAwareCounterCursor } from "./page-counter-cursor"; import { SecretScanningAlertResponseItem, SortDirection } from "../github/client/github-client.types"; -import { transformGitHubStateToJiraStatus } from "../transforms/transform-secret-scanning-alert"; +import { getSecretScanningVulnDescription, transformGitHubStateToJiraStatus } from "../transforms/transform-secret-scanning-alert"; export const getSecretScanningAlertTask = async ( parentLogger: Logger, @@ -89,7 +89,7 @@ const transformSecretScanningAlert = async ( updateSequenceNumber: Date.now(), containerId: transformRepositoryId(repository.id, gitHubClientConfig.baseUrl), displayName: alert.secret_type_display_name || `${alert.secret_type} secret exposed`, - description: "Secret scanning alert", + description: getSecretScanningVulnDescription(alert, logger), url: alert.html_url, type: "sast", introducedDate: alert.created_at, diff --git a/src/transforms/transform-code-scanning-alert.test.ts b/src/transforms/transform-code-scanning-alert.test.ts index b80251f005..0a7b62900c 100644 --- a/src/transforms/transform-code-scanning-alert.test.ts +++ b/src/transforms/transform-code-scanning-alert.test.ts @@ -18,6 +18,8 @@ const buildContext = ( payload, gitHubAppConfig?: GitHubAppConfig ): WebhookContext => { + const logger = { info: jest.fn(), warn: jest.fn() } as unknown as Logger; + logger.child = jest.fn(() => logger); return new WebhookContext({ id: "hi", name: "hi", @@ -30,8 +32,9 @@ const buildContext = ( gitHubApiUrl: "https://api.github.com", uuid: undefined }, - log: { info: jest.fn() } as unknown as Logger + log: logger }); + }; describe("code_scanning_alert transform", () => { @@ -174,46 +177,50 @@ describe("code_scanning_alert transform", () => { describe("security vulnerabilities", () => { it("transform code scanning alert to jira security payload", async () => { + githubUserTokenNock(gitHubInstallationId); + githubNock + .get(`/repos/${codeScanningCreatedPayload.repository.owner.login}/${codeScanningCreatedPayload.repository.name}/code-scanning/alerts/${codeScanningCreatedPayload.alert.number}/instances`) + .reply(200, [{ "ref": "refs/heads/main" }, { "ref": "refs/pull/123" }, { "ref": "refs/heads/dev" }]); const result = await transformCodeScanningAlertToJiraSecurity( buildContext(codeScanningCreatedPayload), gitHubInstallationId, jiraHost ); - expect(result?.vulnerabilities[0]).toMatchInlineSnapshot(` - Object { - "additionalInfo": Object { - "content": "CodeQL", - }, - "containerId": "119484675", - "description": "Hard-coding credentials in source code may enable an attacker to gain unauthorized access.", - "displayName": "js/hardcoded-credentials", - "id": "c-119484675-2", - "identifiers": Array [ - Object { - "displayName": "CWE-259", - "url": "https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=259", - }, - Object { - "displayName": "CWE-321", - "url": "https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=321", - }, - Object { - "displayName": "CWE-798", - "url": "https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=798", - }, - ], - "introducedDate": "2023-08-01T00:25:22Z", - "lastUpdated": "2023-08-01T00:25:22Z", - "schemaVersion": "1.0", - "severity": Object { - "level": "critical", - }, - "status": "open", - "type": "sast", - "updateSequenceNumber": 12345678, - "url": "https://github.com/auzwang/sequelize-playground/security/code-scanning/2", - } - `); + expect(result?.vulnerabilities[0]).toMatchObject( + { + "additionalInfo": { + "content": "CodeQL" + }, + "containerId": "119484675", + "displayName": "Hard-coded credentials", + "description": "**Vulnerability:** Hard-coded credentials\n\n**Impact:** The vulnerability in CodeQL impacts main branch and dev branch.\n\n**Severity:** Critical\n\nGitHub uses [Common Vulnerability Scoring System (CVSS)](https://www.atlassian.com/trust/security/security-severity-levels) data to calculate security severity.\n\n**Status:** Open\n\n**Weaknesses:** [CWE-259](https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=259), [CWE-321](https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=321), [CWE-798](https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=798)\n\nVisit the vulnerability’s [code scanning alert page](https://github.com/auzwang/sequelize-playground/security/code-scanning/2) in GitHub for a recommendation and relevant example.", + "id": "c-119484675-2", + "identifiers": [ + { + "displayName": "CWE-259", + "url": "https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=259" + }, + { + "displayName": "CWE-321", + "url": "https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=321" + }, + { + "displayName": "CWE-798", + "url": "https://cwe.mitre.org/cgi-bin/jumpmenu.cgi?id=798" + } + ], + "introducedDate": "2023-08-01T00:25:22Z", + "lastUpdated": "2023-08-01T00:25:22Z", + "schemaVersion": "1.0", + "severity": { + "level": "critical" + }, + "status": "open", + "type": "sast", + "updateSequenceNumber": 12345678, + "url": "https://github.com/auzwang/sequelize-playground/security/code-scanning/2" + } + ); }); it.each([ @@ -225,6 +232,7 @@ describe("code_scanning_alert transform", () => { ])( "transform code scanning alert state %s to security vulnerability status %s", async (state, expectedStatus) => { + setupNock(gitHubInstallationId, codeScanningCreatedPayload); const payload = { ...codeScanningCreatedPayload, alert: { ...codeScanningCreatedPayload.alert, state } @@ -240,6 +248,7 @@ describe("code_scanning_alert transform", () => { ); it("map fixed code scanning alert fixed_at to jira security lastUpdated", async () => { + setupNock(gitHubInstallationId, codeScanningFixedPayload); const result = await transformCodeScanningAlertToJiraSecurity( buildContext(codeScanningFixedPayload), gitHubInstallationId, @@ -251,6 +260,7 @@ describe("code_scanning_alert transform", () => { }); it("map closed by user code scanning alert dismissed_at to jira security lastUpdated", async () => { + setupNock(gitHubInstallationId, codeScanningClosedByUserPayload); const result = await transformCodeScanningAlertToJiraSecurity( buildContext(codeScanningClosedByUserPayload), gitHubInstallationId, @@ -271,6 +281,7 @@ describe("code_scanning_alert transform", () => { }); it("should log unmapped state", async () => { + setupNock(gitHubInstallationId, codeScanningCreatedPayload); const payload = { ...codeScanningCreatedPayload, alert: { ...codeScanningCreatedPayload.alert, state: "unmapped_state" } @@ -287,6 +298,7 @@ describe("code_scanning_alert transform", () => { }); it("should log unmapped severity", async () => { + setupNock(gitHubInstallationId, codeScanningCreatedPayload); const payload = { ...codeScanningCreatedPayload, alert: { @@ -309,6 +321,7 @@ describe("code_scanning_alert transform", () => { }); it("set identifiers when alert rule tags contains CWEs", async () => { + setupNock(gitHubInstallationId, codeScanningCreatedJsXssPayload); const payload = codeScanningCreatedJsXssPayload; const context = buildContext(payload); const result = await transformCodeScanningAlertToJiraSecurity( @@ -331,6 +344,7 @@ describe("code_scanning_alert transform", () => { }); it("omit identifiers when alert rule tags is null", async () => { + setupNock(gitHubInstallationId, codeScanningCreatedJsXssPayload); const payload = { ...codeScanningCreatedJsXssPayload, alert: { @@ -351,6 +365,7 @@ describe("code_scanning_alert transform", () => { }); it("omit identifiers when alert rule tags is empty", async () => { + setupNock(gitHubInstallationId, codeScanningCreatedJsXssPayload); const payload = { ...codeScanningCreatedJsXssPayload, alert: { @@ -371,6 +386,7 @@ describe("code_scanning_alert transform", () => { }); it("omit identifiers when alert rule tags have no CWEs", async () => { + setupNock(gitHubInstallationId, codeScanningCreatedJsXssPayload); const payload = { ...codeScanningCreatedJsXssPayload, alert: { @@ -391,3 +407,12 @@ describe("code_scanning_alert transform", () => { }); }); }); + + +const setupNock = (gitHubInstallationId, codeScanningPayload) => { + githubUserTokenNock(gitHubInstallationId); + githubNock + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + .get(`/repos/${codeScanningPayload.repository.owner.login}/${codeScanningPayload.repository.name}/code-scanning/alerts/${codeScanningPayload.alert.number}/instances`) + .reply(200); +}; \ No newline at end of file diff --git a/src/transforms/transform-code-scanning-alert.ts b/src/transforms/transform-code-scanning-alert.ts index 2b5eb28e2c..5c90fb5691 100644 --- a/src/transforms/transform-code-scanning-alert.ts +++ b/src/transforms/transform-code-scanning-alert.ts @@ -6,6 +6,7 @@ import { } from "interfaces/jira"; import { GitHubInstallationClient } from "../github/client/github-installation-client"; import Logger from "bunyan"; +import { capitalize } from "lodash"; import { createInstallationClient } from "../util/get-github-client-config"; import { WebhookContext } from "../routes/github/webhook/webhook-context"; import { transformRepositoryId } from "~/src/transforms/transform-repository-id"; @@ -13,6 +14,7 @@ import { transformGitHubSeverityToJiraSeverity, transformGitHubStateToJiraStatus, transformRuleTagsToIdentifiers } from "~/src/transforms/util/github-security-alerts"; +import { CodeScanningAlertInstanceResponseItem } from "../github/client/github-client.types"; const MAX_STRING_LENGTH = 255; @@ -123,6 +125,7 @@ export const transformCodeScanningAlertToJiraSecurity = async (context: WebhookC subTrigger: "code_scanning_alert" }; const gitHubInstallationClient = await createInstallationClient(githubInstallationId, jiraHost, metrics, context.log, context.gitHubAppConfig?.gitHubAppId); + const { data: alertInstances } = await gitHubInstallationClient.getCodeScanningAlertInstances(repository.owner.login, repository.name, alert.number); const handleUnmappedState = (state: string) => context.log.info(`Received unmapped state from code_scanning_alert webhook: ${state}`); const handleUnmappedSeverity = (severity: string | null) => context.log.info(`Received unmapped severity from code_scanning_alert webhook: ${severity ?? "Missing Severity"}`); @@ -135,8 +138,8 @@ export const transformCodeScanningAlertToJiraSecurity = async (context: WebhookC id: `c-${transformRepositoryId(repository.id, gitHubInstallationClient.baseUrl)}-${alert.number as number}`, updateSequenceNumber: Date.now(), containerId: transformRepositoryId(repository.id, gitHubInstallationClient.baseUrl), - displayName: alert.rule.name, - description: alert.rule.full_description || alert.rule.description, + displayName: alert.rule.description || alert.rule.name, + description: getCodeScanningVulnDescription(alert, identifiers, alertInstances, context.log), url: alert.html_url, type: "sast", introducedDate: alert.created_at, @@ -152,3 +155,46 @@ export const transformCodeScanningAlertToJiraSecurity = async (context: WebhookC }] }; }; + + +export const getCodeScanningVulnDescription = ( + alert, + identifiers: { displayName: string, url: string; }[] | null, + alertInstances: CodeScanningAlertInstanceResponseItem[], + logger: Logger) => { + try { + const branches = getBranches(alertInstances); + const identifiersText = getIdentifiersText(identifiers); + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions + return `**Vulnerability:** ${alert.rule.description}\n\n**Impact:** The vulnerability in ${alert.tool.name} impacts ${toSentence(branches)}.\n\n**Severity:** ${capitalize(alert.rule?.security_severity_level)}\n\nGitHub uses [Common Vulnerability Scoring System (CVSS)](https://www.atlassian.com/trust/security/security-severity-levels) data to calculate security severity.\n\n**Status:** ${capitalize(alert.state)}\n\n**Weaknesses:** ${identifiersText}\n\nVisit the vulnerability’s [code scanning alert page](${alert.html_url}) in GitHub for a recommendation and relevant example.`; + } catch (err) { + logger.warn({ err }, "Failed to construct vulnerability description"); + return alert.rule?.description; + } +}; + +const toSentence = (branches: string[]) => { + if (!branches) { + return ""; + } + if (branches.length == 1) { + return `${branches[0]} branch`; + } + const last = branches.pop(); + return `${branches.join(" branch, ")} branch and ${last} branch`; +}; + +const getBranches = (alertInstances: CodeScanningAlertInstanceResponseItem[]): string[] => { + const branchesAlertInstances = alertInstances.filter(alertInstance => alertInstance.ref?.startsWith("refs/heads")); + return branchesAlertInstances.map(alertInstance => { + return alertInstance.ref.replace("refs/heads/", ""); + }); +}; + +const getIdentifiersText = (identifiers: { displayName: string, url: string; }[] | null): string => { + if (identifiers) { + const identifiersLink = identifiers.map(identifier => `[${identifier.displayName}](${identifier.url})`); + return identifiersLink.join(", "); + } + return ""; +}; \ No newline at end of file diff --git a/src/transforms/transform-dependabot-alert.ts b/src/transforms/transform-dependabot-alert.ts index 00cd9859ab..07821b96ce 100644 --- a/src/transforms/transform-dependabot-alert.ts +++ b/src/transforms/transform-dependabot-alert.ts @@ -6,11 +6,14 @@ import { getGitHubClientConfigFromAppId } from "utils/get-github-client-config"; import { WebhookContext } from "routes/github/webhook/webhook-context"; import { transformRepositoryId } from "~/src/transforms/transform-repository-id"; import { GitHubVulnIdentifier, GitHubVulnReference } from "interfaces/github"; -import { DependabotAlertEvent } from "@octokit/webhooks-types"; +import { DependabotAlert, DependabotAlertEvent } from "@octokit/webhooks-types"; import { transformGitHubSeverityToJiraSeverity, transformGitHubStateToJiraStatus } from "~/src/transforms/util/github-security-alerts"; +import { capitalize } from "lodash"; +import Logger from "bunyan"; +import { DependabotAlertResponseItem } from "../github/client/github-client.types"; export const mapVulnIdentifiers = (identifiers: GitHubVulnIdentifier[], references: GitHubVulnReference[]): JiraVulnerabilityIdentifier[] => { const mappedIdentifiers: JiraVulnerabilityIdentifier[] = []; @@ -36,6 +39,7 @@ export const transformDependabotAlert = async (context: WebhookContext context.log.info(`Received unmapped state from dependabot_alert webhook: ${state}`); const handleUnmappedSeverity = (severity: string | null) => context.log.info(`Received unmapped severity from dependabot_alert webhook: ${severity ?? "Missing Serverity"}`); + const identifiers = mapVulnIdentifiers(alert.security_advisory.identifiers, alert.security_advisory.references); return { vulnerabilities: [{ @@ -44,7 +48,7 @@ export const transformDependabotAlert = async (context: WebhookContext { + try { + const identifiersText = getIdentifiersText(identifiers); + return `**Vulnerability:** ${alert.security_advisory.summary}\n\n**Impact:** ${alert.security_advisory.description}\n\n**Severity:** ${capitalize(alert.security_advisory?.severity)} - ${alert.security_advisory?.cvss?.score}\n\nGitHub uses [Common Vulnerability Scoring System (CVSS)](https://www.atlassian.com/trust/security/security-severity-levels) data to calculate security severity.\n\n**State:** ${capitalize(alert.state)}\n\n**Patched version:** ${alert.security_vulnerability?.first_patched_version?.identifier}\n\n**Identifiers:**\n\n${identifiersText}\n\nVisit the vulnerability’s [dependabot alert page](${alert.html_url}) in GitHub to learn more about and see remediation options.`; + } catch (err) { + logger.warn({ err }, "Failed to construct vulnerability description"); + return alert.security_advisory?.summary; + } +}; + +const getIdentifiersText = (identifiers: JiraVulnerabilityIdentifier[]): string => { + if (identifiers) { + const identifiersLink = identifiers.map(identifier => `- [${identifier.displayName}](${identifier.url})`); + return identifiersLink.join("\n"); + } + return ""; +}; diff --git a/src/transforms/transform-secret-scanning-alert.test.ts b/src/transforms/transform-secret-scanning-alert.test.ts index a1f0afdf62..37e12a9b0b 100644 --- a/src/transforms/transform-secret-scanning-alert.test.ts +++ b/src/transforms/transform-secret-scanning-alert.test.ts @@ -37,10 +37,11 @@ describe("transformSecretScanningAlert", () => { it("should correctly map display name identifiers", async () => { const result = await transformSecretScanningAlert(getSecretScanningAlert("open"), repository, jiraHost, undefined, logger); + expect(result.vulnerabilities).toEqual([ { "containerId": "1", - "description": "Secret scanning alert", + "description": "**Vulnerability:** Fix GitHub Personal Access Token\n\n**State:** Open\n\n**Secret type:** personal_access_token\n\nVisit the vulnerability’s [secret scanning alert page](https://sample/123) in GitHub to learn more about the potential active secret and remediation steps.", "displayName": "GitHub Personal Access Token", "id": "s-1-123", "identifiers": [{ diff --git a/src/transforms/transform-secret-scanning-alert.ts b/src/transforms/transform-secret-scanning-alert.ts index e5ddbd6b68..561312172a 100644 --- a/src/transforms/transform-secret-scanning-alert.ts +++ b/src/transforms/transform-secret-scanning-alert.ts @@ -6,6 +6,7 @@ import { transformRepositoryId } from "~/src/transforms/transform-repository-id" import Logger from "bunyan"; import { Repository } from "@octokit/webhooks-types"; import { SecretScanningAlertResponseItem } from "../github/client/github-client.types"; +import { capitalize } from "lodash"; export const transformSecretScanningAlert = async ( alert: SecretScanningAlertResponseItem, @@ -22,7 +23,7 @@ export const transformSecretScanningAlert = async ( updateSequenceNumber: Date.now(), containerId: transformRepositoryId(repository.id, githubClientConfig.baseUrl), displayName: alert.secret_type_display_name || `${alert.secret_type} secret exposed`, - description: "Secret scanning alert", + description: getSecretScanningVulnDescription(alert, logger), url: alert.html_url, type: "sast", introducedDate: alert.created_at, @@ -56,4 +57,13 @@ export const transformGitHubStateToJiraStatus = (state: string | undefined, logg logger.info(`Received unmapped state from secret_scanning_alert webhook: ${state}`); return JiraVulnerabilityStatusEnum.UNKNOWN; } +}; + +export const getSecretScanningVulnDescription = (alert: SecretScanningAlertResponseItem, logger: Logger) => { + try { + return `**Vulnerability:** Fix ${alert.secret_type_display_name}\n\n**State:** ${capitalize(alert.state)}\n\n**Secret type:** ${alert.secret_type}\n\nVisit the vulnerability’s [secret scanning alert page](${alert.html_url}) in GitHub to learn more about the potential active secret and remediation steps.`; + } catch (err) { + logger.warn({ err }, "Failed to construct vulnerability description"); + return alert.secret_type_display_name; + } }; \ No newline at end of file