Compare commits

...
Author SHA1 Message Date
Paul Bakaus 4dc2714566 Remove hosted-runner exclusions for Defender reproduction
Enable download and cloud protection, retain NeverSend sample consent, and record MAPS connectivity. No binaries are executed. AI-assisted under maintainer direction.
2026-09-05 11:16:37 -07:00
Paul Bakaus efb7c67ab0 Verify Defender real-time protection during sample download
The first on-demand scans found no threats, but the runner defaulted to real-time monitoring off. Explicitly enable and verify it before comparing download-time behavior. AI-assisted under maintainer direction.
2026-09-05 11:14:29 -07:00
Paul Bakaus 2d5e0fb623 Investigate Windows release antivirus detections
Scan exact public 0.1.0 and 0.1.1 artifacts on a disposable runner, without executing them or disabling protection. Preserve scanner versions and text evidence. AI assistance: prepared with Codex under Paul Bakaus direction.
2026-09-05 11:12:45 -07:00
2 changed files with 151 additions and 0 deletions
@@ -0,0 +1,30 @@
name: Investigate Windows release detections
on:
workflow_dispatch:
push:
branches: [codex/scan-740-defender]
paths:
- .github/workflows/defender-release-scan.yml
- scripts/scan-windows-releases.ps1
permissions:
contents: read
jobs:
defender:
runs-on: windows-2022
timeout-minutes: 15
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
- name: Scan exact published samples without executing them
shell: pwsh
run: ./scripts/scan-windows-releases.ps1
- name: Preserve text evidence only
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: defender-scan-evidence
path: ${{ runner.temp }}/impeccable-defender-evidence/
if-no-files-found: warn
retention-days: 14
+121
View File
@@ -0,0 +1,121 @@
# Maintainer-authorized diagnostic for #740. Never executes release binaries,
# adds exclusions, restores quarantine, or disables antivirus protection.
# A completed scan is evidence for this engine/definition/host only, not a
# false-positive determination or clearance for other machines.
$ErrorActionPreference = 'Stop'
$PSNativeCommandUseErrorActionPreference = $false
$evidence = Join-Path $env:RUNNER_TEMP 'impeccable-defender-evidence'
New-Item -ItemType Directory -Path $evidence -Force | Out-Null
$samples = Join-Path $env:RUNNER_TEMP ('impeccable-defender-samples-' + [guid]::NewGuid())
New-Item -ItemType Directory -Path $samples | Out-Null
$report = [ordered]@{
startedAt = (Get-Date).ToUniversalTime().ToString('o')
os = (Get-CimInstance Win32_OperatingSystem).Caption
status = 'initializing'
samples = @()
}
$failed = $false
try {
$before = Get-MpComputerStatus
$report.before = $before | Select-Object AMServiceEnabled, AntivirusEnabled, RealTimeProtectionEnabled, AMEngineVersion, AMProductVersion, AntivirusSignatureVersion, AntivirusSignatureLastUpdated
$preferences = Get-MpPreference
$report.preferencesBefore = $preferences | Select-Object DisableRealtimeMonitoring, DisableIOAVProtection, DisableBehaviorMonitoring, MAPSReporting, SubmitSamplesConsent, ExclusionPath, ExclusionProcess, ExclusionExtension
if (-not $before.AMServiceEnabled) {
# Enabling an installed service is safe on this disposable runner. Never
# weaken protection or change remediation policies.
Start-Service WinDefend
}
$mp = Get-ChildItem "$env:ProgramData\Microsoft\Windows Defender\Platform\*\MpCmdRun.exe" -ErrorAction SilentlyContinue |
Sort-Object FullName -Descending | Select-Object -First 1 -ExpandProperty FullName
if (-not $mp) { $mp = "$env:ProgramFiles\Windows Defender\MpCmdRun.exe" }
if (-not (Test-Path -LiteralPath $mp)) { throw 'Microsoft Defender scanner is unavailable on this runner.' }
$report.scanner = $mp
$updateOutput = & $mp -SignatureUpdate 2>&1 | Out-String
$report.signatureUpdateExitCode = $LASTEXITCODE
$updateOutput | Set-Content (Join-Path $evidence 'signature-update.txt')
Write-Output $updateOutput
if ($report.signatureUpdateExitCode -ne 0) { throw 'Defender signature update failed; cannot claim a current-definition scan.' }
# Hosted images can default to real-time monitoring off. Enable it for the
# download-time reproduction and explicitly refuse to mislabel a scan if
# policy prevents activation. Never turn protection off.
# The hosted image excludes both entire drives. Remove those existing
# exclusions on this disposable machine; never add any. Enable the checks
# a normal consumer install has, but keep automatic sample uploads off.
foreach ($excludedPath in @($preferences.ExclusionPath)) {
if ($excludedPath) { Remove-MpPreference -ExclusionPath $excludedPath }
}
foreach ($excludedProcess in @($preferences.ExclusionProcess)) {
if ($excludedProcess) { Remove-MpPreference -ExclusionProcess $excludedProcess }
}
foreach ($excludedExtension in @($preferences.ExclusionExtension)) {
if ($excludedExtension) { Remove-MpPreference -ExclusionExtension $excludedExtension }
}
Set-MpPreference -DisableRealtimeMonitoring $false -DisableIOAVProtection $false -DisableBehaviorMonitoring $false -MAPSReporting Advanced -SubmitSamplesConsent NeverSend
Start-Sleep -Seconds 3
$current = Get-MpComputerStatus
$report.scannerStatus = $current | Select-Object AMServiceEnabled, AntivirusEnabled, RealTimeProtectionEnabled, AMEngineVersion, AMProductVersion, AntivirusSignatureVersion, AntivirusSignatureLastUpdated
if (-not $current.AMServiceEnabled -or -not $current.AntivirusEnabled) { throw 'Defender is not active; no scan verdict can be inferred.' }
if (-not $current.RealTimeProtectionEnabled) { throw 'Real-time monitoring remains disabled by runner policy; cannot reproduce download-time detection on this host.' }
$report.protectionPreferences = Get-MpPreference | Select-Object DisableRealtimeMonitoring, DisableIOAVProtection, DisableBehaviorMonitoring, MAPSReporting, SubmitSamplesConsent, ExclusionPath, ExclusionProcess, ExclusionExtension
$cloudOutput = & $mp -ValidateMapsConnection 2>&1 | Out-String
$report.cloudConnectionExitCode = $LASTEXITCODE
$cloudOutput | Set-Content (Join-Path $evidence 'cloud-connection.txt')
Write-Output $cloudOutput
$http = [System.Net.Http.HttpClient]::new()
$http.Timeout = [TimeSpan]::FromSeconds(90)
foreach ($sample in @(
@{ version = '0.1.0'; sha256 = 'a522fcf352b47f325facc3964b337a6d6d7d55e136440f1442e8013aad27f1d7' },
@{ version = '0.1.1'; sha256 = '5d2f844a7f1dac3acdbac6035785043ab0cba6b81c1af97ba5c9cd1ecdd3dff8' }
)) {
$item = [ordered]@{ version = $sample.version; expectedSha256 = $sample.sha256; status = 'pending' }
$file = Join-Path $samples ("impeccable-" + $sample.version + '.exe')
try {
$url = "https://github.com/pbakaus/impeccable/releases/download/engine-v$($sample.version)/impeccable-windows-x64.exe"
$bytes = $http.GetByteArrayAsync($url).GetAwaiter().GetResult()
$item.actualSha256 = [Convert]::ToHexString([System.Security.Cryptography.SHA256]::HashData($bytes)).ToLowerInvariant()
$item.bytes = $bytes.Length
if ($item.actualSha256 -ne $sample.sha256) { throw 'Release bytes do not match the pinned investigation hash.' }
[System.IO.File]::WriteAllBytes($file, $bytes)
$bytes = $null
if (-not (Test-Path -LiteralPath $file)) { throw 'Sample disappeared after writing; inspect real-time detection evidence.' }
$item.authenticodeStatus = [string](Get-AuthenticodeSignature -LiteralPath $file).Status
# This suppresses remediation for this custom scan, not real-time
# protection. Detections appear in stdout; preserve it without guessing
# from exit 2 (which can mean either a detection or a scanning error).
$scanOutput = & $mp -Scan -ScanType 3 -File $file -DisableRemediation 2>&1 | Out-String
$item.scanExitCode = $LASTEXITCODE
$scanOutput | Set-Content (Join-Path $evidence ("scan-" + $sample.version + '.txt'))
Write-Output "Engine $($sample.version):"
Write-Output $scanOutput
$item.presentAfterScan = Test-Path -LiteralPath $file
$item.status = 'scan-finished-review-output'
if ($item.scanExitCode -ne 0) { $failed = $true }
} catch {
$item.status = 'unavailable-or-error'
$item.error = $_.Exception.Message
$failed = $true
}
$report.samples += $item
}
$http.Dispose()
$report.status = 'completed-review-evidence'
} catch {
$report.status = 'unavailable-or-error'
$report.error = $_.Exception.Message
$failed = $true
} finally {
try {
Get-MpThreatDetection | Select-Object InitialDetectionTime, ThreatID, Resources, ActionSuccess |
ConvertTo-Json -Depth 6 | Set-Content (Join-Path $evidence 'realtime-detections.json')
Get-MpThreat | Select-Object ThreatID, ThreatName, IsActive, DidThreatExecute |
ConvertTo-Json -Depth 6 | Set-Content (Join-Path $evidence 'threat-names.json')
} catch { $_.Exception.Message | Set-Content (Join-Path $evidence 'detection-query-error.txt') }
$report.finishedAt = (Get-Date).ToUniversalTime().ToString('o')
$json = $report | ConvertTo-Json -Depth 8
$json | Set-Content (Join-Path $evidence 'report.json')
Write-Output $json
if ($env:GITHUB_STEP_SUMMARY) {
"## Defender investigation evidence`n`nThis is not a vendor false-positive determination.`n`n``````json`n$json`n``````" | Add-Content $env:GITHUB_STEP_SUMMARY
}
}
if ($failed) { exit 1 }