Compiling Go projects into a Windows executable is problematic, due to a lack of communication between Google and Microsoft
If you’re a Go developer, you’re probably aware that cross-compiling is one of the big features of the language. This allows you to compile your code on one platform for use on another. For example, you can compile your code on a Mac for use on a Windows machine. This is fantastic for developers who want to target multiple platforms with their code.
However, there is a big problem when compiling Go projects for use on Windows.
When a Go project is compiled for Windows, a single binary is created (on Windows, these are .exe files). The process of how Go compiles this binary is relatively unique, and often confuses antivirus scanners, resulting in the binary being mistakenly detected as a virus.
This is of course quite problematic, as it can happen that the browser does not allow a download of the compiled file at all. That was the case in one of my open-source projects. Users were not able to download new releases. If they disabled the virus scanner in Chrome and downloaded the executable, the file was deleted by Windows Defender directly after the download. This made it impossible to install the program without turning off the virus protection of the entire computer.
I wrote a simple “Hello World” program in Go and compiled it for Windows, Linux, and macOS. Here are the results of different virus scanners:
I did not include the test results of macOS, as they were the same as the Linux test results.
All programs are compiled with the latest version at the moment (v1.19.2).
Compiled for Windows
GOOS=windows GOARCH=386 go build .
Compiled for Linux
GOOS=linux GOARCH=amd64 go build .
GOOS=linux GOARCH=386 go build .
Despite the fact that the builds are a simple “Hello World” program, the Windows builds trigger some virus scanners. In our “Hello World” example, Windows Defender did not detect anything. Unfortunately, this happens more often with real programs.
Also, to be seen, is that 32-bit builds were detected more often than 64bit builds.
The worrying thing here is that just about any build for Windows is detected as a virus, no matter what the program actually does. Especially in the open source scene, this is a big blow. Often, there are no big companies behind the programs, which can actively take care of their releases, but private persons. In many cases, the wrong virus detection leads to the fact that a project cannot be installed under Windows without manually overwriting the antivirus protection, which is generally considered bad.
In my opinion, the main problem is the lack of communication between Google and Microsoft.
Google owns Go. Thus, Google should work to ensure that programs written in Go run smoothly on every supported platform. After all, one of the biggest features of Go is cross-compiling. On the other hand, Microsoft should also make sure that its virus scanner does not suspect harmless programs just because they use a certain programming language.
Especially since Microsoft also owns GitHub, Microsoft should have a special interest in helping the open source community.
The best solution would therefore be for the relevant teams from Google and Microsoft to get together and try to tackle this problem.
After all, this problem has existed for many years now and the only official thing that ever came from Google was a FAQ entry in which this behavior is classified as normal. It should not be normal.
(FAQ entry: https://go.dev/doc/faq#virus)
Unfortunately, the problem is not easily fixed. However, there are a few approaches that can help prevent executables from being considered a virus.
On the internet, you frequently read that it should help to import “C.” Since CGO is then used for the build, the structure of the binary changes somewhat and should be less susceptible to false positives.
Lo and behold, it’s true. Instead of seven detections, we now “only” have dive in the 64-bit build.
Unfortunately, this is not enough to guarantee a good deployment.
Another method is called “code signing.” Code signing is the process of cryptographically signing binaries to verify the origin of the program and the developer. To be able to sign a binary, however, you need a “code-signing certificate.” These certificates start at several hundred Euros per year. Better certificates, which Windows trusts directly and no longer trigger a smartscreen message, can cost up to thousands of Euros per year.
This is the most commonly suggested solution. Unfortunately, a signed binary is not a certificate for the absence of viruses. The certificate certifies only who has built the program. So even here, false virus messages can appear.
Obfuscating the build
Another (often suggested) method is, to obfuscate the build. That way, the structure of the binary changes, and it no longer stores things, such as variable names (they get replaced with random values). I tried it out, and this is the result of a 64-bit build:
As you can see, the detections even doubled. This isn’t surprising, as obfuscated builds are even less transparent about their behavior than non-obfuscated builds. Viruses are often obfuscated to hide their intentions.
Compressing the binary, with UPX for example, results in the same results for the same reasons.
Manually removing the binary from the virus databases
This is the safest way. Many virus scanners, including Microsoft Defender, allow false detections to be reported. Microsoft Defender requires a form to be filled out online.
Unfortunately, it is no longer possible to have a fully automated deploy pipeline this way. Each release must be submitted manually to Microsoft (and other virus scanner companies) for verification.
There are a couple of methods to avoid virus detections, but they are less user-friendly.
- Telling your users to use “go install your.package/path@latest”
Go install compiles the program on your user’s device. This does not trigger a virus detection.
- Using docker images
By compiling a docker image, your users can run that image in an isolated environment. This is usually nice for testing CLIs, but in a real scenario, your users probably want the CLI on the host machine.
Waiting is probably the best thing you can do now. As more Go projects get compiled every day, virus scanners should learn the structure and detect it more reliable. Maybe Google and Microsoft even get together and try to solve this problem faster. Only time will tell.