Skip to content

VirusTotal

VirusTotal #24

Workflow file for this run

name: VirusTotal
on:
workflow_dispatch:
inputs:
release_tag:
description: "Release tag - eg v1.0-v1.18-r2 (default: latest)"
required: false
type: string
env:
HAS_VT_API_KEY: ${{ secrets.VIRUSTOTAL_API_KEY != '' }}
permissions:
contents: write
jobs:
scan-release-assets:
name: Scan release assets with VirusTotal
runs-on: ubuntu-latest
steps:
-
name: Setup
if: env.HAS_VT_API_KEY == 'true'
id: setup
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG_NAME="$(printf '%s' '${{ inputs.release_tag }}' | xargs)"
if [ -z "$TAG_NAME" ]; then
TAG_NAME="$(gh release view --repo "$GITHUB_REPOSITORY" --json tagName --jq '.tagName')"
fi
printf 'Using tag: %s\n' "$TAG_NAME"
echo "tag-name=$TAG_NAME" >>"$GITHUB_OUTPUT"
-
name: Checkout
if: env.HAS_VT_API_KEY == 'true'
uses: actions/checkout@v6
-
name: Create assets directory
if: env.HAS_VT_API_KEY == 'true'
run: mkdir release-assets
-
name: Download release assets
if: env.HAS_VT_API_KEY == 'true'
working-directory: release-assets
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release view "${{ steps.setup.outputs.tag-name }}" --repo "$GITHUB_REPOSITORY" --json assets --jq '.assets[].url' | while read -r url; do
echo "Downloading $url"
gh api -H 'Accept: application/octet-stream' "$url" >"$(basename "$url")"
done
echo 'Assets downloaded:'
ls -l
-
name: Build assets list
if: env.HAS_VT_API_KEY == 'true'
id: build-assets-list
run: |
printf 'assets<<EOF\n' >>"$GITHUB_OUTPUT"
./service/list-assets.sh release-assets >>"$GITHUB_OUTPUT"
printf 'EOF\n' >>"$GITHUB_OUTPUT"
cat "$GITHUB_OUTPUT"
-
name: Submit to VirusTotal
if: env.HAS_VT_API_KEY == 'true'
id: submit
uses: crazy-max/ghaction-virustotal@v4
with:
vt_api_key: ${{ secrets.VIRUSTOTAL_API_KEY }}
update_release_body: false
request_rate: 4
files: ${{ steps.build-assets-list.outputs.assets }}
-
name: Update release body with VirusTotal report
if: steps.submit.outputs.analysis
uses: actions/github-script@v8
env:
TAG_NAME: ${{ steps.setup.outputs.tag-name }}
ANALYSIS_RESULT: ${{ steps.submit.outputs.analysis }}
with:
script: |
const MARKER_START = '<!-- virustotal -->';
const MARKER_END = '<!-- /virustotal -->';
const tagName = process.env.TAG_NAME;
const analysisResult = (process.env.ANALYSIS_RESULT || '').trim();
if (analysisResult === '') {
core.setFailed('Analysis output is empty');
return;
}
const {owner, repo} = context.repo;
const release = await github.rest.repos.getReleaseByTag({
owner,
repo,
tag: tagName,
});
const originalBody = release.data.body || '';
if (originalBody.trim() === '') {
core.setFailed('Release body is empty');
return;
}
let detailsLines = [];
const analysisLines = analysisResult
.split(',')
.map(line => line.trim())
.filter(line => line !== '')
;
analysisLines.forEach(analysisLine => {
const match = analysisLine.match(/^\s*(.+?)\s*=\s*(https?:\/\/\S+)\s*$/);
if (!match) {
core.warning(`Skipping invalid analysis line: ${analysisLine}`);
return;
}
const [_, filename, analysisURL] = match;
detailsLines.push(`* [${filename}](${analysisURL})`);
});
if (detailsLines.length === 0) {
core.warning('No valid analysis lines found, skipping update.');
return;
}
const newBody = originalBody.replace(/[\r\n]+$/, '') + `\n\n${MARKER_START}\n## VirusTotal Analysis\n\n${detailsLines.join('\n')}\n\n${MARKER_END}\n`;
core.startGroup('New release body');
console.log(newBody);
core.endGroup();
if (originalBody.includes(MARKER_START) && originalBody.includes(MARKER_END)) {
core.notice('Release body already contains VirusTotal analysis, skipping update.');
return;
}
await github.rest.repos.updateRelease({
owner,
repo,
release_id: release.data.id,
body: newBody,
});