Skip to content

Commit 3d74ab6

Browse files
authored
1 parent 402e122 commit 3d74ab6

16 files changed

+7022
-143
lines changed

.github/workflows/checkAndSubmitAddonMetadata.yml

Lines changed: 17 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -252,22 +252,13 @@ jobs:
252252
script: |
253253
const setVirusTotalAnalysisStatus = require('./.github/workflows/virusTotalAnalysis.js')
254254
setVirusTotalAnalysisStatus({core}, ["${{ needs.getAddonId.outputs.addonFileName }}"])
255-
- name: Upload results
256-
id: uploadResults
257-
if: failure()
258-
uses: actions/upload-artifact@v4
259-
with:
260-
name: VirusTotal
261-
path: vt.json
262-
overwrite: true
263-
- name: Upload manual approval
264-
id: uploadManualApproval
265-
if: failure()
266-
uses: actions/upload-artifact@v4
267-
with:
268-
name: manualApproval
269-
path: reviewedAddons.json
270-
overwrite: true
255+
- name: Commit scanned add-ons results
256+
run: |
257+
git add addons
258+
git config user.name "github-actions"
259+
git config user.email "[email protected]"
260+
git commit -m "Add VirusTotal results"
261+
git push --set-upstream origin ${{ env.branchName }}
271262
- name: Warn if analysis fails
272263
if: failure()
273264
uses: peter-evans/create-or-update-comment@v4
@@ -286,47 +277,20 @@ jobs:
286277
addonFileName: ${{ needs.getAddonId.outputs.addonFileName }}
287278
branchName: ${{ inputs.issueAuthorName }}${{ inputs.issueNumber }}
288279

289-
createManualApproval:
280+
requireManualApproval:
290281
needs: [getAddonId, virusTotal-analysis, codeQL-analysis]
291-
if: ${{ always() && (contains(needs.virusTotal-analysis.result, 'failure') || contains(needs.codeQL-analysis.result, 'failure')) }}
292-
runs-on: windows-latest
293-
strategy:
294-
matrix:
295-
python-version: [ 3.11 ]
296-
permissions:
297-
contents: write
298-
issues: write
299-
pull-requests: write
282+
if: ${{ always() && (needs.virusTotal-analysis.result == 'failure' || needs.codeQL-analysis.result == 'failure') }}
283+
runs-on: ubuntu-latest
284+
environment:
285+
# Require a manual deployment approval if security analysis fails
286+
name: securityReview
300287
steps:
301-
- name: Checkout repository
302-
uses: actions/checkout@v4
303-
- name: Download artifacts
304-
uses: actions/download-artifact@v4
305-
with:
306-
merge-multiple: true
307-
- name: Create pull request
308-
id: cpr
309-
uses: peter-evans/create-pull-request@v7
310-
with:
311-
add-paths: reviewedAddons.json
312-
title: Add reviewed add-on (${{ needs.getAddonId.outputs.addonId }})
313-
branch: reviewedAddon${{ github.event.issue.number }}
314-
commit-message: Add reviewed add-on (${{ needs.getAddonId.outputs.addonId }})
315-
body: |
316-
This add-on needs to be reviewed by NV Access due to analysis failure.
317-
Review #${{ inputs.issueNumber }} for more information.
318-
author: github-actions <[email protected]>
319-
delete-branch: true
320-
- name: Request to keep issue opened
321-
uses: peter-evans/create-or-update-comment@v4
322-
with:
323-
issue-number: ${{ inputs.issueNumber }}
324-
body: |
325-
Please, don't close this issue.
326-
Wait until #${{ steps.cpr.outputs.pull-request-number }} is merged.
288+
- name: Confirm approval
289+
run: echo "Manual approval performed"
327290

328291
mergeToMaster:
329-
needs: [getAddonId, createPullRequest, codeQL-analysis, virusTotal-analysis]
292+
needs: [getAddonId, createPullRequest, requireManualApproval]
293+
if: ${{ always() && (needs.requireManualApproval.result == 'success' || needs.requireManualApproval.result == 'skipped') }}
330294
permissions:
331295
contents: write
332296
pull-requests: write

.github/workflows/codeql-analysis.yml

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -51,22 +51,22 @@ jobs:
5151
script: |
5252
const setSecurityAnalysisStatus = require('./.github/workflows/securityAnalysis.js')
5353
const resultsPath = 'results/python.sarif'
54-
setSecurityAnalysisStatus({core}, "${{ inputs.addonFileName }}", resultsPath)
54+
setSecurityAnalysisStatus({core}, "${{ inputs.addonFileName }}", resultsPath, "errors")
55+
- name: Commit scanned add-ons results
56+
if: always()
57+
run: |
58+
git add ${{ inputs.addonFileName }}
59+
git config user.name "github-actions"
60+
git config user.email "[email protected]"
61+
git commit -m "Add CodeQL error results"
62+
git push --set-upstream origin ${{ inputs.branchName }}
5563
- name: Upload results
5664
id: uploadResults
5765
if: failure()
5866
uses: actions/upload-artifact@v4
5967
with:
6068
name: results-excluding-warnings
6169
path: results/python.sarif
62-
- name: Upload manual approval
63-
id: uploadManualApproval
64-
if: failure()
65-
uses: actions/upload-artifact@v4
66-
with:
67-
name: manualApproval
68-
path: reviewedAddons.json
69-
overwrite: true
7070
- name: Warn if analysis fails
7171
if: failure()
7272
uses: peter-evans/create-or-update-comment@v4
@@ -122,7 +122,16 @@ jobs:
122122
script: |
123123
const setSecurityAnalysisStatus = require('./.github/workflows/securityAnalysis.js')
124124
const resultsPath = 'results/python.sarif'
125-
setSecurityAnalysisStatus({core}, "${{ inputs.addonFileName }}", resultsPath)
125+
setSecurityAnalysisStatus({core}, "${{ inputs.addonFileName }}", resultsPath, "warnings")
126+
- name: Commit scanned add-ons results
127+
if: always()
128+
run: |
129+
git pull --rebase
130+
git add ${{ inputs.addonFileName }}
131+
git config user.name "github-actions"
132+
git config user.email "[email protected]"
133+
git commit -m "Add CodeQL warnings results"
134+
git push --set-upstream origin ${{ inputs.branchName }}
126135
- name: Upload results
127136
id: uploadResults
128137
if: failure()

.github/workflows/securityAnalysis.js

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,23 @@
1-
module.exports = ({core}, addonMetadataPath, resultsPath) => {
1+
module.exports = ({core}, addonMetadataPath, resultsPath, testType) => {
22
const fs = require('fs');
33
const addonMetadataContents = fs.readFileSync(addonMetadataPath);
44
const addonMetadata = JSON.parse(addonMetadataContents);
5-
const addonId = addonMetadata.addonId;
6-
const sha256 = addonMetadata.sha256;
7-
const reviewedAddonsContents = fs.readFileSync('reviewedAddons.json');
8-
const reviewedAddonsData = JSON.parse(reviewedAddonsContents);
9-
if (reviewedAddonsData[addonId] !== undefined && reviewedAddonsData[addonId].includes(sha256)) {
10-
core.info('Analysis skipped');
11-
return;
12-
}
135
const contents = fs.readFileSync(resultsPath);
146
const data = JSON.parse(contents);
157
const runs = data.runs[0];
168
const results = runs.results;
9+
if (testType !== "errors" && testType !== "warnings") {
10+
core.setFailed(`Invalid test type: ${testType}`);
11+
}
12+
if (addonMetadata.scanResults === undefined) {
13+
addonMetadata.scanResults = {};
14+
}
15+
addonMetadata.scanResults[`codeQL-${testType}`] = results;
16+
const stringified = JSON.stringify(addonMetadata, null, "\t");
17+
fs.writeFileSync(addonMetadataPath, stringified + "\n");
1718
if (results.length === 0) {
1819
core.info("Security analysis succeeded");
19-
return;
20-
}
21-
if (reviewedAddonsData[addonId] === undefined) {
22-
reviewedAddonsData[addonId] = [];
20+
} else {
21+
core.setFailed("Security analysis failed");
2322
}
24-
reviewedAddonsData[addonId].push(sha256);
25-
const stringified = JSON.stringify(reviewedAddonsData, null, "\t");
26-
fs.writeFileSync('reviewedAddons.json', stringified);
27-
core.setFailed("Security analysis failed");
2823
};

.github/workflows/virusScanAllAddons.yml

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ jobs:
2323
env:
2424
VT_API_KEY: ${{ secrets.VT_API_KEY }}
2525
VT_API_LIMIT: ${{ vars.VT_API_LIMIT }}
26-
BRANCH_NAME: addVTURLs${{ github.run_number }}
27-
BATCH_SIZE: 100
26+
BRANCH_NAME: addVTresults${{ github.run_number }}
27+
BATCH_SIZE: 10
2828
steps:
2929
- name: Checkout repository
3030
uses: actions/checkout@v4
@@ -39,15 +39,15 @@ jobs:
3939
uses: actions/setup-node@v4
4040
- name: Install npm dependencies
4141
run: npm install glob uuid
42-
- name: Get add-on filenames without vtScanUrl
42+
- name: Get add-on filenames without scan results
4343
shell: bash
4444
run: |
4545
for file in ./addons/*/*.json; do
46-
if (jq -r '.vtScanUrl' "$file" | grep -q 'null\|""'); then
46+
if (jq -r '.scanResults | .virusTotal' "$file" | grep -q 'null\|""'); then
4747
echo "$file" >> addonsWithoutVT.txt
4848
fi
4949
done
50-
wc -l addonsWithoutVT.txt | awk '{print "Total add-ons without VT URLs: " $1}'
50+
wc -l addonsWithoutVT.txt | awk '{print "Total add-ons without scan results: " $1}'
5151
- name: Set Virus Total analysis status
5252
id: setVirusTotalAnalysisStatus
5353
uses: actions/github-script@v7
@@ -63,29 +63,13 @@ jobs:
6363
git add addons
6464
git config user.name "github-actions"
6565
git config user.email "[email protected]"
66-
git commit -m "Add VirusTotal review URLs"
66+
git commit -m "Add VirusTotal results"
6767
git push --set-upstream origin ${{ env.BRANCH_NAME }}
6868
gh pr create \
69-
--title "Add VirusTotal review URLs" \
69+
--title "Add VirusTotal results" \
7070
--base ${{ github.ref }} \
7171
--head ${{ env.BRANCH_NAME }} \
72-
--body "Add VirusTotal review URLs to add-ons"
72+
--body "Add VirusTotal results to add-ons"
7373
gh pr merge --merge "${{ env.BRANCH_NAME }}"
7474
env:
7575
GH_TOKEN: ${{ github.token }}
76-
- name: Upload results
77-
id: uploadResults
78-
if: failure()
79-
uses: actions/upload-artifact@v4
80-
with:
81-
name: VirusTotal
82-
path: vt.json
83-
overwrite: true
84-
- name: Upload manual approval
85-
id: uploadManualApproval
86-
if: failure()
87-
uses: actions/upload-artifact@v4
88-
with:
89-
name: manualApproval
90-
path: reviewedAddons.json
91-
overwrite: true

.github/workflows/virusTotalAnalysis.js

Lines changed: 19 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,16 @@ function writeVTScanUrl({core}, metadataFile, addonMetadata) {
1010
addonMetadata.vtScanUrl = vtScanUrl;
1111
stringified = JSON.stringify(addonMetadata, null, "\t");
1212
// Write vtScanUrl to add-on metadata file
13-
fs.writeFileSync(metadataFile, stringified);
13+
fs.writeFileSync(metadataFile, stringified + "\n");
1414
// Store the latest vtScanUrl for single file analysis
1515
core.setOutput("vtScanUrl", vtScanUrl);
1616
}
1717

1818

19-
function getVirusTotalAnalysis({core}, addonMetadata, metadataFile, reviewedAddonsData) {
19+
function getVirusTotalAnalysis({core}, addonMetadata, metadataFile) {
2020
/*
2121
Get the VirusTotal analysis for the add-on file.
22-
If the add-on is flagged as malicious, store the sha256 hash in reviewedAddons.json.
23-
Always store the scan URL in the add-on metadata file.
22+
Store the results in the metadata file and the scan URL in the add-on metadata file.
2423
If Virus total fails to scan the add-on, fail the job.
2524
*/
2625
countAPIUsageAndWait({core});
@@ -33,27 +32,26 @@ function getVirusTotalAnalysis({core}, addonMetadata, metadataFile, reviewedAddo
3332
if (core._isSingleFileAnalysis) {
3433
core.setFailed(`Failed to get VirusTotal analysis for ${metadataFile}`);
3534
}
35+
// Resubmit and try again
3636
virusTotalSubmit({core}, [metadataFile]);
37-
getVirusTotalAnalysis({core}, addonMetadata, metadataFile, reviewedAddonsData);
37+
getVirusTotalAnalysis({core}, addonMetadata, metadataFile);
3838
return;
3939
}
4040
writeVTScanUrl({core}, metadataFile, addonMetadata);
4141
// Append the VirusTotal analysis to the file for an artifact
4242
const vtData = JSON.parse(stdout);
43-
fs.appendFileSync("vt.json", stdout);
4443
const stats = vtData[0]["last_analysis_stats"];
4544
const malicious = stats.malicious;
45+
if (addonMetadata.scanResults === undefined) {
46+
addonMetadata.scanResults = {};
47+
}
48+
addonMetadata.scanResults.virusTotal = vtData;
49+
stringified = JSON.stringify(addonMetadata, null, "\t");
50+
fs.writeFileSync(metadataFile, stringified + "\n");
4651
if (malicious === 0) {
4752
core.info(`VirusTotal analysis succeeded for ${metadataFile}`);
48-
return;
49-
}
50-
if (reviewedAddonsData[addonMetadata.addonId] === undefined) {
51-
reviewedAddonsData[addonMetadata.addonId] = [];
5253
}
53-
reviewedAddonsData[addonMetadata.addonId].push(addonMetadata.sha256);
54-
stringified = JSON.stringify(reviewedAddonsData, null, "\t");
55-
fs.writeFileSync("reviewedAddons.json", stringified);
56-
if (core._isSingleFileAnalysis) {
54+
else if (core._isSingleFileAnalysis) {
5755
core.setFailed(`VirusTotal analysis failed for ${metadataFile}`);
5856
}
5957
});
@@ -68,20 +66,17 @@ function getVirusTotalAnalysisIfRequired({core}, metadataFile) {
6866
*/
6967
const addonMetadataContents = fs.readFileSync(metadataFile);
7068
const addonMetadata = JSON.parse(addonMetadataContents);
71-
const addonId = addonMetadata.addonId;
72-
const reviewedAddonsContents = fs.readFileSync("reviewedAddons.json");
73-
const reviewedAddonsData = JSON.parse(reviewedAddonsContents);
74-
// Check if add-on has been flagged before through VirusTotal.
75-
if (reviewedAddonsData[addonId] !== undefined && reviewedAddonsData[addonId].includes(sha256)) {
76-
core.info(`VirusTotal analysis skipped, already performed for ${metadataFile}`);
77-
return;
69+
// Check if add-on has been submitted before to VirusTotal.
70+
if (addonMetadata.vtScanUrl === undefined) {
71+
core.info(`VirusTotal scanning has not been performed for ${metadataFile}`);
72+
virusTotalSubmit({core}, [metadataFile]);
7873
}
79-
// Check if add-on has been scanned before through VirusTotal.
80-
if (addonMetadata.vtScanUrl !== undefined) {
74+
// Check if add-on has had results saved before through VirusTotal.
75+
if (addonMetadata.scanResults !== undefined && addonMetadata.scanResults.virusTotal !== undefined) {
8176
core.info(`VirusTotal analysis skipped, already performed for ${metadataFile}`);
8277
return;
8378
}
84-
getVirusTotalAnalysis({core}, addonMetadata, metadataFile, reviewedAddonsData);
79+
getVirusTotalAnalysis({core}, addonMetadata, metadataFile);
8580
}
8681

8782
module.exports = ({core}, metadataFiles) => {

0 commit comments

Comments
 (0)