Problem
Users report that Windows Defender quarantines the APM binary:
- Detection:
Trojan:Win32/Bearfoos.B!ml
- Status: Quarantined
- Description: "This program is dangerous and executes commands from an attacker."
This is a false positive. The !ml suffix indicates a machine-learning/heuristic-based detection, not a specific malware signature match. This is a well-documented class of false positives affecting PyInstaller-built binaries.
Root Cause Analysis
Multiple factors combine to trigger the ML heuristic:
| Factor |
Current State |
Risk |
| No Windows PE version info |
No version_info in build/apm.spec |
HIGH -- anonymous executables score poorly in ML models |
| No code signing |
codesign_identity=None in spec |
HIGH -- unsigned binaries are inherently suspicious to Defender |
| UPX compression is conditional |
is_upx_available() in spec |
MEDIUM -- UPX packing is a classic AV red flag; Windows CI doesn't install UPX today but local builds or future runners might |
| Stock PyInstaller bootloader |
Default bootloader used |
LOW-MEDIUM -- the stock bootloader hash exists in AV databases with mixed reputation |
Why This Happens
PyInstaller bundles a Python interpreter + application code into an executable. The stock bootloader binary has a known hash that AV engines have seen in both legitimate and malicious contexts. Without PE metadata (company name, version, copyright) or a code signature, the ML model has no positive trust signals to offset the suspicious pattern of "unknown executable that unpacks and runs code."
Implementation Plan (Tier 1 -- Code Changes)
These changes can be implemented in a single PR. Each task includes the exact file, location, and code needed.
Task 1: Add Windows PE version info to build/apm.spec
File: build/apm.spec
Location: After the existing imports (line 6), before spec_dir (line 17)
What: Add a VSVersionInfo block that embeds company/product metadata into the PE header.
Add this code after the existing is_upx_available() function (after line 14):
# --- Windows PE version info (reduces AV false positives) ---
# Anonymous executables without metadata score poorly in AV ML models.
# Embedding company, product, and version info into the PE header provides
# positive trust signals to heuristic scanners like Windows Defender.
def _read_version_from_pyproject():
"""Read version string from pyproject.toml and return as 4-tuple."""
import re
pyproject = repo_root / 'pyproject.toml'
if pyproject.exists():
content = pyproject.read_text(encoding='utf-8')
match = re.search(r'version\s*=\s*["\']([^"\']+)["\']', content)
if match:
parts = re.match(r'(\d+)\.(\d+)\.(\d+)', match.group(1))
if parts:
return (int(parts.group(1)), int(parts.group(2)),
int(parts.group(3)), 0)
return (0, 0, 0, 0)
_win_version_info = None
if sys.platform == 'win32':
try:
from PyInstaller.utils.win32 import versioninfo as vi
_ver = _read_version_from_pyproject()
_ver_str = f'{_ver[0]}.{_ver[1]}.{_ver[2]}'
_win_version_info = vi.VSVersionInfo(
ffi=vi.FixedFileInfo(
filevers=_ver,
prodvers=_ver,
mask=0x3f,
flags=0x0,
OS=0x40004, # VOS_NT_WINDOWS32
fileType=0x1, # VFT_APP
subtype=0x0,
),
kids=[
vi.StringFileInfo([vi.StringTable('040904B0', [
vi.StringStruct('CompanyName', 'Microsoft'),
vi.StringStruct('FileDescription',
'APM - AI Package Manager'),
vi.StringStruct('FileVersion', _ver_str),
vi.StringStruct('InternalName', 'apm'),
vi.StringStruct('LegalCopyright',
'Copyright (c) Microsoft Corporation'),
vi.StringStruct('OriginalFilename', 'apm.exe'),
vi.StringStruct('ProductName', 'APM CLI'),
vi.StringStruct('ProductVersion', _ver_str),
])]),
vi.VarFileInfo([vi.VarStruct('Translation', [1033, 1200])]),
],
)
except ImportError:
_win_version_info = None
Then in the EXE() call (currently around line 234), add the version parameter:
exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name='apm',
debug=False,
bootloader_ignore_signals=False,
strip=_strip,
upx=should_use_upx(), # Changed -- see Task 2
upx_exclude=[],
runtime_tmpdir=None,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
version=_win_version_info, # NEW -- PE metadata for Windows
)
Task 2: Explicitly disable UPX on Windows in build/apm.spec
File: build/apm.spec
Location: Replace or augment the existing is_upx_available() function (lines 8-14)
Keep is_upx_available() but add a wrapper:
def should_use_upx():
"""Enable UPX only on non-Windows platforms where it is available.
UPX-compressed PE binaries trigger ML-based AV false positives
(e.g. Trojan:Win32/Bearfoos.B!ml) on Windows Defender.
"""
if sys.platform == 'win32':
return False
return is_upx_available()
Then replace both calls to is_upx_available() in EXE() and COLLECT() with should_use_upx():
- Line ~243:
upx=should_use_upx(),
- Line ~260:
upx=should_use_upx(),
Task 3: Add AV false-positive troubleshooting to installation docs
File: docs/src/content/docs/getting-started/installation.md
Location: In the Troubleshooting section, after the existing "File access errors on Windows" subsection (after line 161, before "## Next steps")
Add this new subsection:
### Windows Defender or antivirus flags APM as a threat
Pre-built APM binaries are packaged with [PyInstaller](https://pyinstaller.org/), which some antivirus engines flag with heuristic detections. This is a **false positive** -- APM does not contain malware.
**Quick fix -- add an exclusion:**
1. Open **Windows Security** > **Virus & threat protection** > **Protection history**
2. Find the APM detection and select **Actions** > **Allow on device**
Or via PowerShell (administrator):
```powershell
Add-MpPreference -ExclusionPath "C:\path\to\apm"
Verify the binary:
Every release includes SHA256 checksums. Compare your download against the .sha256 file on the GitHub Releases page:
(Get-FileHash apm-windows-x86_64.zip -Algorithm SHA256).Hash
Report false positives to Microsoft:
Submit the binary at Microsoft Security Intelligence selecting Should not be detected (false positive).
Alternative -- install via pip:
The pip-installed version uses the system Python interpreter and does not trigger AV detections.
### Task 4: Update CHANGELOG.md
**File:** `CHANGELOG.md`
**Location:** Under `## [Unreleased]`
Add under `### Fixed`:
```markdown
- Windows Defender false-positive (`Trojan:Win32/Bearfoos.B!ml`) mitigation: embed PE version info (company, product, version) in Windows binary and disable UPX compression on Windows builds (#ISSUE_NUMBER)
Add under ### Added:
- Troubleshooting guide for antivirus false positives on Windows pre-built binaries (#ISSUE_NUMBER)
Tier 2 -- Process Items (Post-Release)
These are manual actions to take after the Tier 1 PR is merged and a new release is cut:
-
Submit binary to Microsoft for false-positive review
-
Pre-release VirusTotal scan
- Upload the Windows
.zip to https://www.virustotal.com before publishing a release
- Check detection count -- ideally 0-2 detections (some obscure engines always flag PyInstaller)
- Document results in the release notes if any detections are found
Tier 3 -- Future Infrastructure Investment
These require separate issues and infrastructure setup:
Windows Authenticode Code Signing
- Options: Traditional EV code signing certificate from a CA, or Azure Trusted Signing (formerly Azure Code Signing)
- Impact: Highest-impact mitigation -- signed binaries are trusted by Defender SmartScreen and enterprise policies
- Effort: Requires certificate provisioning, CI secret management, and
signtool.exe integration in the build workflow
- Track as separate issue
macOS Binary Notarization
- Requires: Apple Developer account +
notarytool integration in CI
- Impact: Eliminates macOS Gatekeeper warnings for downloaded binaries
- Track as separate issue
CI-Integrated VirusTotal Scanning
- Add a post-build step that uploads Windows binary to VirusTotal API and fails/warns if detection count exceeds threshold
- Requires: VirusTotal API key (free tier supports 4 req/min)
- Track as separate issue
Custom PyInstaller Bootloader
- Compiling PyInstaller's bootloader from source produces a unique binary hash not in AV databases
- Lower priority since Tier 1 + Tier 2 should resolve most detections
- Track as separate issue only if detections persist after other mitigations
References
Problem
Users report that Windows Defender quarantines the APM binary:
Trojan:Win32/Bearfoos.B!mlThis is a false positive. The
!mlsuffix indicates a machine-learning/heuristic-based detection, not a specific malware signature match. This is a well-documented class of false positives affecting PyInstaller-built binaries.Root Cause Analysis
Multiple factors combine to trigger the ML heuristic:
version_infoinbuild/apm.speccodesign_identity=Nonein specis_upx_available()in specWhy This Happens
PyInstaller bundles a Python interpreter + application code into an executable. The stock bootloader binary has a known hash that AV engines have seen in both legitimate and malicious contexts. Without PE metadata (company name, version, copyright) or a code signature, the ML model has no positive trust signals to offset the suspicious pattern of "unknown executable that unpacks and runs code."
Implementation Plan (Tier 1 -- Code Changes)
These changes can be implemented in a single PR. Each task includes the exact file, location, and code needed.
Task 1: Add Windows PE version info to
build/apm.specFile:
build/apm.specLocation: After the existing imports (line 6), before
spec_dir(line 17)What: Add a
VSVersionInfoblock that embeds company/product metadata into the PE header.Add this code after the existing
is_upx_available()function (after line 14):Then in the
EXE()call (currently around line 234), add theversionparameter:Task 2: Explicitly disable UPX on Windows in
build/apm.specFile:
build/apm.specLocation: Replace or augment the existing
is_upx_available()function (lines 8-14)Keep
is_upx_available()but add a wrapper:Then replace both calls to
is_upx_available()inEXE()andCOLLECT()withshould_use_upx():upx=should_use_upx(),upx=should_use_upx(),Task 3: Add AV false-positive troubleshooting to installation docs
File:
docs/src/content/docs/getting-started/installation.mdLocation: In the Troubleshooting section, after the existing "File access errors on Windows" subsection (after line 161, before "## Next steps")
Add this new subsection:
Verify the binary:
Every release includes SHA256 checksums. Compare your download against the
.sha256file on the GitHub Releases page:Report false positives to Microsoft:
Submit the binary at Microsoft Security Intelligence selecting Should not be detected (false positive).
Alternative -- install via pip:
pip install apm-cliThe pip-installed version uses the system Python interpreter and does not trigger AV detections.
Add under
### Added:- Troubleshooting guide for antivirus false positives on Windows pre-built binaries (#ISSUE_NUMBER)Tier 2 -- Process Items (Post-Release)
These are manual actions to take after the Tier 1 PR is merged and a new release is cut:
Submit binary to Microsoft for false-positive review
microsoftGitHub orgPre-release VirusTotal scan
.zipto https://www.virustotal.com before publishing a releaseTier 3 -- Future Infrastructure Investment
These require separate issues and infrastructure setup:
Windows Authenticode Code Signing
signtool.exeintegration in the build workflowmacOS Binary Notarization
notarytoolintegration in CICI-Integrated VirusTotal Scanning
Custom PyInstaller Bootloader
References