|
1 | 1 | name: av-windows-defender |
2 | 2 | on: |
3 | | - workflow_dispatch: |
4 | | - inputs: |
5 | | - tag: |
6 | | - description: 'Release tag to scan (e.g., v0.15.16)' |
7 | | - required: false |
| 3 | + pull_request: |
8 | 4 | release: |
9 | 5 | types: [published] |
10 | | - pull_request: |
11 | | - branches: [dev] |
12 | | - schedule: |
13 | | - - cron: "0 4 * * 1" |
| 6 | + workflow_dispatch: |
14 | 7 |
|
15 | 8 | permissions: |
16 | 9 | contents: read |
17 | 10 |
|
18 | 11 | jobs: |
19 | | - defender-pr: |
20 | | - if: github.event_name == 'pull_request' || github.event_name == 'push' |
21 | | - runs-on: windows-latest |
| 12 | + build: |
| 13 | + runs-on: ubuntu-latest |
22 | 14 | steps: |
23 | | - - uses: actions/checkout@v4 |
24 | | - - name: Prepare PR scan payload |
25 | | - id: prep |
26 | | - shell: pwsh |
27 | | - run: | |
28 | | - New-Item -ItemType Directory -Force -Path dist-pr | Out-Null |
29 | | - $zipName = "scan_$env:GITHUB_RUN_ID`_$env:GITHUB_RUN_ATTEMPT.zip" |
30 | | - $zip = Join-Path (Resolve-Path 'dist-pr') $zipName |
31 | | - $srcRoot = if (Test-Path dist) { 'dist' } elseif (Test-Path build) { 'build' } else { '.' } |
32 | | - $scanRoot = (Resolve-Path $srcRoot).Path |
33 | | - $tempZip = Join-Path $env:RUNNER_TEMP ("scan_" + [guid]::NewGuid().ToString() + ".zip") |
34 | | - function Invoke-WithRetry([scriptblock]$Script, [int]$Attempts = 10, [int]$Delay = 2) { |
35 | | - for ($i=1; $i -le $Attempts; $i++) { |
36 | | - try { & $Script; return } catch { if ($i -eq $Attempts) { throw }; Start-Sleep -Seconds $Delay } |
37 | | - } |
38 | | - } |
39 | | - # Create the zip in temp using bsdtar (avoids Compress-Archive locking issues) |
40 | | - Invoke-WithRetry { |
41 | | - if (Test-Path $tempZip) { Remove-Item -Force $tempZip } |
42 | | - if ($srcRoot -eq '.') { |
43 | | - tar -a -c -f $tempZip --exclude=.git --exclude=.github * |
44 | | - } else { |
45 | | - tar -a -c -f $tempZip -C $srcRoot . |
46 | | - } |
47 | | - } |
48 | | - # Move into working dir with retries in case Defender holds the handle |
49 | | - Invoke-WithRetry { Move-Item -Force $tempZip $zip } |
50 | | - "zip=$zip" | Out-File -FilePath $env:GITHUB_OUTPUT -Append |
51 | | - "scan_root=$scanRoot" | Out-File -FilePath $env:GITHUB_OUTPUT -Append |
52 | | - Write-Host "Created payload: $zip | scan_root: $scanRoot" |
53 | | - - name: Windows Defender scan (PR; scan directory to avoid archive skip) |
54 | | - shell: pwsh |
55 | | - run: | |
56 | | - $mp = (Get-Command MpCmdRun.exe -ErrorAction SilentlyContinue).Source |
57 | | - if (-not $mp) { $mp = "$env:ProgramFiles\Windows Defender\MpCmdRun.exe" } |
58 | | - if (-not (Test-Path $mp)) { throw 'MpCmdRun.exe not found' } |
59 | | - $scanRoot = "${{ steps.prep.outputs.scan_root }}" |
60 | | - if (-not $scanRoot) { throw 'scan_root output missing from prep step' } |
61 | | - & $mp -Scan -ScanType 3 -File $scanRoot |
62 | | - - name: Collect Defender detections (PR) |
63 | | - shell: pwsh |
64 | | - run: | |
65 | | - $since = (Get-Date).AddMinutes(-30) |
66 | | - $scanRoot = "${{ steps.prep.outputs.scan_root }}" |
67 | | - $re = [Regex]::Escape($scanRoot) |
68 | | - $recent = Get-MpThreatDetection | Where-Object { $_.InitialDetectionTime -ge $since -and $_.Resources -and ($_.Resources.Resource -match $re) } |
69 | | - if ($recent) { |
70 | | - $recent | ConvertTo-Json -Depth 5 | Out-File dist-pr\defender-pr-detections.json -Encoding UTF8 |
71 | | - Write-Host 'Detections found.' |
72 | | - } else { '{"status":"clean"}' | Out-File dist-pr\defender-pr-detections.json -Encoding UTF8 } |
73 | | - - name: Upload PR scan payload & results |
| 15 | + - name: Checkout (release tag) |
| 16 | + if: github.event_name == 'release' |
| 17 | + uses: actions/checkout@v4 |
| 18 | + with: |
| 19 | + ref: ${{ github.event.release.tag_name }} |
| 20 | + - name: Checkout (PR/default) |
| 21 | + if: github.event_name != 'release' |
| 22 | + uses: actions/checkout@v4 |
| 23 | + - name: Build and package |
| 24 | + uses: ./.github/actions/build-package |
| 25 | + - name: Upload build bundle |
74 | 26 | uses: actions/upload-artifact@v4 |
75 | 27 | with: |
76 | | - name: defender-pr-results |
77 | | - path: | |
78 | | - dist-pr/*.zip |
79 | | - dist-pr/defender-pr-detections.json |
| 28 | + name: opencode-bundle |
| 29 | + path: bundle/opencode.zip |
80 | 30 |
|
81 | | - defender-scan: |
82 | | - if: github.event_name == 'release' || github.event_name == 'workflow_dispatch' || github.event_name == 'schedule' |
83 | | - name: Windows Defender scan (release assets) |
| 31 | + defender: |
| 32 | + needs: build |
84 | 33 | runs-on: windows-latest |
85 | | - permissions: |
86 | | - contents: read |
87 | | - actions: read |
88 | 34 | steps: |
89 | | - - name: Prepare download dir |
90 | | - shell: pwsh |
91 | | - run: New-Item -ItemType Directory -Force -Path dist-release | Out-Null |
92 | | - - name: Resolve release tag |
93 | | - id: resolve_tag |
94 | | - shell: pwsh |
95 | | - env: |
96 | | - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
97 | | - run: | |
98 | | - $tag = "${{ github.event.release.tag_name }}" |
99 | | - if (-not $tag) { $tag = "${{ github.event.inputs.tag }}" } |
100 | | - if (-not $tag) { $tag = (gh release list --limit 1 --json tagName -q ".[0].tagName") } |
101 | | - if (-not $tag) { throw 'No release tag found' } |
102 | | - "tag=$tag" | Out-File -FilePath $env:GITHUB_OUTPUT -Append |
103 | | - - name: Download assets for this release |
| 35 | + - name: Download build bundle |
| 36 | + uses: actions/download-artifact@v4 |
| 37 | + with: |
| 38 | + name: opencode-bundle |
| 39 | + path: bundle |
| 40 | + - name: Prepare scan dir |
104 | 41 | shell: pwsh |
105 | | - env: |
106 | | - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
107 | 42 | run: | |
108 | | - gh release download "${{ steps.resolve_tag.outputs.tag }}" --dir dist-release --clobber |
109 | | - - name: List assets |
110 | | - shell: pwsh |
111 | | - run: Get-ChildItem -File dist-release | Select-Object FullName, Length | Format-Table | Out-String | Tee-Object -FilePath dist-release\asset-list.txt |
112 | | - - name: Scan each asset with Defender |
| 43 | + New-Item -ItemType Directory -Force -Path scan | Out-Null |
| 44 | + Expand-Archive -Path bundle/opencode.zip -DestinationPath scan -Force |
| 45 | + - name: Windows Defender scan (directory) |
113 | 46 | shell: pwsh |
114 | 47 | run: | |
115 | 48 | $mp = (Get-Command MpCmdRun.exe -ErrorAction SilentlyContinue).Source |
116 | 49 | if (-not $mp) { $mp = "$env:ProgramFiles\Windows Defender\MpCmdRun.exe" } |
117 | 50 | if (-not (Test-Path $mp)) { throw 'MpCmdRun.exe not found' } |
118 | | - Get-ChildItem -File dist-release | ForEach-Object { |
119 | | - Write-Host "Scanning $($_.FullName)" |
120 | | - & $mp -Scan -ScanType 3 -File $_.FullName |
121 | | - Start-Sleep -Seconds 3 |
122 | | - } |
| 51 | + & $mp -Scan -ScanType 3 -File (Resolve-Path 'scan') |
| 52 | + - name: Collect Defender detections |
| 53 | + shell: pwsh |
| 54 | + run: | |
123 | 55 | $since = (Get-Date).AddMinutes(-30) |
124 | | - $recent = Get-MpThreatDetection | Where-Object { |
125 | | - $_.InitialDetectionTime -ge $since -and |
126 | | - $_.Resources -and ($_.Resources.Resource -match [Regex]::Escape((Resolve-Path "dist-release").Path)) |
127 | | - } |
| 56 | + $re = [Regex]::Escape((Resolve-Path 'scan').Path) |
| 57 | + $recent = Get-MpThreatDetection | Where-Object { $_.InitialDetectionTime -ge $since -and $_.Resources -and ($_.Resources.Resource -match $re) } |
128 | 58 | if ($recent) { |
129 | | - $recent | ConvertTo-Json -Depth 5 | Out-File dist-release\defender-detections.json -Encoding UTF8 |
130 | | - Write-Error "Windows Defender found detections. See artifact." |
131 | | - } else { |
132 | | - '{\"status\":\"clean\"}' | Out-File dist-release\defender-detections.json -Encoding UTF8 |
133 | | - } |
| 59 | + $recent | ConvertTo-Json -Depth 5 | Out-File defender-detections.json -Encoding UTF8 |
| 60 | + Write-Error 'Windows Defender found detections. See artifact.' |
| 61 | + } else { '{"status":"clean"}' | Out-File defender-detections.json -Encoding UTF8 } |
134 | 62 | - name: Upload scan results |
135 | 63 | uses: actions/upload-artifact@v4 |
136 | 64 | with: |
137 | 65 | name: defender-scan-results |
138 | | - path: | |
139 | | - dist-release/asset-list.txt |
140 | | - dist-release/defender-detections.json |
| 66 | + path: defender-detections.json |
0 commit comments