Skip to content

ci(release): double-verify ML antivirus false positives instead of re-rolling builds - #1340

Merged
DeusData merged 1 commit into
mainfrom
ci/release-skip-tests
Jul 29, 2026
Merged

ci(release): double-verify ML antivirus false positives instead of re-rolling builds#1340
DeusData merged 1 commit into
mainfrom
ci/release-skip-tests

Conversation

@DeusData

Copy link
Copy Markdown
Owner

What

Release run 30464288732 cleared every test, soak and smoke leg and was blocked solely by the VirusTotal gate: three linux-amd64 binaries flagged 1/62 by Microsoft Trojan:Script/Wacatac.B!ml. The binaries are fully stripped (0 symbols, verified on the exact artifacts) — the state that scanned clean in the two previous cycles — while a real Defender endpoint (engine 1.1.26060.3008, signatures 1.455.410.0 updated the same day, RTP on) scans the identical bytes clean, files intact, zero detections.

Four release cycles of evidence: this is an unstable ML decision boundary, not a property of the code. Stripping, downloader removal, launcher elimination and metadata changes each "fixed" it only until a later build flipped the verdict again.

This PR replaces build-side whack-a-mole with double verification:

  • scripts/ci/check-virustotal.sh may downgrade BLOCKED → TOLERATED only when all hold: pre-release version (-rc./-pre/-alpha/-beta) · exactly one flagging engine · that engine is Microsoft · the verdict is a pure ML heuristic (!ml) · and hash-pinned Defender endpoint evidence is attached to the draft release. Stable releases, multi-engine hits, signature-named verdicts, non-Microsoft engines, timeouts: always block.
  • scripts/ci/av-endpoint-verify.sh (new) produces the evidence: downloads the draft assets, signature-updates and scans them on a real Defender endpoint (local Windows VM), refuses to attest if RTP is off or Defender itself detects, uploads defender-endpoint-verification.txt. The gate prints the exact command when evidence is missing; re-running the failed verify job does not rebuild, so hashes stay fixed.
  • scripts/ci/append-vt-notes.sh (new, extracted from inline YAML per the venue-parity contract) renders the notes honestly: tolerated files read "1/62 ⚠️ ML false positive, endpoint-verified clean" — never a false "0 detections".
  • release.yml gains a skip_tests dispatch input for re-releases of an already test-green tree (build/smoke/soak/verify always run; lint failures still gate).

CI-change scope

  • Gating impact: loosens the release VT gate for pre-release versions only, under the five conditions above, each of which fails closed. Stable-release gating is unchanged. skip_tests weakens nothing by default (opt-in dispatch input, off unless explicitly set).
  • Cost: none — no new jobs; two inline steps became script calls.
  • Flake surface: none added; the gate still polls the same VT API. The tolerance path adds one gh release download.
  • Trigger scope: unchanged (workflow_dispatch only).

Tests

  • New tests/test_vt_gate_tolerance_contract.sh pins all nine decision directions against a stubbed VT API + release store: clean pass · stable-never-tolerates · missing evidence blocks with operator instructions · pattern+evidence tolerates · stale-hash evidence blocks · endpoint-DETECTED blocks · signature-named verdict blocks · non-Microsoft engine blocks · multi-engine blocks.
  • Full local contract set: 15/15 green (includes venue-parity over the edited workflow).
  • Endpoint harness pattern (push-file + ASCII ps1 driver) exercised against the real VM today with the actual flagged artifacts.

…-rolling builds

Release run 30464288732 was blocked by the VirusTotal gate: three linux-amd64
binaries flagged 1/62 by Microsoft's Wacatac.B!ml -- fully stripped binaries
(0 symbols, verified on the exact artifacts), the state that scanned clean in
the two previous cycles. Meanwhile a real Defender endpoint (engine
1.1.26060.3008, signatures 1.455.410.0 updated the same day, RTP on) scans
the identical bytes clean. Four cycles of evidence now say the same thing:
this verdict is an unstable ML decision boundary, not a property of the code,
and no build-side lever moves it durably -- stripping, downloader removal and
metadata changes each "worked" only until a later build flipped it back.

So stop treating the flag as buildable-away and verify it honestly instead:

  check-virustotal.sh may downgrade BLOCKED to TOLERATED only when ALL hold:
    - pre-release version (-rc./-pre/-alpha/-beta); stable releases never
    - every failing file flagged by exactly ONE engine
    - that engine is Microsoft and the verdict ends in "!ml" (never a
      signature name)
    - hash-pinned Defender ENDPOINT evidence is attached to the draft release
      (defender-endpoint-verification.txt) proving Microsoft's shipping
      product, signature-updated at scan time, reports the exact bytes clean

  av-endpoint-verify.sh (new) produces that evidence: downloads the draft
  assets, scans them on the local Windows VM endpoint, refuses to attest if
  RTP is off or Defender itself detects, uploads the hash-pinned result.
  The gate prints the exact command when evidence is missing; re-running the
  failed verify job does not rebuild, so the bytes stay fixed.

  append-vt-notes.sh (new, extracted from inline YAML per venue-parity) then
  renders the release-notes table honestly: a tolerated file reads "1/62 ML
  false positive, endpoint-verified clean", never "0 detections".

tests/test_vt_gate_tolerance_contract.sh pins all nine decision directions
against a stubbed VT API and release store -- clean pass, stable-never,
missing/stale/DETECTED evidence, signature-named verdict, non-Microsoft
engine, multi-engine -- so the tolerance provably fails closed.

Also: release.yml gains skip_tests for re-releases of an already test-green
tree (build/smoke/soak/verify always run; lint failures still gate via
!cancelled() && !failure()).

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
@DeusData
DeusData merged commit 4730fe9 into main Jul 29, 2026
9 checks passed
meikis pushed a commit to meikis/codebase-memory-mcp that referenced this pull request Jul 29, 2026
The pre-release ML false-positive tolerance (single-engine Microsoft "!ml"
verdicts downgradable with Defender endpoint evidence, DeusData#1340) is reverted by
owner decision: cbm does not ship binaries carrying a VirusTotal detection,
demonstrably false or not. A "trojan" badge on a release asset is a
reputation cost the project is not willing to price in, however good the
accompanying evidence.

The gate returns to its original form: any detection, by any engine, on any
artifact, on any version blocks the release. The endpoint verification tool
and the evidence side-channel are removed with it; the notes renderer keeps
its extracted-script form but only ever states a verified "0 detections".

False positives are resolved upstream instead: verify the bytes on a real
Defender endpoint, submit a Microsoft false-positive report for the exact
hashes, wait for the detection to clear, then RE-RUN the failed verify job --
which does not rebuild, so the cleared hashes are the shipped hashes.

tests/test_vt_gate_zero_tolerance_contract.sh pins the decision: clean
passes; 1 malicious and 1 suspicious each block across stable, -rc., -pre
and -alpha versions; plus a tripwire for the specific reverted evidence
mechanism returning. Loosening this gate again has to consciously delete
that contract.

The skip_tests dispatch input and the script-extracted notes step survive
the revert -- both are orthogonal to gating policy.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant