Microsoft Defender, Wacatac.B!ml, and the Go-binary problem on Windows

2026-05-23 · 6 min read · go, windows, defender, security, wails

The problem

Formidable is built on Wails 3 (alpha.84) + Go 1.25. Shipping it on Windows is fine until Microsoft Defender enters the room. Both the NSIS installer and the bare Formidable.exe get flagged as Trojan:Win32/Wacatac.B!ml.

VirusTotal results, consistent across multiple builds in May 2026:

  • NSIS installer: 2 / 71 (Microsoft + Trapmine)
  • Bare .exe: 1 / 71 (just Trapmine)

Trapmine is generic ML noise that fires on roughly any unsigned binary. The Microsoft flag is the actual issue, because that’s the one driving SmartScreen warnings, Defender quarantines on download, and the “Threat found, contact your administrator” experience for end users.

What Wacatac.B!ml actually means

The !ml suffix is Microsoft’s notation for a detection produced by their machine learning classifier, not a signature definition. The classifier looks at static features (PE structure, sections, imports, entropy, strings, metadata) and produces a probability score. Above a threshold, Wacatac fires.

It is not “Defender saw your binary execute something malicious.” It is “Defender’s ML model thinks your binary looks like the kind of binary that does.”

Diagnostic: dynamic sandbox analysis is clean

Before chasing mitigations, worth confirming what’s actually being detected. VirusTotal runs binaries through four sandbox analyzers (CAPA, CAPE, Jujubox, Zenbox). All four returned 0 detections in dynamic analysis. The installer dropped the expected files (Formidable.exe, WebView2 bootstrapper, shortcuts), made expected network calls (Microsoft’s WebView2 CDN), wrote expected registry keys (uninstaller entry).

The Wacatac flag is purely the static classifier. Nothing the binary actually does at runtime triggers any detection.

Mitigations tested

What I tried, with the score result for each.

Real VersionInfo metadata

Replaced the Wails 3 template defaults ("My Company", "My Product", "com.mycompany.myproduct", "A program that does X") with real values across build/config.yml, build/windows/info.json, and build/windows/wails.exe.manifest. No score change.

Proper Windows manifest

Added a <compatibility> block declaring the Win10 / 11 GUIDs, plus longPathAware, plus explicit requestedExecutionLevel="asInvoker". No score change.

Reproducible build flags

Added -buildid= to ldflags (on top of existing -trimpath) so identical source produces byte-identical binary hashes across rebuilds:

go build -tags production -trimpath -buildvcs=false \
  -ldflags="-buildid= -H windowsgui -X path/to/about.Version=$VERSION" \
  -o bin/Formidable.exe .

Theory: cloud reputation accumulates against a stable hash. No score change on first scan; should help over multiple releases.

Dropped strip flags

Removed -w -s from production ldflags. Theory: stripped binaries correlate with malware in Defender’s training data, so keeping the symbol table and DWARF debug info would push the binary into “legitimate OSS Go tool” territory. Binary grew about 30% as expected. No score change.

NSIS modernization

Replaced default zlib compressor with SetCompressor /SOLID lzma. Added BrandingText, custom Welcome and Finish page copy, an MIT license page, a “Launch Formidable” checkbox on the finish page, richer uninstaller registry entries (URLInfoAbout, HelpLink, NoModify, NoRepair). No score change.

SHA256SUMS published alongside artifacts

Doesn’t directly affect Defender, but reputation systems and humans both prefer checksum-verifiable downloads. No measurable effect on the flag.

Cleaned LLM-flavored prose from source

Removed em-dashes and AI-tell phrasing from code comments, i18n strings, error messages, manuals, README. Theory: the classifier might weight “looks AI-generated” as a signal, because actual recent malware is often LLM-bootstrapped. No score change. (Worth doing anyway for code quality, but not for this.)

Microsoft False Positive submission

Filed FP submissions for both Win10 and Win11 Defender databases. Same result on next scan, no change at the time of writing.

What I think is actually happening

Defender’s ML model weights Go-binary PE shape heavily. Go binaries have distinctive static characteristics:

  • Large .text section
  • Identifiable Go runtime symbols (runtime.morestack_noctxt, runtime.gcWriteBarrier, …)
  • gopclntab section (Go’s line-table format)
  • Specific stack-layout patterns
  • Statically linked, no dynamic CRT dependency

Real malware has heavily adopted Go since around 2020. BlackCat / ALPHV ransomware, Sliver C2, various info-stealers and loaders. Go’s “single static binary, easy cross-compile, no runtime dependency” properties are exactly what malware authors want. As that volume entered Defender’s training data, “Go-shaped binary” became a positive feature for “probably malicious.”

The same code shipped as an Electron app almost certainly wouldn’t trigger Wacatac, because electron.exe is byte-identical across millions of legitimate signed apps in Defender’s training set. The launcher EXE shape is effectively pre-vouched. The original Electron-based Formidable ran for years on Windows without a single Defender flag, despite literally using eval() in Node-side JavaScript. Go on Windows doesn’t get that grace.

This is not a fringe observation. Well-known Go OSS projects (rclone, syncthing, gh, ngrok, plenty more) have all been false-positive flagged at various points and have explicit FAQ entries documenting it.

What’s left to try

Authenticode signing

A Certum Open Source Code Signing certificate is in the verification queue at the time of writing. Once it lands, the binary carries a real Authenticode signature with verified publisher identity. This is the single change most likely to move the classifier, because “signed by verified publisher” is one of the heaviest positive features in the model.

Honest expectation: signing alone may not clear Wacatac on the very first signed release, but should clear it within a few signed releases as cloud reputation accumulates against the certificate subject.

Launcher + DLL split

Compile the Go side with -buildmode=c-shared producing formidable-backend.dll. Write a small launcher in C / Win32 that loads the DLL and hosts WebView2. The launcher EXE wouldn’t have Go-shape signals; the DLL still would, but DLLs are scored differently from EXEs by the classifier.

Some Go developers have used this approach specifically to dodge AV flags. Whether it’s worth the architectural cost of forking from Wails 3’s single-binary model depends on whether signing alone clears the issue.

Where I am

Waiting on the cert. I’ll update this post once the signed release ships and there’s data on whether Wacatac drops.

If you’ve shipped Go on Windows in volume and dealt with this pattern, two questions I would genuinely like to hear answers on:

  1. Has signing alone been sufficient to clear Wacatac on a Go binary in your experience, or did you need multiple signed releases before the classifier relaxed?
  2. Has anyone empirically measured the launcher + DLL split’s effect on the flag rate, or is it folklore?

Closing thought

The Defender Go-binary problem is one specific instance of a more general phenomenon: large vendor security tooling reflects the institutional preferences of the vendor. Microsoft’s training data and feature engineering naturally reflect what Microsoft engineers consider “legitimate.” A Go binary, built with tooling Microsoft didn’t author, that competes with .NET in many of the same niches, sits in a gray area of the training distribution.

You don’t get to vote on what their classifier scores high. You ship the binary you have, you sign it, and you write the postmortem.

Downloads at /download/. Source at github.com/petervdpas/Formidable2.

Comments