trustcheck 2.0.2__tar.gz → 2.0.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. trustcheck-2.0.4/.github/workflows/binary-security.yml +218 -0
  2. {trustcheck-2.0.2 → trustcheck-2.0.4}/.github/workflows/publish.yml +292 -24
  3. {trustcheck-2.0.2 → trustcheck-2.0.4}/CHANGELOG.md +5 -0
  4. {trustcheck-2.0.2 → trustcheck-2.0.4}/PKG-INFO +11 -3
  5. {trustcheck-2.0.2 → trustcheck-2.0.4}/README.md +10 -2
  6. {trustcheck-2.0.2 → trustcheck-2.0.4}/docs/getting-started/installation.md +18 -0
  7. {trustcheck-2.0.2 → trustcheck-2.0.4}/docs/index.md +0 -1
  8. {trustcheck-2.0.2 → trustcheck-2.0.4}/mkdocs.yml +0 -2
  9. trustcheck-2.0.4/scripts/build_standalone.py +41 -0
  10. trustcheck-2.0.4/scripts/trustcheck_binary.py +4 -0
  11. {trustcheck-2.0.2 → trustcheck-2.0.4}/snap/README.md +5 -3
  12. {trustcheck-2.0.2 → trustcheck-2.0.4}/snap/snapcraft.yaml +4 -0
  13. {trustcheck-2.0.2 → trustcheck-2.0.4}/src/trustcheck/_version.py +3 -3
  14. {trustcheck-2.0.2 → trustcheck-2.0.4}/src/trustcheck.egg-info/PKG-INFO +11 -3
  15. {trustcheck-2.0.2 → trustcheck-2.0.4}/src/trustcheck.egg-info/SOURCES.txt +5 -2
  16. trustcheck-2.0.4/tests/test_binary_security_workflow.py +83 -0
  17. trustcheck-2.0.4/tests/test_release_executable.py +85 -0
  18. {trustcheck-2.0.2 → trustcheck-2.0.4}/tests/test_snap_packaging.py +29 -25
  19. trustcheck-2.0.2/docs/guides/development.md +0 -69
  20. trustcheck-2.0.2/docs/guides/release-publishing.md +0 -94
  21. {trustcheck-2.0.2 → trustcheck-2.0.4}/.github/CODEOWNERS +0 -0
  22. {trustcheck-2.0.2 → trustcheck-2.0.4}/.github/ISSUE_TEMPLATE/general.yml +0 -0
  23. {trustcheck-2.0.2 → trustcheck-2.0.4}/.github/dependabot.yml +0 -0
  24. {trustcheck-2.0.2 → trustcheck-2.0.4}/.github/trustcheck-action-fail-policy.json +0 -0
  25. {trustcheck-2.0.2 → trustcheck-2.0.4}/.github/workflows/action-integration.yml +0 -0
  26. {trustcheck-2.0.2 → trustcheck-2.0.4}/.github/workflows/bandit.yml +0 -0
  27. {trustcheck-2.0.2 → trustcheck-2.0.4}/.github/workflows/ci.yml +0 -0
  28. {trustcheck-2.0.2 → trustcheck-2.0.4}/.github/workflows/codeql.yml +0 -0
  29. {trustcheck-2.0.2 → trustcheck-2.0.4}/.github/workflows/docs.yml +0 -0
  30. {trustcheck-2.0.2 → trustcheck-2.0.4}/.github/workflows/semgrep.yml +0 -0
  31. {trustcheck-2.0.2 → trustcheck-2.0.4}/.github/workflows/source-build.yml +0 -0
  32. {trustcheck-2.0.2 → trustcheck-2.0.4}/.gitignore +0 -0
  33. {trustcheck-2.0.2 → trustcheck-2.0.4}/CONTRIBUTING.md +0 -0
  34. {trustcheck-2.0.2 → trustcheck-2.0.4}/LICENSE +0 -0
  35. {trustcheck-2.0.2 → trustcheck-2.0.4}/SECURITY.md +0 -0
  36. {trustcheck-2.0.2 → trustcheck-2.0.4}/action.yml +0 -0
  37. {trustcheck-2.0.2 → trustcheck-2.0.4}/docs/assets/images/coverage.svg +0 -0
  38. {trustcheck-2.0.2 → trustcheck-2.0.4}/docs/assets/images/logo-bg-less.png +0 -0
  39. {trustcheck-2.0.2 → trustcheck-2.0.4}/docs/assets/images/logo.png +0 -0
  40. {trustcheck-2.0.2 → trustcheck-2.0.4}/docs/assets/javascripts/disable-search-shortcut.js +0 -0
  41. {trustcheck-2.0.2 → trustcheck-2.0.4}/docs/changelog.md +0 -0
  42. {trustcheck-2.0.2 → trustcheck-2.0.4}/docs/cli/configuration.md +0 -0
  43. {trustcheck-2.0.2 → trustcheck-2.0.4}/docs/cli/exit-codes.md +0 -0
  44. {trustcheck-2.0.2 → trustcheck-2.0.4}/docs/cli/index.md +0 -0
  45. {trustcheck-2.0.2 → trustcheck-2.0.4}/docs/cli/policies.md +0 -0
  46. {trustcheck-2.0.2 → trustcheck-2.0.4}/docs/getting-started/quickstart.md +0 -0
  47. {trustcheck-2.0.2 → trustcheck-2.0.4}/docs/guides/ci-integration.md +0 -0
  48. {trustcheck-2.0.2 → trustcheck-2.0.4}/docs/reference/compatibility.md +0 -0
  49. {trustcheck-2.0.2 → trustcheck-2.0.4}/docs/reference/json-contract.md +0 -0
  50. {trustcheck-2.0.2 → trustcheck-2.0.4}/docs/reference/python-api.md +0 -0
  51. {trustcheck-2.0.2 → trustcheck-2.0.4}/docs/reference/recommendations.md +0 -0
  52. {trustcheck-2.0.2 → trustcheck-2.0.4}/docs/reference/trust-model.md +0 -0
  53. {trustcheck-2.0.2 → trustcheck-2.0.4}/pyproject.toml +0 -0
  54. {trustcheck-2.0.2 → trustcheck-2.0.4}/scripts/update_coverage_badge.py +0 -0
  55. {trustcheck-2.0.2 → trustcheck-2.0.4}/setup.cfg +0 -0
  56. {trustcheck-2.0.2 → trustcheck-2.0.4}/snap/gui/icon.png +0 -0
  57. {trustcheck-2.0.2 → trustcheck-2.0.4}/src/trustcheck/__init__.py +0 -0
  58. {trustcheck-2.0.2 → trustcheck-2.0.4}/src/trustcheck/__main__.py +0 -0
  59. {trustcheck-2.0.2 → trustcheck-2.0.4}/src/trustcheck/advisories.py +0 -0
  60. {trustcheck-2.0.2 → trustcheck-2.0.4}/src/trustcheck/artifacts.py +0 -0
  61. {trustcheck-2.0.2 → trustcheck-2.0.4}/src/trustcheck/cli.py +0 -0
  62. {trustcheck-2.0.2 → trustcheck-2.0.4}/src/trustcheck/contract.py +0 -0
  63. {trustcheck-2.0.2 → trustcheck-2.0.4}/src/trustcheck/github_action.py +0 -0
  64. {trustcheck-2.0.2 → trustcheck-2.0.4}/src/trustcheck/lockfiles.py +0 -0
  65. {trustcheck-2.0.2 → trustcheck-2.0.4}/src/trustcheck/models.py +0 -0
  66. {trustcheck-2.0.2 → trustcheck-2.0.4}/src/trustcheck/policy.py +0 -0
  67. {trustcheck-2.0.2 → trustcheck-2.0.4}/src/trustcheck/py.typed +0 -0
  68. {trustcheck-2.0.2 → trustcheck-2.0.4}/src/trustcheck/pypi.py +0 -0
  69. {trustcheck-2.0.2 → trustcheck-2.0.4}/src/trustcheck/schemas.py +0 -0
  70. {trustcheck-2.0.2 → trustcheck-2.0.4}/src/trustcheck/service.py +0 -0
  71. {trustcheck-2.0.2 → trustcheck-2.0.4}/src/trustcheck.egg-info/dependency_links.txt +0 -0
  72. {trustcheck-2.0.2 → trustcheck-2.0.4}/src/trustcheck.egg-info/entry_points.txt +0 -0
  73. {trustcheck-2.0.2 → trustcheck-2.0.4}/src/trustcheck.egg-info/requires.txt +0 -0
  74. {trustcheck-2.0.2 → trustcheck-2.0.4}/src/trustcheck.egg-info/top_level.txt +0 -0
  75. {trustcheck-2.0.2 → trustcheck-2.0.4}/tests/_tmp/bad-scan.toml +0 -0
  76. {trustcheck-2.0.2 → trustcheck-2.0.4}/tests/_tmp/cache/5e491d79f8ba9e36d864ae50c690989677616cd509e5b99abb9272c8ad976435.json +0 -0
  77. {trustcheck-2.0.2 → trustcheck-2.0.4}/tests/_tmp/config_non_object.json +0 -0
  78. {trustcheck-2.0.2 → trustcheck-2.0.4}/tests/_tmp/empty-scan.toml +0 -0
  79. {trustcheck-2.0.2 → trustcheck-2.0.4}/tests/_tmp/empty-scan.txt +0 -0
  80. {trustcheck-2.0.2 → trustcheck-2.0.4}/tests/_tmp/invalid-scan.txt +0 -0
  81. {trustcheck-2.0.2 → trustcheck-2.0.4}/tests/_tmp/policy_non_object.json +0 -0
  82. {trustcheck-2.0.2 → trustcheck-2.0.4}/tests/_tmp/scan-poetry.toml +0 -0
  83. {trustcheck-2.0.2 → trustcheck-2.0.4}/tests/_tmp/scan-project.toml +0 -0
  84. {trustcheck-2.0.2 → trustcheck-2.0.4}/tests/fixtures/client_config.json +0 -0
  85. {trustcheck-2.0.2 → trustcheck-2.0.4}/tests/fixtures/policy_require_expected_repo.json +0 -0
  86. {trustcheck-2.0.2 → trustcheck-2.0.4}/tests/snapshots/contract_schema.json +0 -0
  87. {trustcheck-2.0.2 → trustcheck-2.0.4}/tests/snapshots/report_minimal.json +0 -0
  88. {trustcheck-2.0.2 → trustcheck-2.0.4}/tests/snapshots/report_verified.json +0 -0
  89. {trustcheck-2.0.2 → trustcheck-2.0.4}/tests/test_advisories.py +0 -0
  90. {trustcheck-2.0.2 → trustcheck-2.0.4}/tests/test_artifacts.py +0 -0
  91. {trustcheck-2.0.2 → trustcheck-2.0.4}/tests/test_cli.py +0 -0
  92. {trustcheck-2.0.2 → trustcheck-2.0.4}/tests/test_contract.py +0 -0
  93. {trustcheck-2.0.2 → trustcheck-2.0.4}/tests/test_edge_cases.py +0 -0
  94. {trustcheck-2.0.2 → trustcheck-2.0.4}/tests/test_github_action.py +0 -0
  95. {trustcheck-2.0.2 → trustcheck-2.0.4}/tests/test_integration_live.py +0 -0
  96. {trustcheck-2.0.2 → trustcheck-2.0.4}/tests/test_lockfiles.py +0 -0
  97. {trustcheck-2.0.2 → trustcheck-2.0.4}/tests/test_public_api.py +0 -0
  98. {trustcheck-2.0.2 → trustcheck-2.0.4}/tests/test_pypi.py +0 -0
  99. {trustcheck-2.0.2 → trustcheck-2.0.4}/tests/test_release_readiness.py +0 -0
  100. {trustcheck-2.0.2 → trustcheck-2.0.4}/tests/test_service.py +0 -0
@@ -0,0 +1,218 @@
1
+ name: Binary Security
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+ workflow_dispatch:
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ windows-defender:
13
+ name: Windows Defender
14
+ runs-on: windows-latest
15
+
16
+ steps:
17
+ - name: Check out repository
18
+ uses: actions/checkout@v6
19
+ with:
20
+ fetch-depth: 0
21
+ persist-credentials: false
22
+
23
+ - name: Set up Python
24
+ uses: actions/setup-python@v6
25
+ with:
26
+ python-version: "3.12"
27
+
28
+ - name: Install package and binary tooling
29
+ run: |
30
+ python -m pip install --upgrade pip
31
+ python -m pip install -e . "pyinstaller>=6.20,<7"
32
+
33
+ - name: Build Windows executable
34
+ run: python scripts/build_standalone.py
35
+
36
+ - name: Smoke test Windows executable
37
+ shell: pwsh
38
+ run: |
39
+ $binary = (Resolve-Path "dist\standalone\trustcheck.exe").Path
40
+ & $binary --version
41
+ if ($LASTEXITCODE -ne 0) {
42
+ exit $LASTEXITCODE
43
+ }
44
+ & $binary --help | Out-Null
45
+ if ($LASTEXITCODE -ne 0) {
46
+ exit $LASTEXITCODE
47
+ }
48
+
49
+ - name: Locate Microsoft Defender CLI
50
+ id: defender
51
+ shell: pwsh
52
+ run: |
53
+ $platformRoot = Join-Path $env:ProgramData "Microsoft\Windows Defender\Platform"
54
+ $candidates = @()
55
+ if (Test-Path $platformRoot) {
56
+ $candidates += Get-ChildItem -Path $platformRoot -Directory |
57
+ Sort-Object Name -Descending |
58
+ ForEach-Object { Join-Path $_.FullName "MpCmdRun.exe" }
59
+ }
60
+ $candidates += Join-Path $env:ProgramFiles "Windows Defender\MpCmdRun.exe"
61
+ $defender = $candidates |
62
+ Where-Object { Test-Path $_ } |
63
+ Select-Object -First 1
64
+ if (-not $defender) {
65
+ throw "MpCmdRun.exe was not found on the Windows runner."
66
+ }
67
+ "path=$defender" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
68
+ Write-Host "Using Microsoft Defender CLI: $defender"
69
+
70
+ - name: Verify Microsoft Defender is enabled
71
+ shell: pwsh
72
+ run: |
73
+ Set-Service -Name wuauserv -StartupType Manual
74
+ Start-Service -Name wuauserv
75
+ $status = Get-MpComputerStatus
76
+ $status |
77
+ Select-Object AMServiceEnabled, AntivirusEnabled, AntivirusSignatureVersion |
78
+ Format-List |
79
+ Out-File defender-status.txt
80
+ Get-Content defender-status.txt
81
+ if (-not $status.AMServiceEnabled -or -not $status.AntivirusEnabled) {
82
+ throw "Microsoft Defender Antivirus is not enabled on the runner."
83
+ }
84
+
85
+ - name: Update Microsoft Defender signatures
86
+ shell: pwsh
87
+ env:
88
+ DEFENDER_CLI: ${{ steps.defender.outputs.path }}
89
+ run: |
90
+ "Microsoft Defender signature update" |
91
+ Set-Content -Path defender-signature-update.txt
92
+ $output = & $env:DEFENDER_CLI -SignatureUpdate 2>&1
93
+ $exitCode = $LASTEXITCODE
94
+ $output | Tee-Object -FilePath defender-signature-update.txt -Append
95
+ if ($exitCode -ne 0) {
96
+ Write-Error "Microsoft Defender signature update failed with exit code $exitCode."
97
+ exit $exitCode
98
+ }
99
+
100
+ - name: Scan executable with Microsoft Defender
101
+ shell: pwsh
102
+ env:
103
+ DEFENDER_CLI: ${{ steps.defender.outputs.path }}
104
+ run: |
105
+ $binary = (Resolve-Path "dist\standalone\trustcheck.exe").Path
106
+ "Microsoft Defender custom scan: $binary" |
107
+ Set-Content -Path defender-scan.txt
108
+ $output = & $env:DEFENDER_CLI `
109
+ -Scan `
110
+ -ScanType 3 `
111
+ -File $binary `
112
+ -DisableRemediation 2>&1
113
+ $exitCode = $LASTEXITCODE
114
+ $output | Tee-Object -FilePath defender-scan.txt -Append
115
+ if ($exitCode -ne 0) {
116
+ Write-Error "Microsoft Defender scan failed with exit code $exitCode."
117
+ exit $exitCode
118
+ }
119
+
120
+ - name: Generate Windows executable checksum
121
+ shell: pwsh
122
+ run: |
123
+ Get-FileHash "dist\standalone\trustcheck.exe" -Algorithm SHA256 |
124
+ Format-List |
125
+ Out-File "dist\standalone\trustcheck.exe.sha256.txt"
126
+
127
+ - name: Upload Microsoft Defender reports
128
+ if: always()
129
+ uses: actions/upload-artifact@v7
130
+ with:
131
+ name: windows-defender-reports-${{ github.sha }}
132
+ path: defender-*.txt
133
+ if-no-files-found: warn
134
+
135
+ - name: Upload clean Windows executable
136
+ uses: actions/upload-artifact@v7
137
+ with:
138
+ name: trustcheck-windows-${{ github.sha }}
139
+ path: |
140
+ dist/standalone/trustcheck.exe
141
+ dist/standalone/trustcheck.exe.sha256.txt
142
+ if-no-files-found: error
143
+
144
+ linux-clamav:
145
+ name: ClamAV
146
+ runs-on: ubuntu-latest
147
+
148
+ steps:
149
+ - name: Check out repository
150
+ uses: actions/checkout@v6
151
+ with:
152
+ fetch-depth: 0
153
+ persist-credentials: false
154
+
155
+ - name: Set up Python
156
+ uses: actions/setup-python@v6
157
+ with:
158
+ python-version: "3.12"
159
+
160
+ - name: Install package and binary tooling
161
+ run: |
162
+ python -m pip install --upgrade pip
163
+ python -m pip install -e . "pyinstaller>=6.20,<7"
164
+
165
+ - name: Build Linux executable
166
+ run: python scripts/build_standalone.py
167
+
168
+ - name: Smoke test Linux executable
169
+ run: |
170
+ test -x dist/standalone/trustcheck
171
+ dist/standalone/trustcheck --version
172
+ dist/standalone/trustcheck --help > /dev/null
173
+
174
+ - name: Install ClamAV
175
+ run: |
176
+ sudo apt-get update
177
+ sudo apt-get install --yes clamav
178
+
179
+ - name: Update ClamAV signatures
180
+ run: |
181
+ sudo systemctl stop clamav-freshclam.service 2>/dev/null || true
182
+ sudo freshclam --verbose | tee clamav-signature-update.txt
183
+
184
+ - name: Scan executable with ClamAV
185
+ run: |
186
+ set +e
187
+ clamscan \
188
+ --official-db-only=yes \
189
+ --verbose \
190
+ --log=clamav-scan.txt \
191
+ dist/standalone/trustcheck
192
+ scan_status=$?
193
+ set -e
194
+ cat clamav-scan.txt
195
+ if [ "$scan_status" -ne 0 ]; then
196
+ echo "ClamAV scan failed with exit code ${scan_status}." >&2
197
+ exit "$scan_status"
198
+ fi
199
+
200
+ - name: Generate Linux executable checksum
201
+ run: sha256sum dist/standalone/trustcheck > dist/standalone/trustcheck.sha256
202
+
203
+ - name: Upload ClamAV reports
204
+ if: always()
205
+ uses: actions/upload-artifact@v7
206
+ with:
207
+ name: linux-clamav-reports-${{ github.sha }}
208
+ path: clamav-*.txt
209
+ if-no-files-found: warn
210
+
211
+ - name: Upload clean Linux executable
212
+ uses: actions/upload-artifact@v7
213
+ with:
214
+ name: trustcheck-linux-${{ github.sha }}
215
+ path: |
216
+ dist/standalone/trustcheck
217
+ dist/standalone/trustcheck.sha256
218
+ if-no-files-found: error
@@ -86,8 +86,8 @@ jobs:
86
86
  - name: Run type checks
87
87
  run: mypy src
88
88
 
89
- test-matrix:
90
- needs: verify-tag
89
+ matrix-build:
90
+ needs: qa
91
91
  strategy:
92
92
  fail-fast: false
93
93
  matrix:
@@ -159,9 +159,7 @@ jobs:
159
159
  python -m build --sdist --wheel --outdir "$archive_root/dist" "$archive_root"
160
160
 
161
161
  coverage-build:
162
- needs:
163
- - qa
164
- - test-matrix
162
+ needs: matrix-build
165
163
  runs-on: ubuntu-latest
166
164
 
167
165
  steps:
@@ -235,10 +233,54 @@ jobs:
235
233
  name: dist-${{ github.sha }}
236
234
  path: dist/*
237
235
 
236
+ clam-av:
237
+ needs: coverage-build
238
+ runs-on: ubuntu-latest
239
+
240
+ steps:
241
+ - name: Download build artifacts
242
+ uses: actions/download-artifact@v8
243
+ with:
244
+ name: dist-${{ github.sha }}
245
+ path: dist
246
+
247
+ - name: Install ClamAV
248
+ run: |
249
+ sudo apt-get update
250
+ sudo apt-get install --yes clamav
251
+
252
+ - name: Update ClamAV signatures
253
+ run: |
254
+ sudo systemctl stop clamav-freshclam.service 2>/dev/null || true
255
+ sudo freshclam --verbose | tee clamav-signature-update.txt
256
+
257
+ - name: Scan release build with ClamAV
258
+ run: |
259
+ set +e
260
+ clamscan \
261
+ --recursive \
262
+ --official-db-only=yes \
263
+ --verbose \
264
+ --log=clamav-scan.txt \
265
+ dist
266
+ scan_status=$?
267
+ set -e
268
+ cat clamav-scan.txt
269
+ if [ "$scan_status" -ne 0 ]; then
270
+ echo "ClamAV scan failed with exit code ${scan_status}." >&2
271
+ exit "$scan_status"
272
+ fi
273
+
274
+ - name: Upload ClamAV reports
275
+ if: always()
276
+ uses: actions/upload-artifact@v7
277
+ with:
278
+ name: release-clamav-reports-${{ github.sha }}
279
+ path: clamav-*.txt
280
+ if-no-files-found: warn
281
+
238
282
  snap-qa:
239
- needs:
240
- - qa
241
- - test-matrix
283
+ needs: clam-av
242
284
  runs-on: ubuntu-latest
243
285
 
244
286
  steps:
@@ -310,6 +352,25 @@ jobs:
310
352
  export PATH="/snap/bin:$PATH"
311
353
  test "$(command -v trustcheck)" = "/snap/bin/trustcheck"
312
354
  trustcheck --help > /dev/null
355
+ trustcheck inspect sampleproject \
356
+ --version 4.0.0 \
357
+ --format json \
358
+ > snap-verification-report.json
359
+ python - <<'PY'
360
+ import json
361
+ from pathlib import Path
362
+
363
+ payload = json.loads(
364
+ Path("snap-verification-report.json").read_text(encoding="utf-8")
365
+ )
366
+ report = payload["report"]
367
+ assert report["coverage"]["verified_files"] > 0, report["coverage"]
368
+ failures = report["diagnostics"]["artifact_failures"]
369
+ assert not any(
370
+ item["subcode"] == "unexpected_verification_error"
371
+ for item in failures
372
+ ), failures
373
+ PY
313
374
 
314
375
  - name: Generate snap checksum
315
376
  env:
@@ -330,10 +391,204 @@ jobs:
330
391
  ${{ steps.snapcraft.outputs.snap }}.sha256
331
392
  if-no-files-found: error
332
393
 
394
+ build-windows-executable:
395
+ needs: coverage-build
396
+ runs-on: windows-latest
397
+
398
+ steps:
399
+ - name: Check out tagged commit
400
+ uses: actions/checkout@v6
401
+ with:
402
+ fetch-depth: 0
403
+ ref: ${{ github.sha }}
404
+ persist-credentials: false
405
+
406
+ - name: Set up Python
407
+ uses: actions/setup-python@v6
408
+ with:
409
+ python-version: "3.12"
410
+
411
+ - name: Install PyInstaller tooling
412
+ run: |
413
+ python -m pip install --upgrade pip
414
+ python -m pip install -e . "pyinstaller>=6.20,<7"
415
+
416
+ - name: Build standalone Windows executable
417
+ run: python scripts/build_standalone.py
418
+
419
+ - name: Smoke test and name release executable
420
+ id: executable
421
+ shell: pwsh
422
+ env:
423
+ TAG_NAME: ${{ github.ref_name }}
424
+ run: |
425
+ $binary = (Resolve-Path "dist\standalone\trustcheck.exe").Path
426
+ $releaseVersion = $env:TAG_NAME.TrimStart("v")
427
+ $versionOutput = & $binary --version
428
+ if ($LASTEXITCODE -ne 0) {
429
+ throw "The standalone executable failed its version smoke test."
430
+ }
431
+ if (-not $versionOutput.StartsWith("trustcheck $releaseVersion ")) {
432
+ throw "Executable version '$versionOutput' does not match $releaseVersion."
433
+ }
434
+ & $binary --help | Out-Null
435
+ if ($LASTEXITCODE -ne 0) {
436
+ throw "The standalone executable failed its help smoke test."
437
+ }
438
+
439
+ New-Item -ItemType Directory -Force "release\windows" | Out-Null
440
+ $releaseName = "trustcheck-$releaseVersion-windows-x86_64.exe"
441
+ $releasePath = Join-Path "release\windows" $releaseName
442
+ Move-Item -LiteralPath $binary -Destination $releasePath
443
+ "path=$releasePath" >> $env:GITHUB_OUTPUT
444
+ "name=$releaseName" >> $env:GITHUB_OUTPUT
445
+
446
+ - name: Generate executable checksum
447
+ shell: pwsh
448
+ env:
449
+ EXECUTABLE_NAME: ${{ steps.executable.outputs.name }}
450
+ EXECUTABLE_PATH: ${{ steps.executable.outputs.path }}
451
+ run: |
452
+ $hash = (Get-FileHash $env:EXECUTABLE_PATH -Algorithm SHA256).Hash.ToLowerInvariant()
453
+ "$hash $env:EXECUTABLE_NAME" |
454
+ Set-Content -Encoding ascii "$env:EXECUTABLE_PATH.sha256"
455
+
456
+ - name: Attest executable build provenance
457
+ uses: actions/attest-build-provenance@v4
458
+ with:
459
+ subject-path: ${{ steps.executable.outputs.path }}
460
+
461
+ - name: Upload executable for Microsoft Defender scan
462
+ uses: actions/upload-artifact@v7
463
+ with:
464
+ name: windows-executable-unscanned-${{ github.sha }}
465
+ path: |
466
+ ${{ steps.executable.outputs.path }}
467
+ ${{ steps.executable.outputs.path }}.sha256
468
+ if-no-files-found: error
469
+
470
+ windows-defender-scan:
471
+ needs: build-windows-executable
472
+ runs-on: windows-latest
473
+
474
+ steps:
475
+ - name: Download built Windows executable
476
+ uses: actions/download-artifact@v8
477
+ with:
478
+ name: windows-executable-unscanned-${{ github.sha }}
479
+ path: windows-executable
480
+
481
+ - name: Locate built Windows executable
482
+ id: executable
483
+ shell: pwsh
484
+ run: |
485
+ $binary = Get-ChildItem -Path "windows-executable" -Filter "*.exe" -File |
486
+ Select-Object -First 1
487
+ if (-not $binary) {
488
+ throw "The Windows executable from the build job was not found."
489
+ }
490
+ "path=$($binary.FullName)" >> $env:GITHUB_OUTPUT
491
+ Write-Host "Scanning executable produced by the build job: $($binary.FullName)"
492
+
493
+ - name: Locate Microsoft Defender CLI
494
+ id: defender
495
+ shell: pwsh
496
+ run: |
497
+ $platformRoot = Join-Path $env:ProgramData "Microsoft\Windows Defender\Platform"
498
+ $candidates = @()
499
+ if (Test-Path $platformRoot) {
500
+ $candidates += Get-ChildItem -Path $platformRoot -Directory |
501
+ Sort-Object Name -Descending |
502
+ ForEach-Object { Join-Path $_.FullName "MpCmdRun.exe" }
503
+ }
504
+ $candidates += Join-Path $env:ProgramFiles "Windows Defender\MpCmdRun.exe"
505
+ $defender = $candidates |
506
+ Where-Object { Test-Path $_ } |
507
+ Select-Object -First 1
508
+ if (-not $defender) {
509
+ throw "MpCmdRun.exe was not found on the Windows runner."
510
+ }
511
+ "path=$defender" >> $env:GITHUB_OUTPUT
512
+ Write-Host "Using Microsoft Defender CLI: $defender"
513
+
514
+ - name: Verify Microsoft Defender is enabled
515
+ shell: pwsh
516
+ run: |
517
+ Set-Service -Name wuauserv -StartupType Manual
518
+ Start-Service -Name wuauserv
519
+ $status = Get-MpComputerStatus
520
+ $status |
521
+ Select-Object AMServiceEnabled, AntivirusEnabled, AntivirusSignatureVersion |
522
+ Format-List |
523
+ Out-File defender-status.txt
524
+ Get-Content defender-status.txt
525
+ if (-not $status.AMServiceEnabled -or -not $status.AntivirusEnabled) {
526
+ throw "Microsoft Defender Antivirus is not enabled on the runner."
527
+ }
528
+
529
+ - name: Update Microsoft Defender signatures
530
+ shell: pwsh
531
+ env:
532
+ DEFENDER_CLI: ${{ steps.defender.outputs.path }}
533
+ run: |
534
+ "Microsoft Defender signature update" |
535
+ Set-Content -Path defender-signature-update.txt
536
+ $output = & $env:DEFENDER_CLI -SignatureUpdate 2>&1
537
+ $exitCode = $LASTEXITCODE
538
+ $output | Tee-Object -FilePath defender-signature-update.txt -Append
539
+ if ($exitCode -ne 0) {
540
+ Write-Error "Microsoft Defender signature update failed with exit code $exitCode."
541
+ exit $exitCode
542
+ }
543
+
544
+ - name: Scan built executable with Microsoft Defender
545
+ shell: pwsh
546
+ env:
547
+ DEFENDER_CLI: ${{ steps.defender.outputs.path }}
548
+ EXECUTABLE_PATH: ${{ steps.executable.outputs.path }}
549
+ run: |
550
+ "Microsoft Defender custom scan: $env:EXECUTABLE_PATH" |
551
+ Set-Content -Path defender-scan.txt
552
+ $output = & $env:DEFENDER_CLI `
553
+ -Scan `
554
+ -ScanType 3 `
555
+ -File $env:EXECUTABLE_PATH `
556
+ -DisableRemediation 2>&1
557
+ $exitCode = $LASTEXITCODE
558
+ $output | Tee-Object -FilePath defender-scan.txt -Append
559
+ if ($exitCode -ne 0) {
560
+ Write-Error "Microsoft Defender scan failed with exit code $exitCode."
561
+ exit $exitCode
562
+ }
563
+
564
+ - name: Regenerate executable checksum after scan
565
+ shell: pwsh
566
+ env:
567
+ EXECUTABLE_PATH: ${{ steps.executable.outputs.path }}
568
+ run: |
569
+ $name = Split-Path -Leaf $env:EXECUTABLE_PATH
570
+ $hash = (Get-FileHash $env:EXECUTABLE_PATH -Algorithm SHA256).Hash.ToLowerInvariant()
571
+ "$hash $name" | Set-Content -Encoding ascii "$env:EXECUTABLE_PATH.sha256"
572
+
573
+ - name: Upload Microsoft Defender reports
574
+ if: always()
575
+ uses: actions/upload-artifact@v7
576
+ with:
577
+ name: windows-defender-reports-${{ github.sha }}
578
+ path: defender-*.txt
579
+ if-no-files-found: warn
580
+
581
+ - name: Upload scanned Windows executable
582
+ uses: actions/upload-artifact@v7
583
+ with:
584
+ name: windows-executable-${{ github.sha }}
585
+ path: |
586
+ windows-executable/*.exe
587
+ windows-executable/*.exe.sha256
588
+ if-no-files-found: error
589
+
333
590
  publish-pypi:
334
- needs:
335
- - coverage-build
336
- - snap-qa
591
+ needs: coverage-build
337
592
  runs-on: ubuntu-latest
338
593
 
339
594
  steps:
@@ -358,7 +613,6 @@ jobs:
358
613
  publish-github-action:
359
614
  needs:
360
615
  - coverage-build
361
- - snap-qa
362
616
  - verify-tag
363
617
  runs-on: ubuntu-latest
364
618
 
@@ -378,12 +632,6 @@ jobs:
378
632
  name: dist-${{ github.sha }}
379
633
  path: dist
380
634
 
381
- - name: Download verified snap
382
- uses: actions/download-artifact@v8
383
- with:
384
- name: snap-${{ github.sha }}
385
- path: snap-artifact
386
-
387
635
  - name: Create GitHub Release with generated notes
388
636
  uses: softprops/action-gh-release@v3
389
637
  with:
@@ -394,20 +642,19 @@ jobs:
394
642
  body: |
395
643
  Published from immutable commit `${{ github.sha }}`.
396
644
  The release workflow publishes PyPI, GitHub Action, and Snap Store
397
- distributions in parallel after shared package and Snap QA.
645
+ distributions after shared tag verification, QA, matrix, and coverage builds.
398
646
 
399
647
  Release artifacts:
400
648
  - `dist/*`
401
649
  - `dist/SHA256SUMS.txt`
402
650
  - `dist/trustcheck-sbom.json`
403
- - verified `trustcheck_*.snap` with checksum
651
+ - standalone `trustcheck-*-windows-x86_64.exe` with checksum
404
652
 
405
653
  GitHub Action:
406
654
  - Immutable: `uses: Halfblood-Prince/trustcheck@${{ github.ref_name }}`
407
655
  - Compatible major: `uses: Halfblood-Prince/trustcheck@${{ needs['verify-tag'].outputs.action_major_tag }}`
408
656
  files: |
409
657
  dist/*
410
- snap-artifact/*
411
658
 
412
659
  - name: Publish moving major action tag
413
660
  env:
@@ -463,10 +710,31 @@ jobs:
463
710
  echo "[Edit ${RELEASE_TAG} and select **Publish this Action to the GitHub Marketplace**](https://github.com/${GITHUB_REPOSITORY}/releases/edit/${RELEASE_TAG})."
464
711
  } >> "$GITHUB_STEP_SUMMARY"
465
712
 
466
- publish-snap:
713
+ upload-windows-executable:
467
714
  needs:
468
- - coverage-build
469
- - snap-qa
715
+ - windows-defender-scan
716
+ - publish-github-action
717
+ runs-on: ubuntu-latest
718
+
719
+ steps:
720
+ - name: Download Windows executable
721
+ uses: actions/download-artifact@v8
722
+ with:
723
+ name: windows-executable-${{ github.sha }}
724
+ path: windows-executable
725
+
726
+ - name: Attach executable to GitHub Release
727
+ env:
728
+ GH_TOKEN: ${{ secrets.RELEASE_TOKEN || github.token }}
729
+ RELEASE_TAG: ${{ github.ref_name }}
730
+ run: |
731
+ gh release upload "$RELEASE_TAG" \
732
+ windows-executable/* \
733
+ --clobber \
734
+ --repo "$GITHUB_REPOSITORY"
735
+
736
+ publish-snap:
737
+ needs: snap-qa
470
738
  runs-on: ubuntu-latest
471
739
 
472
740
  steps:
@@ -31,6 +31,11 @@ The project follows Semantic Versioning for the supported public API described i
31
31
  - Renamed the reusable GitHub Action and Marketplace display name to `TrustCheck Package Scanner`.
32
32
  - Expanded the Snap Store listing with richer feature copy, quick-start examples, project links, and a dedicated storefront icon.
33
33
  - Updated Snap release smoke tests and installation documentation to verify the public `trustcheck` command and diagnose shells where `/snap/bin` is missing from `PATH`.
34
+ - Redirected Sigstore XDG data, cache, and configuration into writable Snap-owned storage, fixing errno 13 provenance verification failures under strict confinement.
35
+ - Expanded Snap release QA to perform live verified-provenance inspection from the installed snap and reject unexpected verification errors.
36
+ - Added a push-triggered binary security workflow that builds standalone Windows and Linux executables with PyInstaller.
37
+ - Added a parallel release job that builds, smoke-tests, checksums, and attests a versioned Windows executable, then attaches it to the GitHub release.
38
+ - Added Microsoft Defender CLI and ClamAV scanning, retained scan reports, clean binary artifacts, checksums, and independent README check-run badges.
34
39
 
35
40
  ## [1.9.0] - 2026-06-09
36
41
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: trustcheck
3
- Version: 2.0.2
3
+ Version: 2.0.4
4
4
  Summary: Package trust and provenance verification for PyPI consumers.
5
5
  License: Trustcheck Personal Use License
6
6
 
@@ -113,13 +113,15 @@ Dynamic: license-file
113
113
  [![pip-audit](https://img.shields.io/github/actions/workflow/status/Halfblood-Prince/trustcheck/ci.yml?branch=main&label=pip-audit)](https://github.com/Halfblood-Prince/trustcheck/actions/workflows/ci.yml)
114
114
  [![Bandit](https://github.com/Halfblood-Prince/trustcheck/actions/workflows/bandit.yml/badge.svg?branch=main)](https://github.com/Halfblood-Prince/trustcheck/actions/workflows/bandit.yml)
115
115
  [![Semgrep](https://github.com/Halfblood-Prince/trustcheck/actions/workflows/semgrep.yml/badge.svg?branch=main)](https://github.com/Halfblood-Prince/trustcheck/actions/workflows/semgrep.yml)
116
+ [![Windows Defender](https://img.shields.io/github/check-runs/Halfblood-Prince/trustcheck/main?nameFilter=Windows%20Defender&label=Windows%20Defender&logo=windows11)](https://github.com/Halfblood-Prince/trustcheck/actions/workflows/binary-security.yml)
117
+ [![ClamAV](https://img.shields.io/github/check-runs/Halfblood-Prince/trustcheck/main?nameFilter=ClamAV&label=ClamAV&logo=linux)](https://github.com/Halfblood-Prince/trustcheck/actions/workflows/binary-security.yml)
116
118
  [![Ruff](https://img.shields.io/github/actions/workflow/status/Halfblood-Prince/trustcheck/ci.yml?branch=main&label=ruff)](https://github.com/Halfblood-Prince/trustcheck/actions/workflows/ci.yml)
117
119
  [![mypy](https://img.shields.io/github/actions/workflow/status/Halfblood-Prince/trustcheck/ci.yml?branch=main&label=mypy)](https://github.com/Halfblood-Prince/trustcheck/actions/workflows/ci.yml)
118
120
  [![Coverage](docs/assets/images/coverage.svg)](https://github.com/Halfblood-Prince/trustcheck/actions/workflows/ci.yml)
119
121
  [![PyPI](https://img.shields.io/pypi/v/trustcheck.svg)](https://pypi.org/project/trustcheck/)
120
122
  [![Python 3.10 | 3.11 | 3.12 | 3.13 | 3.14](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13%20%7C%203.14-blue.svg)](https://github.com/Halfblood-Prince/trustcheck/actions/workflows/ci.yml)
121
123
  [![PyPI Downloads](https://static.pepy.tech/personalized-badge/trustcheck?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads)](https://pepy.tech/projects/trustcheck)
122
- [![TrustCheck Package Scanner](https://img.shields.io/badge/GitHub%20Action-TrustCheck%20Package%20Scanner-blue?logo=githubactions&logoColor=white)](https://github.com/Halfblood-Prince/trustcheck/blob/main/action.yml)
124
+ [![TrustCheck Package Scanner](https://img.shields.io/badge/GitHub%20Action-TrustCheck%20Package%20Scanner-blue?logo=githubactions&logoColor=white)](https://github.com/marketplace/actions/trustcheck-package-scanner)
123
125
 
124
126
  `trustcheck` is a Python package and CLI for evaluating the trust posture of PyPI releases before they are installed, promoted, or approved.
125
127
 
@@ -140,6 +142,12 @@ For a selected package version, `trustcheck` can:
140
142
  - optionally inspect wheel and sdist contents without importing or executing package code
141
143
  - emit concise text output or structured JSON for automation
142
144
 
145
+ Every push also builds standalone Windows and Linux executables. The Windows
146
+ artifact is scanned with Microsoft Defender's `MpCmdRun.exe`; the Linux
147
+ artifact is scanned with ClamAV. Clean binaries, checksums, and scanner reports
148
+ are retained as workflow artifacts by
149
+ [Binary Security](https://github.com/Halfblood-Prince/trustcheck/actions/workflows/binary-security.yml).
150
+
143
151
  ## Installation
144
152
 
145
153
  Install from PyPI:
@@ -318,7 +326,7 @@ Full documentation: https://halfblood-prince.github.io/trustcheck/
318
326
  - Integrations: [JSON contract](https://halfblood-prince.github.io/trustcheck/reference/json-contract/), [Python API](https://halfblood-prince.github.io/trustcheck/reference/python-api/), and [Compatibility](https://halfblood-prince.github.io/trustcheck/reference/compatibility/)
319
327
  - Trust model: [Verification model and repository matching](https://halfblood-prince.github.io/trustcheck/reference/trust-model/)
320
328
  - Automation: [CI integration](https://halfblood-prince.github.io/trustcheck/guides/ci-integration/)
321
- - Project details: [Development and release process](https://halfblood-prince.github.io/trustcheck/guides/development/), [release publishing](https://halfblood-prince.github.io/trustcheck/guides/release-publishing/), and [Changelog](https://halfblood-prince.github.io/trustcheck/changelog/)
329
+ - Project details: [Changelog](https://halfblood-prince.github.io/trustcheck/changelog/)
322
330
 
323
331
  Project support:
324
332