Skip to content

Commit

Permalink
πŸ”„ synced file(s) with DevExpress/testcafe-build-system (#130)
Browse files Browse the repository at this point in the history
* πŸ”„ synced local '.github/workflows/' with remote 'workflows/'

* πŸ”„ created local '.github/scripts/security-checker.mjs' from remote 'scripts/security-checker.mjs'

---------

Co-authored-by: testcafe-build-bot <null>
  • Loading branch information
testcafe-build-bot authored Dec 6, 2023
1 parent 68f3d56 commit 54e10f3
Show file tree
Hide file tree
Showing 2 changed files with 185 additions and 93 deletions.
177 changes: 177 additions & 0 deletions .github/scripts/security-checker.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
const STATES = {
open: 'open',
closed: 'closed',
};

const LABELS = {
dependabot: 'dependabot',
codeq: 'codeql',
security: 'security notification',
};

class SecurityChecker {
constructor (github, context, issueRepo) {
this.github = github;
this.issueRepo = issueRepo;
this.context = {
owner: context.repo.owner,
repo: context.repo.repo,
};
}

async check () {
const dependabotAlerts = await this.getDependabotAlerts();
const codeqlAlerts = await this.getCodeqlAlerts();
const existedIssues = await this.getExistedIssues();

this.alertDictionary = this.createAlertDictionary(existedIssues);

await this.closeSpoiledIssues();
this.createDependabotlIssues(dependabotAlerts);
this.createCodeqlIssues(codeqlAlerts);
}

async getDependabotAlerts () {
const { data } = await this.github.rest.dependabot.listAlertsForRepo({ state: STATES.open, ...this.context });

return data;
}

async getCodeqlAlerts () {
try {
const { data } = await this.github.rest.codeScanning.listAlertsForRepo({ state: STATES.open, ...this.context });

return data;
}
catch (e) {
if (e.message.includes('no analysis found'))
return [];

throw e;
}
}

async getExistedIssues () {
const { data: existedIssues } = await this.github.rest.issues.listForRepo({
owner: this.context.owner,
repo: this.issueRepo,
labels: [LABELS.security],
state: STATES.open,
});

return existedIssues;
}

createAlertDictionary (existedIssues) {
return existedIssues.reduce((res, issue) => {
const [, url, number] = issue.body.match(/Link:\s*(https.*?(\d+)$)/);

if (!url)
return res;

res[url] = {
issue, number,
isDependabot: url.includes('dependabot'),
};

return res;
}, {});
}

async closeSpoiledIssues () {
for (const key in this.alertDictionary) {
const alert = this.alertDictionary[key];

if (alert.isDependabot) {
const isAlertOpened = await this.isDependabotAlertOpened(alert.number);

if (isAlertOpened)
continue;

await this.closeIssue(alert.issue.number);
}
}
}

async isDependabotAlertOpened (alertNumber) {
const alert = await this.getDependabotAlertInfo(alertNumber);

return alert.state === STATES.open;
}

async getDependabotAlertInfo (alertNumber) {
try {
const { data } = await this.github.rest.dependabot.getAlert({ alert_number: alertNumber, ...this.context });

return data;
}
catch (e) {
if (e.message.includes('No alert found for alert number'))
return {};

throw e;
}
}

async closeIssue (issueNumber) {
return this.github.rest.issues.update({
owner: this.context.owner,
repo: this.issueRepo,
issue_number: issueNumber,
state: STATES.closed,
});
}

async createDependabotlIssues (dependabotAlerts) {
dependabotAlerts.forEach(alert => {
if (!this.needCreateIssue(alert))
return;

this.createIssue({
labels: [LABELS.dependabot, LABELS.security, alert.dependency.scope],
originRepo: this.context.repo,
summary: alert.security_advisory.summary,
description: alert.security_advisory.description,
link: alert.html_url,
issuePackage: alert.dependency.package.name,
});
});
}

async createCodeqlIssues (codeqlAlerts) {
codeqlAlerts.forEach(alert => {
if (!this.needCreateIssue(alert))
return;

this.createIssue({
labels: [LABELS.codeql, LABELS.security],
originRepo: this.context.repo,
summary: alert.rule.description,
description: alert.most_recent_instance.message.text,
link: alert.html_url,
});
});
}

needCreateIssue (alert) {
return !this.alertDictionary[alert.html_url];
}

async createIssue ({ labels, originRepo, summary, description, link, issuePackage = '' }) {
const title = `[${originRepo}] ${summary}`;
const body = ''
+ `#### Repository: \`${originRepo}\`\n`
+ (issuePackage ? `#### Package: \`${issuePackage}\`\n` : '')
+ `#### Description:\n`
+ `${description}\n`
+ `#### Link: ${link}`;

return this.github.rest.issues.create({
title, body, labels,
owner: this.context.owner,
repo: this.issueRepo,
});
}
}

export default SecurityChecker;
101 changes: 8 additions & 93 deletions .github/workflows/check-security-alerts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,101 +9,16 @@ jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v6
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: latest
- uses: actions/github-script@v7
with:
github-token: ${{ secrets.ACTIVE_TOKEN }}
script: |
if (!'${{secrets.SECURITY_ISSUE_REPO}}')
return;
const {default: SecurityChecker} = await import('${{ github.workspace }}/.github/scripts/security-checker.mjs')
const { owner, repo } = context.repo;
const state = 'open';
const dependabotLabel = 'dependabot';
const codeqlLabel = 'codeql';
const securityLabel = 'security notification';
const securityChecker = new SecurityChecker(github, context, '${{secrets.SECURITY_ISSUE_REPO}}');
async function getDependabotAlerts () {
const dependabotListAlertsUrl = `https://api.github.com/repos/${ owner }/${ repo }/dependabot/alerts?state=${ state }`;
const dependabotRequestOptions = {
headers: { 'Authorization': 'Bearer ${{ secrets.ACTIVE_TOKEN }}' }
}
const response = await fetch(dependabotListAlertsUrl, dependabotRequestOptions);
const data = await response.json();
// If data isn't arry somethig goes wrong
if (Array.isArray(data))
return data;
return [];
}
async function getCodeqlAlerts () {
// When CodeQL is turned of it throws error
try {
const { data } = await github.rest.codeScanning.listAlertsForRepo({ owner, repo, state });
return data;
} catch (_) {
return [];
}
}
async function createIssue ({owner, repo, labels, originRepo, summary, description, link, package = ''}) {
const title = `[${originRepo}] ${summary}`;
const body = ''
+ `#### Repository: \`${ originRepo }\`\n`
+ (!!package ? `#### Package: \`${ package }\`\n` : '')
+ `#### Description:\n`
+ `${ description }\n`
+ `#### Link: ${ link }`
return github.rest.issues.create({ owner, repo, title, body, labels });
}
function needCreateIssue (alert) {
return !issueDictionary[alert.html_url]
&& Date.now() - new Date(alert.created_at) <= 1000 * 60 * 60 * 24;
}
const dependabotAlerts = await getDependabotAlerts();
const codeqlAlerts = await getCodeqlAlerts();
const {data: existedIssues} = await github.rest.issues.listForRepo({ owner, repo, labels: [securityLabel], state });
const issueDictionary = existedIssues.reduce((res, issue) => {
const alertUrl = issue.body.match(/Link:\s*(https.*\d*)/)?.[1];
if (alertUrl)
res[alertUrl] = issue;
return res;
}, {})
dependabotAlerts.forEach(alert => {
if (!needCreateIssue(alert))
return;
createIssue({ owner,
repo: '${{ secrets.SECURITY_ISSUE_REPO }}',
labels: [dependabotLabel, securityLabel],
originRepo: repo,
summary: alert.security_advisory.summary,
description: alert.security_advisory.description,
link: alert.html_url,
package: alert.dependency.package.name
})
});
codeqlAlerts.forEach(alert => {
if (!needCreateIssue(alert))
return;
createIssue({ owner,
repo: '${{ secrets.SECURITY_ISSUE_REPO }}',
labels: [codeqlLabel, securityLabel],
originRepo: repo,
summary: alert.rule.description,
description: alert.most_recent_instance.message.text,
link: alert.html_url,
})
});
await securityChecker.check();

0 comments on commit 54e10f3

Please sign in to comment.