Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 43 additions & 53 deletions .github/workflows/checkAndSubmitAddonMetadata.yml
Original file line number Diff line number Diff line change
Expand Up @@ -234,15 +234,16 @@ jobs:
VT_API_LIMIT: ${{ vars.VT_API_LIMIT }}
outputs:
vtScanUrl: ${{ steps.setVirusTotalAnalysisStatus.outputs.vtScanUrl }}
vtResultsJSON: ${{ steps.setVirusTotalAnalysisStatus.outputs.vtResults }}
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
ref: ${{ env.branchName }}
- name: Install Node.js
uses: actions/setup-node@v4
- name: Install glob
run: npm install glob uuid
- name: Install npm dependencies
run: npm install uuid
- name: Install virusTotal
run: choco install vt-cli
- name: Set Virus Total analysis status
Expand All @@ -252,22 +253,6 @@ jobs:
script: |
const setVirusTotalAnalysisStatus = require('./.github/workflows/virusTotalAnalysis.js')
setVirusTotalAnalysisStatus({core}, ["${{ needs.getAddonId.outputs.addonFileName }}"])
- name: Upload results
id: uploadResults
if: failure()
uses: actions/upload-artifact@v4
with:
name: VirusTotal
path: vt.json
overwrite: true
- name: Upload manual approval
id: uploadManualApproval
if: failure()
uses: actions/upload-artifact@v4
with:
name: manualApproval
path: reviewedAddons.json
overwrite: true
- name: Warn if analysis fails
if: failure()
uses: peter-evans/create-or-update-comment@v4
Expand All @@ -286,47 +271,53 @@ jobs:
addonFileName: ${{ needs.getAddonId.outputs.addonFileName }}
branchName: ${{ inputs.issueAuthorName }}${{ inputs.issueNumber }}

createManualApproval:
commitScanResults:
needs: [getAddonId, virusTotal-analysis, codeQL-analysis]
if: ${{ always() && (contains(needs.virusTotal-analysis.result, 'failure') || contains(needs.codeQL-analysis.result, 'failure')) }}
runs-on: windows-latest
strategy:
matrix:
python-version: [ 3.13 ]
permissions:
contents: write
issues: write
pull-requests: write
if: ${{ always() }}
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v5
- name: Download artifacts
uses: actions/download-artifact@v4
with:
merge-multiple: true
- name: Create pull request
id: cpr
uses: peter-evans/create-pull-request@v7
with:
add-paths: reviewedAddons.json
title: Add reviewed add-on (${{ needs.getAddonId.outputs.addonId }})
branch: reviewedAddon${{ github.event.issue.number }}
commit-message: Add reviewed add-on (${{ needs.getAddonId.outputs.addonId }})
body: |
This add-on needs to be reviewed by NV Access due to analysis failure.
Review #${{ inputs.issueNumber }} for more information.
author: github-actions <[email protected]>
delete-branch: true
- name: Request to keep issue opened
uses: peter-evans/create-or-update-comment@v4
with:
issue-number: ${{ inputs.issueNumber }}
body: |
Please, don't close this issue.
Wait until #${{ steps.cpr.outputs.pull-request-number }} is merged.
ref: ${{ env.branchName }}
- name: Collate analysis results into add-on metadata
run: |
jq --argjson codeQLWarnings '${{ needs.codeQL-analysis.outputs.warningsJSON }}' \
--argjson codeQLErrors '${{ needs.codeQL-analysis.outputs.errorsJSON }}' \
--argjson vtResults '${{ needs.virusTotal-analysis.outputs.vtResultsJSON }}' \
--arg vtScanUrl '${{ needs.virusTotal-analysis.outputs.vtScanUrl }}' \
'. + {
vtScanUrl: $vtScanUrl,
scanResults: {
"codeQL-warnings": $codeQLWarnings,
"codeQL-errors": $codeQLErrors,
"virusTotal": $vtResults
}
}' \
${{ needs.getAddonId.outputs.addonFileName }} > tmp.json
mv tmp.json ${{ needs.getAddonId.outputs.addonFileName }}
- name: Commit add-on metadata
run: |
git add ${{ needs.getAddonId.outputs.addonFileName }}
git config user.name "github-actions"
git config user.email "[email protected]"
git commit -m "Add code analysis results"
git push --set-upstream origin ${{ env.branchName }}

requireManualApproval:
needs: [commitScanResults, virusTotal-analysis, codeQL-analysis]
if: ${{ always() && (needs.virusTotal-analysis.result == 'failure' || needs.codeQL-analysis.result == 'failure') }}
runs-on: ubuntu-latest
environment:
# Require a manual deployment approval if security analysis fails
name: securityReview
steps:
- name: Confirm approval
run: echo "Manual approval performed"

mergeToMaster:
needs: [getAddonId, createPullRequest, codeQL-analysis, virusTotal-analysis]
needs: [getAddonId, commitScanResults, createPullRequest, requireManualApproval]
if: ${{ always() && needs.commitScanResults.result == 'success' && (needs.requireManualApproval.result == 'success' || needs.requireManualApproval.result == 'skipped') }}
permissions:
contents: write
pull-requests: write
Expand Down Expand Up @@ -445,7 +436,6 @@ jobs:
git add .
git commit -m "update discussion URL"
git branch -u origin/master
git pull
git push

call-workflow:
Expand Down
23 changes: 13 additions & 10 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ on:
branchName:
required: true
type: string
outputs:
warningsJSON:
description: "The JSON from CodeQL warnings"
value: ${{ jobs.analyze.outputs.codeQLResults }}
errorsJSON:
description: "The JSON from CodeQL errors"
value: ${{ jobs.analyzeExcludingWarnings.outputs.codeQLResults }}

jobs:
analyzeExcludingWarnings:
Expand All @@ -19,6 +26,8 @@ jobs:
contents: read
security-events: write
issues: write
outputs:
codeQLResults: ${{ steps.setSecurityAnalysisStatus.outputs.codeQLResults }}
steps:
- name: Checkout repository
uses: actions/checkout@v5
Expand Down Expand Up @@ -51,22 +60,14 @@ jobs:
script: |
const setSecurityAnalysisStatus = require('./.github/workflows/securityAnalysis.js')
const resultsPath = 'results/python.sarif'
setSecurityAnalysisStatus({core}, "${{ inputs.addonFileName }}", resultsPath)
setSecurityAnalysisStatus({core}, resultsPath)
- name: Upload results
id: uploadResults
if: failure()
uses: actions/upload-artifact@v4
with:
name: results-excluding-warnings
path: results/python.sarif
- name: Upload manual approval
id: uploadManualApproval
if: failure()
uses: actions/upload-artifact@v4
with:
name: manualApproval
path: reviewedAddons.json
overwrite: true
- name: Warn if analysis fails
if: failure()
uses: peter-evans/create-or-update-comment@v4
Expand All @@ -90,6 +91,8 @@ jobs:
actions: read
security-events: write
issues: write
outputs:
codeQLResults: ${{ steps.setSecurityAnalysisStatus.outputs.codeQLResults }}
steps:
- name: Checkout repository
uses: actions/checkout@v5
Expand Down Expand Up @@ -122,7 +125,7 @@ jobs:
script: |
const setSecurityAnalysisStatus = require('./.github/workflows/securityAnalysis.js')
const resultsPath = 'results/python.sarif'
setSecurityAnalysisStatus({core}, "${{ inputs.addonFileName }}", resultsPath)
setSecurityAnalysisStatus({core}, resultsPath)
- name: Upload results
id: uploadResults
if: failure()
Expand Down
23 changes: 4 additions & 19 deletions .github/workflows/securityAnalysis.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,13 @@
module.exports = ({core}, addonMetadataPath, resultsPath) => {
module.exports = ({core}, resultsPath) => {
const fs = require('fs');
const addonMetadataContents = fs.readFileSync(addonMetadataPath);
const addonMetadata = JSON.parse(addonMetadataContents);
const addonId = addonMetadata.addonId;
const sha256 = addonMetadata.sha256;
const reviewedAddonsContents = fs.readFileSync('reviewedAddons.json');
const reviewedAddonsData = JSON.parse(reviewedAddonsContents);
if (reviewedAddonsData[addonId] !== undefined && reviewedAddonsData[addonId].includes(sha256)) {
core.info('Analysis skipped');
return;
}
const contents = fs.readFileSync(resultsPath);
const data = JSON.parse(contents);
const runs = data.runs[0];
const results = runs.results;
core.setOutput("codeQLResults", results);
if (results.length === 0) {
core.info("Security analysis succeeded");
return;
}
if (reviewedAddonsData[addonId] === undefined) {
reviewedAddonsData[addonId] = [];
} else {
core.setFailed("Security analysis failed");
}
reviewedAddonsData[addonId].push(sha256);
const stringified = JSON.stringify(reviewedAddonsData, null, "\t");
fs.writeFileSync('reviewedAddons.json', stringified);
core.setFailed("Security analysis failed");
};
66 changes: 33 additions & 33 deletions .github/workflows/virusScanAllAddons.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Scan a batch of submitted add-ons with Virus Total
name: Scan a batch of add-ons

on:
# Every day at 6pm UTC.
Expand All @@ -11,20 +11,19 @@ on:
- cron: '0 18 * * *'
workflow_dispatch:

env:
BRANCH_NAME: addScanResults${{ github.run_number }}

jobs:
virusTotal-analysis:
runs-on: windows-latest
strategy:
matrix:
python-version: [ 3.13 ]
permissions:
contents: write
pull-requests: write
env:
VT_API_KEY: ${{ secrets.VT_API_KEY }}
VT_API_LIMIT: ${{ vars.VT_API_LIMIT }}
BRANCH_NAME: addVTURLs${{ github.run_number }}
BATCH_SIZE: 100
BATCH_SIZE: 10
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

temporarily adjusted for testing, we can probably up this to 300 once testing is complete

steps:
- name: Checkout repository
uses: actions/checkout@v5
Expand All @@ -38,16 +37,16 @@ jobs:
- name: Install Node.js
uses: actions/setup-node@v4
- name: Install npm dependencies
run: npm install glob uuid
- name: Get add-on filenames without vtScanUrl
run: npm install uuid
- name: Get add-on filenames without scan results
shell: bash
run: |
for file in ./addons/*/*.json; do
if (jq -r '.vtScanUrl' "$file" | grep -q 'null\|""'); then
if (jq -r '.scanResults.virusTotal' "$file" | grep -q '^null\|""$'); then
echo "$file" >> addonsWithoutVT.txt
fi
done
wc -l addonsWithoutVT.txt | awk '{print "Total add-ons without VT URLs: " $1}'
wc -l addonsWithoutVT.txt | awk '{print "Total add-ons without scan results: " $1}'
- name: Set Virus Total analysis status
id: setVirusTotalAnalysisStatus
uses: actions/github-script@v7
Expand All @@ -57,35 +56,36 @@ jobs:
const fs = require('fs')
const addonsWithoutVT = fs.readFileSync('addonsWithoutVT.txt', 'utf-8').split('\n').filter(Boolean)
setVirusTotalAnalysisStatus({core}, addonsWithoutVT.slice(0, ${{ env.BATCH_SIZE }}))
- name: Create PR for updated VT urls
- name: Push updated VT urls
shell: bash
run: |
git add addons
git config user.name "github-actions"
git config user.email "[email protected]"
git commit -m "Add VirusTotal review URLs"
git commit -m "Add VirusTotal results"
git push --set-upstream origin ${{ env.BRANCH_NAME }}
gh pr create \
--title "Add VirusTotal review URLs" \
--base ${{ github.ref }} \
--head ${{ env.BRANCH_NAME }} \
--body "Add VirusTotal review URLs to add-ons"
gh pr merge --merge "${{ env.BRANCH_NAME }}"
env:
GH_TOKEN: ${{ github.token }}
- name: Upload results
id: uploadResults
if: failure()
uses: actions/upload-artifact@v4
with:
name: VirusTotal
path: vt.json
overwrite: true
- name: Upload manual approval
id: uploadManualApproval
if: failure()
uses: actions/upload-artifact@v4

pull-request:
needs: virusTotal-analysis
runs-on: windows-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
name: manualApproval
path: reviewedAddons.json
overwrite: true
ref: ${{ env.BRANCH_NAME }}
- name: Open pr and merge
shell: bash
run: |
gh pr create \
--title "Add scanning results" \
--base ${{ github.ref }} \
--head ${{ env.BRANCH_NAME }} \
--body "Add scanning results to add-ons"
gh pr merge --merge "${{ env.BRANCH_NAME }}"
env:
GH_TOKEN: ${{ github.token }}
Loading