Skip to content

Windows Defender false positive: Trojan:Win32/Bearfoos.B!ml on APM binary #487

@danielmeppiel

Description

@danielmeppiel

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:

pip install apm-cli

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:

  1. Submit binary to Microsoft for false-positive review

  2. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugDeprecated: use type/bug. Kept for issue history; will be removed in milestone 0.10.0.documentationDeprecated: use type/docs. Kept for issue history; will be removed in milestone 0.10.0.securityDeprecated: use theme/security. Kept for issue history; will be removed in milestone 0.10.0.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions