Skip to content

Commit 87fc174

Browse files
committed
Continuous Building
1 parent 4252035 commit 87fc174

File tree

7 files changed

+921
-2
lines changed

7 files changed

+921
-2
lines changed
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
inputs:
2+
ARTIFACT_FILE_PATH:
3+
required: true
4+
description: Absolute path to the file to scan
5+
VIRUSTOTAL_API_KEY:
6+
required: true
7+
description: VirusTotal API key
8+
SCAN_RESULTS_PATH:
9+
required: true
10+
description: Path where scan results JSON will be saved
11+
runs:
12+
using: composite
13+
steps:
14+
- name: Calculate file hash
15+
id: file-hash
16+
working-directory: ${{ github.workspace }}
17+
run: |
18+
set -euo pipefail
19+
FILE_HASH=$(sha256sum "${{ inputs.ARTIFACT_FILE_PATH }}" | awk '{print $1}')
20+
echo "FILE_HASH=${FILE_HASH}" >> $GITHUB_OUTPUT
21+
echo "📄 File: $(basename "${{ inputs.ARTIFACT_FILE_PATH }}")"
22+
echo "🔑 SHA-256: ${FILE_HASH}"
23+
shell: bash
24+
- name: Scan with VirusTotal
25+
id: virustotal-scan
26+
uses: crazy-max/ghaction-virustotal@d34968c958ae283fe976efed637081b9f9dcf74f
27+
with:
28+
vt_api_key: ${{ inputs.VIRUSTOTAL_API_KEY }}
29+
files: ${{ inputs.ARTIFACT_FILE_PATH }}
30+
vt_monitor: false
31+
- name: Wait for VirusTotal analysis to complete
32+
run: |
33+
echo "⏳ Waiting 45 seconds for VirusTotal analysis to complete..."
34+
echo " (Respecting API rate limits: 4 lookups/min)"
35+
sleep 45
36+
shell: bash
37+
- name: Fetch and parse VirusTotal results
38+
id: vt-results
39+
working-directory: ${{ github.workspace }}
40+
run: |
41+
set -euo pipefail
42+
43+
VT_URL="${{ steps.virustotal-scan.outputs.analysis }}"
44+
FILE_HASH="${{ steps.file-hash.outputs.FILE_HASH }}"
45+
46+
echo "🔍 Fetching VirusTotal results..."
47+
48+
# Extract file ID from URL (sha256 hash)
49+
if [[ "$VT_URL" =~ /file/([a-f0-9]+) ]]; then
50+
FILE_ID="${BASH_REMATCH[1]}"
51+
else
52+
FILE_ID="$FILE_HASH"
53+
fi
54+
55+
# Fetch analysis results from VirusTotal API
56+
VT_RESPONSE=$(curl -s --request GET \
57+
--url "https://www.virustotal.com/api/v3/files/${FILE_ID}" \
58+
--header "x-apikey: ${{ inputs.VIRUSTOTAL_API_KEY }}")
59+
60+
# Extract detection stats
61+
MALICIOUS=$(echo "$VT_RESPONSE" | jq -r '.data.attributes.last_analysis_stats.malicious // 0')
62+
SUSPICIOUS=$(echo "$VT_RESPONSE" | jq -r '.data.attributes.last_analysis_stats.suspicious // 0')
63+
UNDETECTED=$(echo "$VT_RESPONSE" | jq -r '.data.attributes.last_analysis_stats.undetected // 0')
64+
HARMLESS=$(echo "$VT_RESPONSE" | jq -r '.data.attributes.last_analysis_stats.harmless // 0')
65+
66+
# Extract individual engine results
67+
DETECTIONS=$(echo "$VT_RESPONSE" | jq -r '
68+
.data.attributes.last_analysis_results |
69+
to_entries |
70+
map(select(.value.category == "malicious" or .value.category == "suspicious")) |
71+
map({engine: .key, category: .value.category, result: .value.result}) |
72+
.[]' | jq -s '.')
73+
74+
echo "MALICIOUS=${MALICIOUS}" >> $GITHUB_OUTPUT
75+
echo "SUSPICIOUS=${SUSPICIOUS}" >> $GITHUB_OUTPUT
76+
echo "UNDETECTED=${UNDETECTED}" >> $GITHUB_OUTPUT
77+
echo "HARMLESS=${HARMLESS}" >> $GITHUB_OUTPUT
78+
echo "DETECTIONS<<EOF" >> $GITHUB_OUTPUT
79+
echo "$DETECTIONS" >> $GITHUB_OUTPUT
80+
echo "EOF" >> $GITHUB_OUTPUT
81+
82+
echo "📊 Detection Stats:"
83+
echo " Malicious: ${MALICIOUS}"
84+
echo " Suspicious: ${SUSPICIOUS}"
85+
echo " Undetected: ${UNDETECTED}"
86+
echo " Harmless: ${HARMLESS}"
87+
shell: bash
88+
- name: Analyze detections for false positives
89+
id: analyze-fp
90+
working-directory: ${{ github.workspace }}
91+
run: |
92+
set -euo pipefail
93+
94+
MALICIOUS=${{ steps.vt-results.outputs.MALICIOUS }}
95+
SUSPICIOUS=${{ steps.vt-results.outputs.SUSPICIOUS }}
96+
DETECTIONS='${{ steps.vt-results.outputs.DETECTIONS }}'
97+
98+
# Known false positive patterns for game development tools
99+
FP_PATTERNS=(
100+
"Generic"
101+
"Heur"
102+
"Heuristic"
103+
"PUA"
104+
"PUP"
105+
"Potentially"
106+
"Unsafe"
107+
"Suspect"
108+
"BehavesLike"
109+
"Artemis"
110+
"ML\."
111+
"DDS:"
112+
"HEUR:"
113+
"Gen:"
114+
)
115+
116+
# Known problematic engines with high false positive rates for legitimate software
117+
FP_ENGINES=(
118+
"Cyren"
119+
"Cylance"
120+
"Sangfor"
121+
"VBA32"
122+
"MaxSecure"
123+
"Gridinsoft"
124+
"Jiangmin"
125+
)
126+
127+
LEGITIMATE_DETECTIONS=0
128+
FALSE_POSITIVE_COUNT=0
129+
130+
if [ "$MALICIOUS" -gt 0 ] || [ "$SUSPICIOUS" -gt 0 ]; then
131+
echo "⚠️ Analyzing $(( MALICIOUS + SUSPICIOUS )) detections..."
132+
133+
# Check each detection
134+
echo "$DETECTIONS" | jq -c '.[]' | while read -r detection; do
135+
ENGINE=$(echo "$detection" | jq -r '.engine')
136+
RESULT=$(echo "$detection" | jq -r '.result')
137+
138+
IS_FP=false
139+
140+
# Check if engine is known for false positives
141+
for fp_engine in "${FP_ENGINES[@]}"; do
142+
if [[ "$ENGINE" == "$fp_engine" ]]; then
143+
IS_FP=true
144+
echo " ✓ ${ENGINE}: ${RESULT} (Known FP engine)"
145+
break
146+
fi
147+
done
148+
149+
# Check if result matches false positive patterns
150+
if [ "$IS_FP" = false ]; then
151+
for pattern in "${FP_PATTERNS[@]}"; do
152+
if echo "$RESULT" | grep -qi "$pattern"; then
153+
IS_FP=true
154+
echo " ✓ ${ENGINE}: ${RESULT} (FP pattern: ${pattern})"
155+
break
156+
fi
157+
done
158+
fi
159+
160+
# If not a false positive, count as legitimate threat
161+
if [ "$IS_FP" = false ]; then
162+
echo " ⚠️ ${ENGINE}: ${RESULT} (POTENTIAL THREAT)"
163+
LEGITIMATE_DETECTIONS=$((LEGITIMATE_DETECTIONS + 1))
164+
else
165+
FALSE_POSITIVE_COUNT=$((FALSE_POSITIVE_COUNT + 1))
166+
fi
167+
done
168+
169+
# Save detection analysis results
170+
echo "$LEGITIMATE_DETECTIONS" > /tmp/legitimate_detections.txt
171+
echo "$FALSE_POSITIVE_COUNT" > /tmp/false_positive_count.txt
172+
else
173+
echo "✅ No detections found"
174+
echo "0" > /tmp/legitimate_detections.txt
175+
echo "0" > /tmp/false_positive_count.txt
176+
fi
177+
178+
LEGIT=$(cat /tmp/legitimate_detections.txt 2>/dev/null || echo "0")
179+
FP=$(cat /tmp/false_positive_count.txt 2>/dev/null || echo "0")
180+
181+
echo "LEGITIMATE_THREATS=${LEGIT}" >> $GITHUB_OUTPUT
182+
echo "FALSE_POSITIVES=${FP}" >> $GITHUB_OUTPUT
183+
184+
# Determine verdict
185+
if [ "$LEGIT" -gt 0 ]; then
186+
echo "VERDICT=BLOCKED" >> $GITHUB_OUTPUT
187+
echo "🚨 VERDICT: BLOCKED - ${LEGIT} legitimate threat(s) detected"
188+
else
189+
echo "VERDICT=PASSED" >> $GITHUB_OUTPUT
190+
echo "✅ VERDICT: PASSED - All detections are false positives"
191+
fi
192+
shell: bash
193+
- name: Save scan results
194+
working-directory: ${{ github.workspace }}
195+
run: |
196+
set -euo pipefail
197+
198+
FILENAME=$(basename "${{ inputs.ARTIFACT_FILE_PATH }}")
199+
RESULTS_FILE="${{ inputs.SCAN_RESULTS_PATH }}/${FILENAME}.json"
200+
201+
mkdir -p "$(dirname "${RESULTS_FILE}")"
202+
203+
# Create JSON structure with comprehensive scan results
204+
cat > "${RESULTS_FILE}" << EOFRESULTS
205+
{
206+
"filename": "${FILENAME}",
207+
"filepath": "${{ inputs.ARTIFACT_FILE_PATH }}",
208+
"sha256": "${{ steps.file-hash.outputs.FILE_HASH }}",
209+
"scan_date": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
210+
"virustotal": {
211+
"analysis_url": "${{ steps.virustotal-scan.outputs.analysis }}",
212+
"status": "scanned",
213+
"stats": {
214+
"malicious": ${{ steps.vt-results.outputs.MALICIOUS }},
215+
"suspicious": ${{ steps.vt-results.outputs.SUSPICIOUS }},
216+
"undetected": ${{ steps.vt-results.outputs.UNDETECTED }},
217+
"harmless": ${{ steps.vt-results.outputs.HARMLESS }}
218+
},
219+
"detections": ${{ steps.vt-results.outputs.DETECTIONS }},
220+
"analysis": {
221+
"legitimate_threats": ${{ steps.analyze-fp.outputs.LEGITIMATE_THREATS }},
222+
"false_positives": ${{ steps.analyze-fp.outputs.FALSE_POSITIVES }},
223+
"verdict": "${{ steps.analyze-fp.outputs.VERDICT }}"
224+
}
225+
}
226+
}
227+
EOFRESULTS
228+
229+
echo "✅ Scan results saved to: ${RESULTS_FILE}"
230+
echo "🔗 VirusTotal Analysis: ${{ steps.virustotal-scan.outputs.analysis }}"
231+
echo "📊 Verdict: ${{ steps.analyze-fp.outputs.VERDICT }}"
232+
233+
# Fail the action if legitimate threats are detected
234+
if [ "${{ steps.analyze-fp.outputs.VERDICT }}" = "BLOCKED" ]; then
235+
echo "❌ SCAN FAILED: Legitimate threats detected!"
236+
echo " Legitimate threats: ${{ steps.analyze-fp.outputs.LEGITIMATE_THREATS }}"
237+
echo " False positives: ${{ steps.analyze-fp.outputs.FALSE_POSITIVES }}"
238+
exit 1
239+
fi
240+
shell: bash

.github/actions/continuous-releasing/release-starter-kit/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ runs:
1313
- uses: ncipollo/release-action@b7eabc95ff50cbeeedec83973935c8f306dfcd0b
1414
with:
1515
allowUpdates: true
16-
artifacts: ${{ inputs.CICD_PROJECT_SLUG }}-${{ inputs.CICD_CURRENT_DATE }}-windows.zip,${{ inputs.CICD_PROJECT_SLUG }}-${{ inputs.CICD_CURRENT_DATE }}-linux.zip
16+
artifacts: ${{ inputs.CICD_PROJECT_SLUG }}-${{ inputs.CICD_CURRENT_DATE }}-windows.zip,${{ inputs.CICD_PROJECT_SLUG }}-${{ inputs.CICD_CURRENT_DATE }}-linux.zip,${{ inputs.CICD_PROJECT_SLUG }}-scan/SECURITY-SCAN-REPORT.md,${{ inputs.CICD_PROJECT_SLUG }}-scan/scan-report.json
1717
bodyFile: ${{ inputs.CICD_PROJECT_SLUG }}/RELEASE_NOTES.md
1818
makeLatest: false
1919
name: ${{ inputs.CICD_PROJECT_ACRONYM }} v${{ inputs.CICD_CURRENT_DATE }} (${{ inputs.GIT_CURRENT_COMMIT_HEAD_SHA }})

0 commit comments

Comments
 (0)