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.
- trustcheck-2.0.3/.github/workflows/binary-security.yml +218 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/.github/workflows/publish.yml +121 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/CHANGELOG.md +5 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/PKG-INFO +9 -1
- {trustcheck-2.0.2 → trustcheck-2.0.3}/README.md +8 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/docs/getting-started/installation.md +18 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/docs/guides/development.md +1 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/docs/guides/release-publishing.md +7 -4
- trustcheck-2.0.3/scripts/build_standalone.py +41 -0
- trustcheck-2.0.3/scripts/trustcheck_binary.py +4 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/snap/README.md +5 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/snap/snapcraft.yaml +4 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/src/trustcheck/_version.py +3 -3
- {trustcheck-2.0.2 → trustcheck-2.0.3}/src/trustcheck.egg-info/PKG-INFO +9 -1
- {trustcheck-2.0.2 → trustcheck-2.0.3}/src/trustcheck.egg-info/SOURCES.txt +5 -0
- trustcheck-2.0.3/tests/test_binary_security_workflow.py +83 -0
- trustcheck-2.0.3/tests/test_release_executable.py +74 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/test_snap_packaging.py +7 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/.github/CODEOWNERS +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/.github/ISSUE_TEMPLATE/general.yml +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/.github/dependabot.yml +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/.github/trustcheck-action-fail-policy.json +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/.github/workflows/action-integration.yml +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/.github/workflows/bandit.yml +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/.github/workflows/ci.yml +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/.github/workflows/codeql.yml +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/.github/workflows/docs.yml +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/.github/workflows/semgrep.yml +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/.github/workflows/source-build.yml +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/.gitignore +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/CONTRIBUTING.md +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/LICENSE +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/SECURITY.md +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/action.yml +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/docs/assets/images/coverage.svg +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/docs/assets/images/logo-bg-less.png +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/docs/assets/images/logo.png +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/docs/assets/javascripts/disable-search-shortcut.js +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/docs/changelog.md +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/docs/cli/configuration.md +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/docs/cli/exit-codes.md +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/docs/cli/index.md +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/docs/cli/policies.md +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/docs/getting-started/quickstart.md +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/docs/guides/ci-integration.md +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/docs/index.md +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/docs/reference/compatibility.md +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/docs/reference/json-contract.md +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/docs/reference/python-api.md +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/docs/reference/recommendations.md +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/docs/reference/trust-model.md +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/mkdocs.yml +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/pyproject.toml +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/scripts/update_coverage_badge.py +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/setup.cfg +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/snap/gui/icon.png +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/src/trustcheck/__init__.py +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/src/trustcheck/__main__.py +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/src/trustcheck/advisories.py +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/src/trustcheck/artifacts.py +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/src/trustcheck/cli.py +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/src/trustcheck/contract.py +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/src/trustcheck/github_action.py +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/src/trustcheck/lockfiles.py +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/src/trustcheck/models.py +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/src/trustcheck/policy.py +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/src/trustcheck/py.typed +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/src/trustcheck/pypi.py +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/src/trustcheck/schemas.py +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/src/trustcheck/service.py +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/src/trustcheck.egg-info/dependency_links.txt +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/src/trustcheck.egg-info/entry_points.txt +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/src/trustcheck.egg-info/requires.txt +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/src/trustcheck.egg-info/top_level.txt +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/_tmp/bad-scan.toml +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/_tmp/cache/5e491d79f8ba9e36d864ae50c690989677616cd509e5b99abb9272c8ad976435.json +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/_tmp/config_non_object.json +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/_tmp/empty-scan.toml +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/_tmp/empty-scan.txt +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/_tmp/invalid-scan.txt +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/_tmp/policy_non_object.json +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/_tmp/scan-poetry.toml +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/_tmp/scan-project.toml +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/fixtures/client_config.json +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/fixtures/policy_require_expected_repo.json +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/snapshots/contract_schema.json +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/snapshots/report_minimal.json +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/snapshots/report_verified.json +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/test_advisories.py +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/test_artifacts.py +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/test_cli.py +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/test_contract.py +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/test_edge_cases.py +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/test_github_action.py +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/test_integration_live.py +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/test_lockfiles.py +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/test_public_api.py +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/test_pypi.py +0 -0
- {trustcheck-2.0.2 → trustcheck-2.0.3}/tests/test_release_readiness.py +0 -0
- {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.
|
|
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
|
[](https://github.com/Halfblood-Prince/trustcheck/actions/workflows/ci.yml)
|
|
114
114
|
[](https://github.com/Halfblood-Prince/trustcheck/actions/workflows/bandit.yml)
|
|
115
115
|
[](https://github.com/Halfblood-Prince/trustcheck/actions/workflows/semgrep.yml)
|
|
116
|
+
[](https://github.com/Halfblood-Prince/trustcheck/actions/workflows/binary-security.yml)
|
|
117
|
+
[](https://github.com/Halfblood-Prince/trustcheck/actions/workflows/binary-security.yml)
|
|
116
118
|
[](https://github.com/Halfblood-Prince/trustcheck/actions/workflows/ci.yml)
|
|
117
119
|
[](https://github.com/Halfblood-Prince/trustcheck/actions/workflows/ci.yml)
|
|
118
120
|
[](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
|
[](https://github.com/Halfblood-Prince/trustcheck/actions/workflows/ci.yml)
|
|
11
11
|
[](https://github.com/Halfblood-Prince/trustcheck/actions/workflows/bandit.yml)
|
|
12
12
|
[](https://github.com/Halfblood-Prince/trustcheck/actions/workflows/semgrep.yml)
|
|
13
|
+
[](https://github.com/Halfblood-Prince/trustcheck/actions/workflows/binary-security.yml)
|
|
14
|
+
[](https://github.com/Halfblood-Prince/trustcheck/actions/workflows/binary-security.yml)
|
|
13
15
|
[](https://github.com/Halfblood-Prince/trustcheck/actions/workflows/ci.yml)
|
|
14
16
|
[](https://github.com/Halfblood-Prince/trustcheck/actions/workflows/ci.yml)
|
|
15
17
|
[](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
|
|
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
|
|
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,
|
|
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())
|
|
@@ -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
|
|
@@ -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.
|
|
22
|
-
__version_tuple__ = version_tuple = (2, 0,
|
|
21
|
+
__version__ = version = '2.0.3'
|
|
22
|
+
__version_tuple__ = version_tuple = (2, 0, 3)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'g8dc614f20'
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: trustcheck
|
|
3
|
-
Version: 2.0.
|
|
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
|
[](https://github.com/Halfblood-Prince/trustcheck/actions/workflows/ci.yml)
|
|
114
114
|
[](https://github.com/Halfblood-Prince/trustcheck/actions/workflows/bandit.yml)
|
|
115
115
|
[](https://github.com/Halfblood-Prince/trustcheck/actions/workflows/semgrep.yml)
|
|
116
|
+
[](https://github.com/Halfblood-Prince/trustcheck/actions/workflows/binary-security.yml)
|
|
117
|
+
[](https://github.com/Halfblood-Prince/trustcheck/actions/workflows/binary-security.yml)
|
|
116
118
|
[](https://github.com/Halfblood-Prince/trustcheck/actions/workflows/ci.yml)
|
|
117
119
|
[](https://github.com/Halfblood-Prince/trustcheck/actions/workflows/ci.yml)
|
|
118
120
|
[](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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|