trustcheck 2.0.2__tar.gz → 2.0.3__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.3/.github/workflows/binary-security.yml +218 -0
  2. {trustcheck-2.0.2 → trustcheck-2.0.3}/.github/workflows/publish.yml +121 -0
  3. {trustcheck-2.0.2 → trustcheck-2.0.3}/CHANGELOG.md +5 -0
  4. {trustcheck-2.0.2 → trustcheck-2.0.3}/PKG-INFO +9 -1
  5. {trustcheck-2.0.2 → trustcheck-2.0.3}/README.md +8 -0
  6. {trustcheck-2.0.2 → trustcheck-2.0.3}/docs/getting-started/installation.md +18 -0
  7. {trustcheck-2.0.2 → trustcheck-2.0.3}/docs/guides/development.md +1 -0
  8. {trustcheck-2.0.2 → trustcheck-2.0.3}/docs/guides/release-publishing.md +7 -4
  9. trustcheck-2.0.3/scripts/build_standalone.py +41 -0
  10. trustcheck-2.0.3/scripts/trustcheck_binary.py +4 -0
  11. {trustcheck-2.0.2 → trustcheck-2.0.3}/snap/README.md +5 -0
  12. {trustcheck-2.0.2 → trustcheck-2.0.3}/snap/snapcraft.yaml +4 -0
  13. {trustcheck-2.0.2 → trustcheck-2.0.3}/src/trustcheck/_version.py +3 -3
  14. {trustcheck-2.0.2 → trustcheck-2.0.3}/src/trustcheck.egg-info/PKG-INFO +9 -1
  15. {trustcheck-2.0.2 → trustcheck-2.0.3}/src/trustcheck.egg-info/SOURCES.txt +5 -0
  16. trustcheck-2.0.3/tests/test_binary_security_workflow.py +83 -0
  17. trustcheck-2.0.3/tests/test_release_executable.py +74 -0
  18. {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/test_snap_packaging.py +7 -0
  19. {trustcheck-2.0.2 → trustcheck-2.0.3}/.github/CODEOWNERS +0 -0
  20. {trustcheck-2.0.2 → trustcheck-2.0.3}/.github/ISSUE_TEMPLATE/general.yml +0 -0
  21. {trustcheck-2.0.2 → trustcheck-2.0.3}/.github/dependabot.yml +0 -0
  22. {trustcheck-2.0.2 → trustcheck-2.0.3}/.github/trustcheck-action-fail-policy.json +0 -0
  23. {trustcheck-2.0.2 → trustcheck-2.0.3}/.github/workflows/action-integration.yml +0 -0
  24. {trustcheck-2.0.2 → trustcheck-2.0.3}/.github/workflows/bandit.yml +0 -0
  25. {trustcheck-2.0.2 → trustcheck-2.0.3}/.github/workflows/ci.yml +0 -0
  26. {trustcheck-2.0.2 → trustcheck-2.0.3}/.github/workflows/codeql.yml +0 -0
  27. {trustcheck-2.0.2 → trustcheck-2.0.3}/.github/workflows/docs.yml +0 -0
  28. {trustcheck-2.0.2 → trustcheck-2.0.3}/.github/workflows/semgrep.yml +0 -0
  29. {trustcheck-2.0.2 → trustcheck-2.0.3}/.github/workflows/source-build.yml +0 -0
  30. {trustcheck-2.0.2 → trustcheck-2.0.3}/.gitignore +0 -0
  31. {trustcheck-2.0.2 → trustcheck-2.0.3}/CONTRIBUTING.md +0 -0
  32. {trustcheck-2.0.2 → trustcheck-2.0.3}/LICENSE +0 -0
  33. {trustcheck-2.0.2 → trustcheck-2.0.3}/SECURITY.md +0 -0
  34. {trustcheck-2.0.2 → trustcheck-2.0.3}/action.yml +0 -0
  35. {trustcheck-2.0.2 → trustcheck-2.0.3}/docs/assets/images/coverage.svg +0 -0
  36. {trustcheck-2.0.2 → trustcheck-2.0.3}/docs/assets/images/logo-bg-less.png +0 -0
  37. {trustcheck-2.0.2 → trustcheck-2.0.3}/docs/assets/images/logo.png +0 -0
  38. {trustcheck-2.0.2 → trustcheck-2.0.3}/docs/assets/javascripts/disable-search-shortcut.js +0 -0
  39. {trustcheck-2.0.2 → trustcheck-2.0.3}/docs/changelog.md +0 -0
  40. {trustcheck-2.0.2 → trustcheck-2.0.3}/docs/cli/configuration.md +0 -0
  41. {trustcheck-2.0.2 → trustcheck-2.0.3}/docs/cli/exit-codes.md +0 -0
  42. {trustcheck-2.0.2 → trustcheck-2.0.3}/docs/cli/index.md +0 -0
  43. {trustcheck-2.0.2 → trustcheck-2.0.3}/docs/cli/policies.md +0 -0
  44. {trustcheck-2.0.2 → trustcheck-2.0.3}/docs/getting-started/quickstart.md +0 -0
  45. {trustcheck-2.0.2 → trustcheck-2.0.3}/docs/guides/ci-integration.md +0 -0
  46. {trustcheck-2.0.2 → trustcheck-2.0.3}/docs/index.md +0 -0
  47. {trustcheck-2.0.2 → trustcheck-2.0.3}/docs/reference/compatibility.md +0 -0
  48. {trustcheck-2.0.2 → trustcheck-2.0.3}/docs/reference/json-contract.md +0 -0
  49. {trustcheck-2.0.2 → trustcheck-2.0.3}/docs/reference/python-api.md +0 -0
  50. {trustcheck-2.0.2 → trustcheck-2.0.3}/docs/reference/recommendations.md +0 -0
  51. {trustcheck-2.0.2 → trustcheck-2.0.3}/docs/reference/trust-model.md +0 -0
  52. {trustcheck-2.0.2 → trustcheck-2.0.3}/mkdocs.yml +0 -0
  53. {trustcheck-2.0.2 → trustcheck-2.0.3}/pyproject.toml +0 -0
  54. {trustcheck-2.0.2 → trustcheck-2.0.3}/scripts/update_coverage_badge.py +0 -0
  55. {trustcheck-2.0.2 → trustcheck-2.0.3}/setup.cfg +0 -0
  56. {trustcheck-2.0.2 → trustcheck-2.0.3}/snap/gui/icon.png +0 -0
  57. {trustcheck-2.0.2 → trustcheck-2.0.3}/src/trustcheck/__init__.py +0 -0
  58. {trustcheck-2.0.2 → trustcheck-2.0.3}/src/trustcheck/__main__.py +0 -0
  59. {trustcheck-2.0.2 → trustcheck-2.0.3}/src/trustcheck/advisories.py +0 -0
  60. {trustcheck-2.0.2 → trustcheck-2.0.3}/src/trustcheck/artifacts.py +0 -0
  61. {trustcheck-2.0.2 → trustcheck-2.0.3}/src/trustcheck/cli.py +0 -0
  62. {trustcheck-2.0.2 → trustcheck-2.0.3}/src/trustcheck/contract.py +0 -0
  63. {trustcheck-2.0.2 → trustcheck-2.0.3}/src/trustcheck/github_action.py +0 -0
  64. {trustcheck-2.0.2 → trustcheck-2.0.3}/src/trustcheck/lockfiles.py +0 -0
  65. {trustcheck-2.0.2 → trustcheck-2.0.3}/src/trustcheck/models.py +0 -0
  66. {trustcheck-2.0.2 → trustcheck-2.0.3}/src/trustcheck/policy.py +0 -0
  67. {trustcheck-2.0.2 → trustcheck-2.0.3}/src/trustcheck/py.typed +0 -0
  68. {trustcheck-2.0.2 → trustcheck-2.0.3}/src/trustcheck/pypi.py +0 -0
  69. {trustcheck-2.0.2 → trustcheck-2.0.3}/src/trustcheck/schemas.py +0 -0
  70. {trustcheck-2.0.2 → trustcheck-2.0.3}/src/trustcheck/service.py +0 -0
  71. {trustcheck-2.0.2 → trustcheck-2.0.3}/src/trustcheck.egg-info/dependency_links.txt +0 -0
  72. {trustcheck-2.0.2 → trustcheck-2.0.3}/src/trustcheck.egg-info/entry_points.txt +0 -0
  73. {trustcheck-2.0.2 → trustcheck-2.0.3}/src/trustcheck.egg-info/requires.txt +0 -0
  74. {trustcheck-2.0.2 → trustcheck-2.0.3}/src/trustcheck.egg-info/top_level.txt +0 -0
  75. {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/_tmp/bad-scan.toml +0 -0
  76. {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/_tmp/cache/5e491d79f8ba9e36d864ae50c690989677616cd509e5b99abb9272c8ad976435.json +0 -0
  77. {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/_tmp/config_non_object.json +0 -0
  78. {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/_tmp/empty-scan.toml +0 -0
  79. {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/_tmp/empty-scan.txt +0 -0
  80. {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/_tmp/invalid-scan.txt +0 -0
  81. {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/_tmp/policy_non_object.json +0 -0
  82. {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/_tmp/scan-poetry.toml +0 -0
  83. {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/_tmp/scan-project.toml +0 -0
  84. {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/fixtures/client_config.json +0 -0
  85. {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/fixtures/policy_require_expected_repo.json +0 -0
  86. {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/snapshots/contract_schema.json +0 -0
  87. {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/snapshots/report_minimal.json +0 -0
  88. {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/snapshots/report_verified.json +0 -0
  89. {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/test_advisories.py +0 -0
  90. {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/test_artifacts.py +0 -0
  91. {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/test_cli.py +0 -0
  92. {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/test_contract.py +0 -0
  93. {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/test_edge_cases.py +0 -0
  94. {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/test_github_action.py +0 -0
  95. {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/test_integration_live.py +0 -0
  96. {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/test_lockfiles.py +0 -0
  97. {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/test_public_api.py +0 -0
  98. {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/test_pypi.py +0 -0
  99. {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/test_release_readiness.py +0 -0
  100. {trustcheck-2.0.2 → trustcheck-2.0.3}/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
@@ -310,6 +310,25 @@ jobs:
310
310
  export PATH="/snap/bin:$PATH"
311
311
  test "$(command -v trustcheck)" = "/snap/bin/trustcheck"
312
312
  trustcheck --help > /dev/null
313
+ trustcheck inspect sampleproject \
314
+ --version 4.0.0 \
315
+ --format json \
316
+ > snap-verification-report.json
317
+ python - <<'PY'
318
+ import json
319
+ from pathlib import Path
320
+
321
+ payload = json.loads(
322
+ Path("snap-verification-report.json").read_text(encoding="utf-8")
323
+ )
324
+ report = payload["report"]
325
+ assert report["coverage"]["verified_files"] > 0, report["coverage"]
326
+ failures = report["diagnostics"]["artifact_failures"]
327
+ assert not any(
328
+ item["subcode"] == "unexpected_verification_error"
329
+ for item in failures
330
+ ), failures
331
+ PY
313
332
 
314
333
  - name: Generate snap checksum
315
334
  env:
@@ -330,6 +349,84 @@ jobs:
330
349
  ${{ steps.snapcraft.outputs.snap }}.sha256
331
350
  if-no-files-found: error
332
351
 
352
+ build-windows-executable:
353
+ needs:
354
+ - coverage-build
355
+ - snap-qa
356
+ runs-on: windows-latest
357
+
358
+ steps:
359
+ - name: Check out tagged commit
360
+ uses: actions/checkout@v6
361
+ with:
362
+ fetch-depth: 0
363
+ ref: ${{ github.sha }}
364
+ persist-credentials: false
365
+
366
+ - name: Set up Python
367
+ uses: actions/setup-python@v6
368
+ with:
369
+ python-version: "3.12"
370
+
371
+ - name: Install PyInstaller tooling
372
+ run: |
373
+ python -m pip install --upgrade pip
374
+ python -m pip install -e . "pyinstaller>=6.20,<7"
375
+
376
+ - name: Build standalone Windows executable
377
+ run: python scripts/build_standalone.py
378
+
379
+ - name: Smoke test and name release executable
380
+ id: executable
381
+ shell: pwsh
382
+ env:
383
+ TAG_NAME: ${{ github.ref_name }}
384
+ run: |
385
+ $binary = (Resolve-Path "dist\standalone\trustcheck.exe").Path
386
+ $releaseVersion = $env:TAG_NAME.TrimStart("v")
387
+ $versionOutput = & $binary --version
388
+ if ($LASTEXITCODE -ne 0) {
389
+ throw "The standalone executable failed its version smoke test."
390
+ }
391
+ if (-not $versionOutput.StartsWith("trustcheck $releaseVersion ")) {
392
+ throw "Executable version '$versionOutput' does not match $releaseVersion."
393
+ }
394
+ & $binary --help | Out-Null
395
+ if ($LASTEXITCODE -ne 0) {
396
+ throw "The standalone executable failed its help smoke test."
397
+ }
398
+
399
+ New-Item -ItemType Directory -Force "release\windows" | Out-Null
400
+ $releaseName = "trustcheck-$releaseVersion-windows-x86_64.exe"
401
+ $releasePath = Join-Path "release\windows" $releaseName
402
+ Move-Item -LiteralPath $binary -Destination $releasePath
403
+ "path=$releasePath" >> $env:GITHUB_OUTPUT
404
+ "name=$releaseName" >> $env:GITHUB_OUTPUT
405
+
406
+ - name: Generate executable checksum
407
+ shell: pwsh
408
+ env:
409
+ EXECUTABLE_NAME: ${{ steps.executable.outputs.name }}
410
+ EXECUTABLE_PATH: ${{ steps.executable.outputs.path }}
411
+ run: |
412
+ $hash = (Get-FileHash $env:EXECUTABLE_PATH -Algorithm SHA256).Hash.ToLowerInvariant()
413
+ "$hash $env:EXECUTABLE_NAME" |
414
+ Set-Content -Encoding ascii "$env:EXECUTABLE_PATH.sha256"
415
+
416
+ - name: Attest executable build provenance
417
+ uses: actions/attest-build-provenance@v4
418
+ with:
419
+ subject-path: ${{ steps.executable.outputs.path }}
420
+
421
+ - name: Upload Windows executable
422
+ uses: actions/upload-artifact@v7
423
+ with:
424
+ name: windows-executable-${{ github.sha }}
425
+ path: |
426
+ ${{ steps.executable.outputs.path }}
427
+ ${{ steps.executable.outputs.path }}.sha256
428
+ if-no-files-found: error
429
+
333
430
  publish-pypi:
334
431
  needs:
335
432
  - coverage-build
@@ -401,6 +498,7 @@ jobs:
401
498
  - `dist/SHA256SUMS.txt`
402
499
  - `dist/trustcheck-sbom.json`
403
500
  - verified `trustcheck_*.snap` with checksum
501
+ - standalone `trustcheck-*-windows-x86_64.exe` with checksum
404
502
 
405
503
  GitHub Action:
406
504
  - Immutable: `uses: Halfblood-Prince/trustcheck@${{ github.ref_name }}`
@@ -463,6 +561,29 @@ jobs:
463
561
  echo "[Edit ${RELEASE_TAG} and select **Publish this Action to the GitHub Marketplace**](https://github.com/${GITHUB_REPOSITORY}/releases/edit/${RELEASE_TAG})."
464
562
  } >> "$GITHUB_STEP_SUMMARY"
465
563
 
564
+ attach-windows-executable:
565
+ needs:
566
+ - build-windows-executable
567
+ - publish-github-action
568
+ runs-on: ubuntu-latest
569
+
570
+ steps:
571
+ - name: Download Windows executable
572
+ uses: actions/download-artifact@v8
573
+ with:
574
+ name: windows-executable-${{ github.sha }}
575
+ path: windows-executable
576
+
577
+ - name: Attach executable to GitHub Release
578
+ env:
579
+ GH_TOKEN: ${{ secrets.RELEASE_TOKEN || github.token }}
580
+ RELEASE_TAG: ${{ github.ref_name }}
581
+ run: |
582
+ gh release upload "$RELEASE_TAG" \
583
+ windows-executable/* \
584
+ --clobber \
585
+ --repo "$GITHUB_REPOSITORY"
586
+
466
587
  publish-snap:
467
588
  needs:
468
589
  - coverage-build
@@ -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.3
4
4
  Summary: Package trust and provenance verification for PyPI consumers.
5
5
  License: Trustcheck Personal Use License
6
6
 
@@ -113,6 +113,8 @@ 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)
@@ -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:
@@ -10,6 +10,8 @@
10
10
  [![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)
11
11
  [![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)
12
12
  [![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)
13
+ [![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)
14
+ [![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)
13
15
  [![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)
14
16
  [![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)
15
17
  [![Coverage](docs/assets/images/coverage.svg)](https://github.com/Halfblood-Prince/trustcheck/actions/workflows/ci.yml)
@@ -37,6 +39,12 @@ For a selected package version, `trustcheck` can:
37
39
  - optionally inspect wheel and sdist contents without importing or executing package code
38
40
  - emit concise text output or structured JSON for automation
39
41
 
42
+ Every push also builds standalone Windows and Linux executables. The Windows
43
+ artifact is scanned with Microsoft Defender's `MpCmdRun.exe`; the Linux
44
+ artifact is scanned with ClamAV. Clean binaries, checksums, and scanner reports
45
+ are retained as workflow artifacts by
46
+ [Binary Security](https://github.com/Halfblood-Prince/trustcheck/actions/workflows/binary-security.yml).
47
+
40
48
  ## Installation
41
49
 
42
50
  Install from PyPI:
@@ -65,6 +65,24 @@ snap info trustcheck
65
65
  snap connections trustcheck
66
66
  ```
67
67
 
68
+ ### Snap provenance verification reports permission denied
69
+
70
+ Versions before the XDG confinement fix could let Sigstore resolve its TUF
71
+ trust metadata to hidden directories in the real home directory. The Snap
72
+ `home` interface does not grant general access to hidden home paths, so
73
+ verification could report `[unexpected_verification_error] [Errno 13]
74
+ Permission denied`.
75
+
76
+ Refresh to the latest stable revision:
77
+
78
+ ```bash
79
+ sudo snap refresh trustcheck
80
+ trustcheck inspect sampleproject --version 4.0.0
81
+ ```
82
+
83
+ Current revisions keep Sigstore data, cache, and configuration under the
84
+ snap-owned `$SNAP_USER_COMMON` directory.
85
+
68
86
  ## Notes
69
87
 
70
88
  CI runs should stay aligned with the package's advertised Python support. When you need fully reproducible automation, pin both the Python version and the `trustcheck` version.
@@ -38,6 +38,7 @@ The repository includes:
38
38
 
39
39
  - CI for lint, type checks, cross-platform test matrices, coverage enforcement, and build smoke tests
40
40
  - dependency auditing and secret scanning in CI
41
+ - standalone Windows and Linux binary builds on every push, scanned with Microsoft Defender and ClamAV
41
42
  - CodeQL analysis for the Python codebase
42
43
  - release publishing from immutable tagged commits
43
44
  - annotated tag enforcement for releases
@@ -3,12 +3,13 @@
3
3
  An annotated `vMAJOR.MINOR.PATCH` tag starts `.github/workflows/publish.yml`.
4
4
  The workflow verifies the tag, runs the package test matrix, builds the Python
5
5
  distribution, and builds, lints, installs, and smoke-tests the snap. Only
6
- after those checks pass do three independent publication jobs start in
7
- parallel:
6
+ after those checks pass do three independent publication jobs and the Windows
7
+ executable build start in parallel:
8
8
 
9
9
  - PyPI Trusted Publishing
10
10
  - GitHub Release and GitHub Action version tags
11
11
  - Snap Store publication to `stable`
12
+ - PyInstaller build of the standalone Windows executable
12
13
 
13
14
  ## PyPI setup
14
15
 
@@ -25,7 +26,9 @@ only the wheel and sdist produced by the verified build job.
25
26
 
26
27
  The workflow creates the immutable release tag, updates the compatible major
27
28
  tag such as `v1`, and creates a GitHub Release containing the Python
28
- distributions, SBOM, checksums, and snap.
29
+ distributions, SBOM, checksums, snap, and versioned Windows executable. The
30
+ executable is smoke-tested, checksummed, and attested before a follow-up job
31
+ attaches it to the generated GitHub Release.
29
32
 
30
33
  GitHub does not expose Marketplace publication as a workflow or Releases API
31
34
  field. For the first Marketplace publication, and for any release GitHub
@@ -91,4 +94,4 @@ git push origin v1.10.0
91
94
  ```
92
95
 
93
96
  Lightweight tags and prerelease-shaped tags are rejected. If any QA job
94
- fails, none of the three publication jobs starts.
97
+ fails, neither the publication jobs nor the Windows executable build starts.
@@ -0,0 +1,41 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ import PyInstaller.__main__
6
+
7
+ ROOT = Path(__file__).parents[1]
8
+ DIST_DIR = ROOT / "dist" / "standalone"
9
+ WORK_DIR = ROOT / "build" / "pyinstaller"
10
+ SPEC_DIR = WORK_DIR / "spec"
11
+
12
+
13
+ def main() -> int:
14
+ SPEC_DIR.mkdir(parents=True, exist_ok=True)
15
+ arguments = [
16
+ "--clean",
17
+ "--noconfirm",
18
+ "--onefile",
19
+ "--noupx",
20
+ "--name=trustcheck",
21
+ f"--distpath={DIST_DIR}",
22
+ f"--workpath={WORK_DIR}",
23
+ f"--specpath={SPEC_DIR}",
24
+ "--recursive-copy-metadata=trustcheck",
25
+ ]
26
+ for package in (
27
+ "pypi_attestations",
28
+ "rekor_types",
29
+ "sigstore",
30
+ "sigstore_models",
31
+ "tuf",
32
+ ):
33
+ arguments.append(f"--collect-all={package}")
34
+ arguments.append(str(ROOT / "scripts" / "trustcheck_binary.py"))
35
+
36
+ PyInstaller.__main__.run(arguments)
37
+ return 0
38
+
39
+
40
+ if __name__ == "__main__":
41
+ raise SystemExit(main())
@@ -0,0 +1,4 @@
1
+ from trustcheck.cli import main
2
+
3
+ if __name__ == "__main__":
4
+ raise SystemExit(main())
@@ -5,6 +5,11 @@ The root `snap/snapcraft.yaml` builds the `trustcheck` CLI as a strict,
5
5
  interfaces so it can query package registries and scan dependency files
6
6
  without classic confinement.
7
7
 
8
+ Sigstore stores TUF trust metadata through the XDG data and cache paths. The
9
+ snap redirects those paths into `$SNAP_USER_COMMON` so provenance verification
10
+ can update trust material under strict confinement and retain it across snap
11
+ revisions.
12
+
8
13
  Supported Snap platforms are:
9
14
 
10
15
  - `amd64` for 64-bit x86
@@ -62,6 +62,10 @@ platforms:
62
62
  apps:
63
63
  trustcheck:
64
64
  command: bin/trustcheck
65
+ environment:
66
+ XDG_CACHE_HOME: $SNAP_USER_COMMON/cache
67
+ XDG_CONFIG_HOME: $SNAP_USER_COMMON/config
68
+ XDG_DATA_HOME: $SNAP_USER_COMMON/data
65
69
  plugs:
66
70
  - home
67
71
  - network
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '2.0.2'
22
- __version_tuple__ = version_tuple = (2, 0, 2)
21
+ __version__ = version = '2.0.3'
22
+ __version_tuple__ = version_tuple = (2, 0, 3)
23
23
 
24
- __commit_id__ = commit_id = 'g4c245c0ac'
24
+ __commit_id__ = commit_id = 'g8dc614f20'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: trustcheck
3
- Version: 2.0.2
3
+ Version: 2.0.3
4
4
  Summary: Package trust and provenance verification for PyPI consumers.
5
5
  License: Trustcheck Personal Use License
6
6
 
@@ -113,6 +113,8 @@ 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)
@@ -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:
@@ -13,6 +13,7 @@ pyproject.toml
13
13
  .github/ISSUE_TEMPLATE/general.yml
14
14
  .github/workflows/action-integration.yml
15
15
  .github/workflows/bandit.yml
16
+ .github/workflows/binary-security.yml
16
17
  .github/workflows/ci.yml
17
18
  .github/workflows/codeql.yml
18
19
  .github/workflows/docs.yml
@@ -39,6 +40,8 @@ docs/reference/json-contract.md
39
40
  docs/reference/python-api.md
40
41
  docs/reference/recommendations.md
41
42
  docs/reference/trust-model.md
43
+ scripts/build_standalone.py
44
+ scripts/trustcheck_binary.py
42
45
  scripts/update_coverage_badge.py
43
46
  snap/README.md
44
47
  snap/snapcraft.yaml
@@ -66,6 +69,7 @@ src/trustcheck.egg-info/requires.txt
66
69
  src/trustcheck.egg-info/top_level.txt
67
70
  tests/test_advisories.py
68
71
  tests/test_artifacts.py
72
+ tests/test_binary_security_workflow.py
69
73
  tests/test_cli.py
70
74
  tests/test_contract.py
71
75
  tests/test_edge_cases.py
@@ -74,6 +78,7 @@ tests/test_integration_live.py
74
78
  tests/test_lockfiles.py
75
79
  tests/test_public_api.py
76
80
  tests/test_pypi.py
81
+ tests/test_release_executable.py
77
82
  tests/test_release_readiness.py
78
83
  tests/test_service.py
79
84
  tests/test_snap_packaging.py
@@ -0,0 +1,83 @@
1
+ from __future__ import annotations
2
+
3
+ import unittest
4
+ from pathlib import Path
5
+
6
+ ROOT = Path(__file__).parents[1]
7
+
8
+
9
+ class BinarySecurityWorkflowTests(unittest.TestCase):
10
+ def test_workflow_builds_and_scans_both_platform_binaries_on_push(self) -> None:
11
+ workflow = (
12
+ ROOT / ".github" / "workflows" / "binary-security.yml"
13
+ ).read_text(encoding="utf-8")
14
+
15
+ self.assertRegex(workflow, r"(?m)^ push:$")
16
+ self.assertIn("name: Windows Defender", workflow)
17
+ self.assertIn("runs-on: windows-latest", workflow)
18
+ self.assertIn("name: ClamAV", workflow)
19
+ self.assertIn("runs-on: ubuntu-latest", workflow)
20
+ self.assertEqual(workflow.count("python scripts/build_standalone.py"), 2)
21
+ self.assertIn("dist\\standalone\\trustcheck.exe", workflow)
22
+ self.assertIn("dist/standalone/trustcheck", workflow)
23
+
24
+ def test_windows_job_uses_defender_cli_for_exact_executable(self) -> None:
25
+ workflow = (
26
+ ROOT / ".github" / "workflows" / "binary-security.yml"
27
+ ).read_text(encoding="utf-8")
28
+
29
+ self.assertIn("MpCmdRun.exe", workflow)
30
+ self.assertIn("Start-Service -Name wuauserv", workflow)
31
+ self.assertIn("Get-MpComputerStatus", workflow)
32
+ self.assertIn("$status.AntivirusEnabled", workflow)
33
+ self.assertIn("-SignatureUpdate", workflow)
34
+ self.assertIn("-ScanType 3", workflow)
35
+ self.assertIn("-File $binary", workflow)
36
+ self.assertIn("-DisableRemediation", workflow)
37
+ self.assertIn("defender-scan.txt", workflow)
38
+ self.assertIn("Upload clean Windows executable", workflow)
39
+
40
+ def test_linux_job_updates_signatures_and_scans_exact_executable(self) -> None:
41
+ workflow = (
42
+ ROOT / ".github" / "workflows" / "binary-security.yml"
43
+ ).read_text(encoding="utf-8")
44
+
45
+ self.assertIn("sudo apt-get install --yes clamav", workflow)
46
+ self.assertIn("sudo freshclam --verbose", workflow)
47
+ self.assertIn("clamscan \\", workflow)
48
+ self.assertIn("dist/standalone/trustcheck", workflow)
49
+ self.assertIn("--official-db-only=yes", workflow)
50
+ self.assertIn("clamav-scan.txt", workflow)
51
+ self.assertIn("Upload clean Linux executable", workflow)
52
+
53
+ def test_standalone_builder_includes_verification_resources(self) -> None:
54
+ builder = (ROOT / "scripts" / "build_standalone.py").read_text(
55
+ encoding="utf-8"
56
+ )
57
+ entrypoint = (ROOT / "scripts" / "trustcheck_binary.py").read_text(
58
+ encoding="utf-8"
59
+ )
60
+
61
+ self.assertIn('"--onefile"', builder)
62
+ self.assertIn('"--noupx"', builder)
63
+ self.assertIn('"--recursive-copy-metadata=trustcheck"', builder)
64
+ for package in (
65
+ "pypi_attestations",
66
+ "rekor_types",
67
+ "sigstore",
68
+ "sigstore_models",
69
+ "tuf",
70
+ ):
71
+ self.assertIn(f'"{package}"', builder)
72
+ self.assertIn("from trustcheck.cli import main", entrypoint)
73
+
74
+ def test_readme_has_independent_check_run_badges(self) -> None:
75
+ readme = (ROOT / "README.md").read_text(encoding="utf-8")
76
+
77
+ self.assertIn("nameFilter=Windows%20Defender", readme)
78
+ self.assertIn("nameFilter=ClamAV", readme)
79
+ self.assertEqual(readme.count("actions/workflows/binary-security.yml"), 3)
80
+
81
+
82
+ if __name__ == "__main__":
83
+ unittest.main()
@@ -0,0 +1,74 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ import unittest
5
+ from pathlib import Path
6
+
7
+ ROOT = Path(__file__).parents[1]
8
+
9
+
10
+ def _job_block(workflow: str, job_name: str) -> str:
11
+ marker = f" {job_name}:\n"
12
+ start = workflow.index(marker)
13
+ tail_start = start + len(marker)
14
+ next_job = re.search(r"(?m)^ [A-Za-z0-9_-]+:\s*$", workflow[tail_start:])
15
+ if next_job is None:
16
+ return workflow[start:]
17
+ return workflow[start : tail_start + next_job.start()]
18
+
19
+
20
+ class ReleaseExecutableWorkflowTests(unittest.TestCase):
21
+ def test_windows_executable_build_runs_beside_publish_jobs(self) -> None:
22
+ workflow = (
23
+ ROOT / ".github" / "workflows" / "publish.yml"
24
+ ).read_text(encoding="utf-8")
25
+ build = _job_block(workflow, "build-windows-executable")
26
+
27
+ self.assertIn("- coverage-build", build)
28
+ self.assertIn("- snap-qa", build)
29
+ self.assertNotIn("- publish-pypi", build)
30
+ self.assertNotIn("- publish-github-action", build)
31
+ self.assertNotIn("- publish-snap", build)
32
+ self.assertIn("runs-on: windows-latest", build)
33
+
34
+ def test_release_build_creates_and_verifies_versioned_executable(self) -> None:
35
+ workflow = (
36
+ ROOT / ".github" / "workflows" / "publish.yml"
37
+ ).read_text(encoding="utf-8")
38
+ build = _job_block(workflow, "build-windows-executable")
39
+
40
+ self.assertIn('"pyinstaller>=6.20,<7"', build)
41
+ self.assertIn("python scripts/build_standalone.py", build)
42
+ self.assertIn("dist\\standalone\\trustcheck.exe", build)
43
+ self.assertIn("& $binary --version", build)
44
+ self.assertIn("& $binary --help", build)
45
+ self.assertIn("trustcheck-$releaseVersion-windows-x86_64.exe", build)
46
+ self.assertIn("Get-FileHash", build)
47
+ self.assertIn("actions/attest-build-provenance@v4", build)
48
+ self.assertIn("windows-executable-${{ github.sha }}", build)
49
+
50
+ def test_verified_executable_is_attached_after_release_creation(self) -> None:
51
+ workflow = (
52
+ ROOT / ".github" / "workflows" / "publish.yml"
53
+ ).read_text(encoding="utf-8")
54
+ attach = _job_block(workflow, "attach-windows-executable")
55
+
56
+ self.assertIn("- build-windows-executable", attach)
57
+ self.assertIn("- publish-github-action", attach)
58
+ self.assertIn("actions/download-artifact@v8", attach)
59
+ self.assertIn('gh release upload "$RELEASE_TAG"', attach)
60
+ self.assertIn("windows-executable/*", attach)
61
+ self.assertIn("--clobber", attach)
62
+
63
+ def test_release_guide_documents_executable_publication(self) -> None:
64
+ guide = (
65
+ ROOT / "docs" / "guides" / "release-publishing.md"
66
+ ).read_text(encoding="utf-8")
67
+
68
+ self.assertIn("Windows\nexecutable build start in parallel", guide)
69
+ self.assertIn("PyInstaller build", guide)
70
+ self.assertIn("versioned Windows executable", guide)
71
+
72
+
73
+ if __name__ == "__main__":
74
+ unittest.main()
@@ -36,6 +36,9 @@ class SnapPackagingTests(unittest.TestCase):
36
36
  self.assertIn("confinement: strict", snapcraft)
37
37
  self.assertIn("license: Proprietary", snapcraft)
38
38
  self.assertIn("command: bin/trustcheck", snapcraft)
39
+ self.assertIn("XDG_CACHE_HOME: $SNAP_USER_COMMON/cache", snapcraft)
40
+ self.assertIn("XDG_CONFIG_HOME: $SNAP_USER_COMMON/config", snapcraft)
41
+ self.assertIn("XDG_DATA_HOME: $SNAP_USER_COMMON/data", snapcraft)
39
42
  self.assertIn("plugin: python", snapcraft)
40
43
  self.assertIn("source: .", snapcraft)
41
44
  self.assertIn("- home", snapcraft)
@@ -108,6 +111,8 @@ class SnapPackagingTests(unittest.TestCase):
108
111
  "snap run trustcheck --version",
109
112
  'export PATH="/snap/bin:$PATH"',
110
113
  "trustcheck --help",
114
+ "trustcheck inspect sampleproject",
115
+ "unexpected_verification_error",
111
116
  "actions/attest-build-provenance@v4",
112
117
  "Upload verified snap",
113
118
  )
@@ -118,6 +123,8 @@ class SnapPackagingTests(unittest.TestCase):
118
123
  self.assertIn("snapcraft status trustcheck", qa)
119
124
  self.assertIn("secrets.SNAPCRAFT_STORE_CREDENTIALS", qa)
120
125
  self.assertIn('test "$(command -v trustcheck)" = "/snap/bin/trustcheck"', qa)
126
+ self.assertIn("--version 4.0.0", qa)
127
+ self.assertIn('report["coverage"]["verified_files"] > 0', qa)
121
128
  self.assertIn("snap-${{ github.sha }}", qa)
122
129
  self.assertIn("${{ steps.snapcraft.outputs.snap }}.sha256", qa)
123
130
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes