11#! /usr/bin/env bash
22# Wait for VirusTotal scans to complete and check results.
33# Expects: VT_API_KEY, VT_ANALYSIS (comma-separated "file=URL" pairs)
4- # Optional: VERSION (release tag) + GH_TOKEN — these enable the PRE-RELEASE
5- # single-ML-detection tolerance described below.
64set -euo pipefail
75
86MIN_ENGINES=60
9- rm -f /tmp/vt_gate_fail /tmp/vt_suspects.tsv /tmp/vt_tolerated.tsv
10-
11- # ── Pre-release ML false-positive tolerance ──────────────────────────────────
12- # A verdict may be downgraded from BLOCKED to TOLERATED only when ALL of:
13- # 1. the version is a pre-release (-rc. / -pre / -alpha / -beta),
14- # 2. every failing file was flagged by exactly ONE engine,
15- # 3. that engine is Microsoft and the verdict is a machine-learning
16- # heuristic (result string ends in "!ml") — never a signature name,
17- # 4. an up-to-date Microsoft Defender ENDPOINT has scanned the identical
18- # bytes clean, proven by a hash-pinned evidence file attached to the
19- # draft release (defender-endpoint-verification.txt, produced by
20- # scripts/ci/av-endpoint-verify.sh).
21- #
22- # Why this is sound: VT's "Microsoft" engine is a differently-configured
23- # instance of the same engine that ships in Windows, and for our large
24- # binaries it sits at an unstable ML decision boundary — across four release
25- # cycles the Wacatac!ml flag appeared and disappeared with no code-side cause
26- # (stripping, downloader removal and metadata changes each "fixed" it only
27- # until the next build). The shipping product's verdict on identical bytes,
28- # with signatures updated minutes before the scan, is the stronger statement
29- # of Microsoft's own estimate. Multi-engine hits, signature-named verdicts,
30- # non-Microsoft engines, timeouts and stable releases ALWAYS block.
31- TOLERANCE_ELIGIBLE=false
32- case " ${VERSION:- } " in
33- * -rc.* | * -pre* | * -alpha* | * -beta* ) TOLERANCE_ELIGIBLE=true ;;
34- esac
7+ rm -f /tmp/vt_gate_fail
358
369echo " === Waiting for VirusTotal scans to fully complete ==="
3710
@@ -95,22 +68,6 @@ print(f'{status},{malicious},{suspicious},{completed},{total}')
9568 echo " BLOCKED: $BASENAME flagged ($MALICIOUS malicious, $SUSPICIOUS suspicious / $COMPLETED engines)"
9669 echo " $URL "
9770 echo " FAIL" >> /tmp/vt_gate_fail
98- # Record WHO flagged it so the tolerance can classify the failure.
99- FLAG_INFO=$( echo " $RESULT " | python3 -c "
100- import json, sys
101- d = json.loads(sys.stdin.read())
102- res = d.get('data', {}).get('attributes', {}).get('results', {})
103- flagged = [(e, (r.get('result') or '?')) for e, r in res.items()
104- if r.get('category') in ('malicious', 'suspicious')]
105- engines = ';'.join(e for e, _ in flagged)
106- verdicts = ';'.join(v for _, v in flagged)
107- print(f'{len(flagged)}\t{engines}\t{verdicts}')
108- " 2> /dev/null || printf ' parse-error\tparse-error\tparse-error\n' )
109- SHA256=$( (sha256sum " $FILE " 2> /dev/null || shasum -a 256 " $FILE " 2> /dev/null) \
110- | awk ' NR==1{print $1}' )
111- [ -n " $SHA256 " ] || SHA256=" unknown"
112- printf ' %s\t%s\t%s\t%s\n' " $BASENAME " " $SHA256 " " $FLAG_INFO " " $COMPLETED " \
113- >> /tmp/vt_suspects.tsv
11471 else
11572 echo " OK: $BASENAME clean ($COMPLETED engines, 0 detections)"
11673 fi
@@ -127,76 +84,8 @@ print(f'{len(flagged)}\t{engines}\t{verdicts}')
12784 fi
12885done
12986
130- if [ ! -f /tmp/vt_gate_fail ]; then
131- echo " === All VirusTotal scans passed ==="
132- exit 0
133- fi
134-
135- # ── Classify the failures against the tolerance conditions ──────────────────
136- FAILS=$( wc -l < /tmp/vt_gate_fail | tr -d ' ' )
137- SUSPECTS=0
138- [ -f /tmp/vt_suspects.tsv ] && SUSPECTS=$( wc -l < /tmp/vt_suspects.tsv | tr -d ' ' )
139-
140- TOLERABLE=" $TOLERANCE_ELIGIBLE "
141- # Timeouts and unparseable URLs produce FAIL lines with no suspect record —
142- # those are never tolerable, and the counts diverging catches them.
143- [ " $FAILS " -eq " $SUSPECTS " ] || TOLERABLE=false
144- if [ " $TOLERABLE " = " true" ]; then
145- while IFS=$' \t ' read -r _name _sha nflagged engines verdicts _completed; do
146- [ " $nflagged " = " 1" ] || TOLERABLE=false
147- [ " $engines " = " Microsoft" ] || TOLERABLE=false
148- case " $verdicts " in
149- * ' !ml' ) ;;
150- * ) TOLERABLE=false ;;
151- esac
152- done < /tmp/vt_suspects.tsv
153- fi
154-
155- if [ " $TOLERABLE " != " true" ]; then
87+ if [ -f /tmp/vt_gate_fail ]; then
15688 echo " BLOCKED: One or more VirusTotal checks failed"
15789 exit 1
15890fi
159-
160- # ── Every failure matches the ML-FP pattern: require endpoint evidence ──────
161- EVIDENCE=/tmp/defender-endpoint-verification.txt
162- rm -f " $EVIDENCE "
163- if ! gh release download " ${VERSION:? } " \
164- --pattern defender-endpoint-verification.txt \
165- --output " $EVIDENCE " --repo " ${GITHUB_REPOSITORY:- DeusData/ codebase-memory-mcp} " \
166- 2> /dev/null; then
167- echo " BLOCKED: all detections match the pre-release ML false-positive pattern"
168- echo " (single engine, Microsoft, '!ml' verdict) but the release carries no"
169- echo " Defender endpoint evidence. To verify Microsoft's shipping product"
170- echo " against these exact bytes and attach the proof, run locally:"
171- echo " "
172- NAMES=$( cut -f1 /tmp/vt_suspects.tsv | sed ' s/\.exe$//' | tr ' \n' ' ' )
173- echo " scripts/ci/av-endpoint-verify.sh $VERSION $NAMES --upload"
174- echo " "
175- echo " then RE-RUN THIS JOB (Re-run failed jobs — it does not rebuild, so"
176- echo " the bytes and hashes stay identical)."
177- exit 1
178- fi
179-
180- MISSING=false
181- while IFS=$' \t ' read -r name sha _rest; do
182- if grep -qE " ^${sha} [[:space:]]+[^[:space:]]+[[:space:]]+CLEAN[[:space:]]*$" " $EVIDENCE " ; then
183- echo " ENDPOINT-VERIFIED: $name ($sha ) clean on a current Defender endpoint"
184- else
185- echo " BLOCKED: $name ($sha ) has no CLEAN endpoint-evidence line"
186- MISSING=true
187- fi
188- done < /tmp/vt_suspects.tsv
189- if [ " $MISSING " = " true" ]; then
190- echo " Evidence file present but stale or incomplete — regenerate it with"
191- echo " scripts/ci/av-endpoint-verify.sh (hashes must match this build)."
192- exit 1
193- fi
194-
195- cp /tmp/vt_suspects.tsv /tmp/vt_tolerated.tsv
196- echo " === TOLERATED (pre-release ML false-positive policy) ==="
197- sed -n ' s/^# //p' " $EVIDENCE "
198- echo " Every remaining engine reports clean; the detections above are Microsoft"
199- echo " ML heuristics that Microsoft's own shipping Defender does not reproduce."
200- echo " The release notes will state this transparently, with the evidence file"
201- echo " attached as a release asset."
202- exit 0
91+ echo " === All VirusTotal scans passed ==="
0 commit comments