18
import { Octokit } from '@octokit/rest';
19
import minimist from 'minimist';
21
import synkJsonOutput from '../snyk.json';
32
const argv = minimist(process.argv.slice(2));
34
const GH_OWNER = 'backstage';
35
const GH_REPO = 'backstage';
36
const SNYK_GH_LABEL = 'snyk-vulnerability';
37
const SNYK_ID_REGEX = /\[([^\]]+)]/i;
39
const isDryRun = 'dryrun' in argv;
41
if (!process.env.GITHUB_TOKEN) {
42
console.error('GITHUB_TOKEN is not set. Please provide a Github token');
46
const octokit = new Octokit({
47
auth: process.env.GITHUB_TOKEN,
52
'⚠️ Running in dryrun mode, no issues will be updated on Github ⚠️',
56
const fetchSnykGithubIssueMap = async (): Promise<Record<string, number>> => {
57
const snykGithubIssueMap: Record<string, number> = {};
59
const iterator = octokit.paginate.iterator(octokit.rest.issues.listForRepo, {
64
labels: SNYK_GH_LABEL,
67
for await (const { data: issues } of iterator) {
68
for (const issue of issues) {
70
const match = SNYK_ID_REGEX.exec(issue.title);
72
if (match && match[1]) {
73
snykGithubIssueMap[match[1]] = issue.number;
75
console.log(`Unmatched Snyk ID for ${issue.title}`);
80
return snykGithubIssueMap;
83
const generateIssueBody = (vulnerability: Vulnerability) => `
84
## Affecting Packages/Plugins
86
${Array.from(vulnerability.packages)
87
.map(({ name, target }) => `* [${name}](${target})`)
90
${vulnerability.description}
93
const createGithubIssue = async (vulnerability: Vulnerability) => {
95
`Create Github Issue for Snyk Vulnerability ${vulnerability.snykId}`,
98
vulnerability.packages.forEach(({ name, target }) => {
99
console.log(`- ${name} [${target}]`);
103
await octokit.issues.create({
106
title: `Snyk vulnerability [${vulnerability.snykId}]`,
107
labels: [SNYK_GH_LABEL, 'help wanted'],
108
body: generateIssueBody(vulnerability),
113
const updateGithubIssue = async (
114
githubIssueId: number,
115
vulnerability: Vulnerability,
118
`Update Github Issue #${githubIssueId} for Snky Vulnerability ${vulnerability.snykId}`,
122
await octokit.issues.update({
125
issue_number: githubIssueId,
126
body: generateIssueBody(vulnerability),
131
const closeGithubIssue = async (githubIssueId: number) => {
132
console.log(`Closing Github Issue #${githubIssueId}`);
135
await octokit.issues.update({
138
issue_number: githubIssueId,
144
async function main() {
145
const snykGithubIssueMap = await fetchSnykGithubIssueMap();
146
const vulnerabilityStore: Record<string, Vulnerability> = {};
149
synkJsonOutput.forEach(
150
({ projectName, displayTargetFile, vulnerabilities }) => {
151
vulnerabilities.forEach(
152
({ id, description }: { id: string; description: string }) => {
153
if (id !== undefined && description !== undefined) {
154
if (vulnerabilityStore[id]) {
156
!vulnerabilityStore[id].packages.some(
157
({ name }) => name === projectName,
160
vulnerabilityStore[id].packages.push({
162
target: displayTargetFile,
166
vulnerabilityStore[id] = {
172
target: displayTargetFile,
183
for (const [id, vulnerability] of Object.entries(vulnerabilityStore)) {
184
if (snykGithubIssueMap[id]) {
185
await updateGithubIssue(snykGithubIssueMap[id], vulnerability);
187
await createGithubIssue(vulnerability);
191
for (const [snykId, githubIssueId] of Object.entries(snykGithubIssueMap)) {
192
if (!vulnerabilityStore[snykId]) {
193
await closeGithubIssue(githubIssueId);
198
main().catch(error => {
199
console.error(error.stack);