From d5d6d4da96f9711db75ba331adf6149901cbc17e Mon Sep 17 00:00:00 2001 From: Michael Waddell Date: Thu, 17 Feb 2022 07:45:14 -0600 Subject: [PATCH] Using graphql to pull alert details closes #84 closes #102 --- src/dependabot/update_metadata.ts | 48 ++++++++++++++++++++---------- src/dependabot/verified_commits.ts | 39 ++++++++++++++++++++++++ src/dry-run.ts | 5 ++-- src/main.ts | 3 +- 4 files changed, 77 insertions(+), 18 deletions(-) diff --git a/src/dependabot/update_metadata.ts b/src/dependabot/update_metadata.ts index 36ac2ce..b31318d 100644 --- a/src/dependabot/update_metadata.ts +++ b/src/dependabot/update_metadata.ts @@ -1,15 +1,30 @@ import * as YAML from 'yaml' -export interface updatedDependency { +export interface dependencyAlert { + alertState: string, + ghsaId: string, + cvss: number +} + +export interface updatedDependency extends dependencyAlert { dependencyName: string, dependencyType: string, updateType: string, directory: string, packageEcosystem: string, - targetBranch: string + targetBranch: string, + prevVersion: string, + newVersion: string } -export function parse (commitMessage: string, branchName: string, mainBranch: string): Array { +export interface alertLookup { + (dependencyName: string, dependencyVersion: string, directory: string): Promise; +} + +export async function parse (commitMessage: string, branchName: string, mainBranch: string, lookup: alertLookup): Promise> { + const firstLine = commitMessage.split('\n')[0] + const directory = firstLine.match(/ in (?\/[^ ]*)$/) + const bumpFragment = commitMessage.match(/^Bumps .* from (?\d[^ ]*) to (?\d[^ ]*)\.$/m) const yamlFragment = commitMessage.match(/^-{3}\n(?[\S|\s]*?)\n^\.{3}\n/m) if (yamlFragment?.groups && branchName.startsWith('dependabot')) { @@ -18,21 +33,24 @@ export function parse (commitMessage: string, branchName: string, mainBranch: st // Since we are on the `dependabot` branch (9 letters), the 10th letter in the branch name is the delimiter const delim = branchName[10] const chunks = branchName.split(delim) - const dirname = chunks.slice(2, -1).join(delim) || '/' + const dirname = directory?.groups?.directory ?? "/" + const prev = bumpFragment?.groups?.from ?? "" + const next = bumpFragment?.groups?.to ?? "" if (data['updated-dependencies']) { - return data['updated-dependencies'].map(dependency => { - return { - dependencyName: dependency['dependency-name'], - dependencyType: dependency['dependency-type'], - updateType: dependency['update-type'], - directory: dirname, - packageEcosystem: chunks[1], - targetBranch: mainBranch - } - }) + return await Promise.all(data['updated-dependencies'].map(async (dependency) => ({ + dependencyName: dependency['dependency-name'], + dependencyType: dependency['dependency-type'], + updateType: dependency['update-type'], + directory: dirname, + packageEcosystem: chunks[1], + targetBranch: mainBranch, + prevVersion: prev, + newVersion: next, + ...await lookup(dependency['dependency-name'], prev, dirname) + }))) } } - return [] + return Promise.resolve([]) } diff --git a/src/dependabot/verified_commits.ts b/src/dependabot/verified_commits.ts index 976a0cd..f11ddd8 100644 --- a/src/dependabot/verified_commits.ts +++ b/src/dependabot/verified_commits.ts @@ -1,6 +1,7 @@ import * as core from '@actions/core' import { GitHub } from '@actions/github/lib/utils' import { Context } from '@actions/github/lib/context' +import type { dependencyAlert } from './update_metadata' const DEPENDABOT_LOGIN = 'dependabot[bot]' @@ -61,3 +62,41 @@ function warnOtherCommits (): void { 'any non-Dependabot changes.' ) } + +export async function getAlert (name: string, version: string, directory: string, client: InstanceType, context: Context): Promise { + const alerts: any = await client.graphql(` + { + repository(owner: "${context.repo.owner}", name: "${context.repo.repo}") { + vulnerabilityAlerts(first: 100) { + nodes { + vulnerableManifestFilename + vulnerableManifestPath + vulnerableRequirements + state + securityVulnerability { + package { name } + } + securityAdvisory { + cvss { score } + ghsaId + } + } + } + } + }`) + + const nodes = alerts?.repository?.vulnerabilityAlerts?.nodes + const found = nodes.find(a => a.vulnerableRequirements == `= ${version}` + && trimSlashes(a.vulnerableManifestPath) == `${trimSlashes(directory)}/${a.vulnerableManifestFilename}` + && a.securityVulnerability.package.name == name) + + return { + alertState: found?.state ?? "", + ghsaId: found?.securityAdvisory.ghsaId ?? "", + cvss: found?.securityAdvisory.cvss.score ?? 0.0 + } +} + +export function trimSlashes(value: string): string { + return value.replace(/^\//, '').replace(/\/$/, '') +} diff --git a/src/dry-run.ts b/src/dry-run.ts index 8d7343a..ce82f98 100755 --- a/src/dry-run.ts +++ b/src/dry-run.ts @@ -5,7 +5,7 @@ import * as dotenv from 'dotenv' import { Argv } from 'yargs' import { hideBin } from 'yargs/helpers' -import { getMessage } from './dependabot/verified_commits' +import { getMessage, getAlert } from './dependabot/verified_commits' import { parse } from './dependabot/update_metadata' import { getBranchNames, parseNwo } from './dependabot/util' @@ -50,8 +50,9 @@ async function check (args: any): Promise { if (commitMessage) { console.log('This appears to be a valid Dependabot Pull Request.') const branchNames = getBranchNames(newContext) + const alertLookup = (name, version, directory) => getAlert(name, version, directory, githubClient, actionContext) - const updatedDependencies = parse(commitMessage, branchNames.headName, branchNames.baseName) + const updatedDependencies = await parse(commitMessage, branchNames.headName, branchNames.baseName, alertLookup) if (updatedDependencies.length > 0) { console.log('Updated dependencies:') diff --git a/src/main.ts b/src/main.ts index 40a9bff..73afd3a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -24,12 +24,13 @@ export async function run (): Promise { // Validate the job const commitMessage = await verifiedCommits.getMessage(githubClient, github.context) const branchNames = util.getBranchNames(github.context) + const alertLookup = (name, version, directory) => verifiedCommits.getAlert(name, version, directory, githubClient, github.context) if (commitMessage) { // Parse metadata core.info('Parsing Dependabot metadata') - const updatedDependencies = updateMetadata.parse(commitMessage, branchNames.headName, branchNames.baseName) + const updatedDependencies = await updateMetadata.parse(commitMessage, branchNames.headName, branchNames.baseName, alertLookup) if (updatedDependencies.length > 0) { output.set(updatedDependencies)