Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 15 additions & 10 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ jobs:
with:
python-version: "3.14"
- name: Install the linter
run: pip install -r requirements-lint.txt
run: pip install --require-hashes -r requirements-lint.txt
# The gate. `--select` on the command line replaces the families named in
# pyproject.toml and nothing else: the per-file ignores and the target
# version still come from there, so the two runs below cannot drift apart
Expand Down Expand Up @@ -121,7 +121,7 @@ jobs:
with:
python-version: "3.14"
- name: Install the type checker
run: pip install -r requirements-lint.txt
run: pip install --require-hashes -r requirements-lint.txt
# No project dependencies installed on purpose: `pydivert` is Windows-only
# and `psutil` is not needed to READ this package, and both are ignored by
# name in [tool.mypy]. Verified in that exact shape (Linux, neither
Expand All @@ -145,7 +145,7 @@ jobs:
with:
python-version: "3.14"
- name: Install the scanner
run: pip install -r requirements-lint.txt
run: pip install -r requirements-scan.txt
# No --error and no --severity here: the scan reports, and tools/semgrep_gate.py
# decides. `--severity` knows INFO/WARNING/ERROR only, while registry rules also
# carry HIGH and CRITICAL - measured with a probe rule, `--severity ERROR`
Expand Down Expand Up @@ -181,7 +181,7 @@ jobs:
with:
python-version: "3.14"
- name: Install the auditor
run: pip install -r requirements-lint.txt
run: pip install -r requirements-scan.txt
# The pinned set, installed exactly as the build installs it, into its own
# environment - then audited BY PATH. Auditing the requirement files instead
# silently skips `packaging` and `setuptools` (measured), and a scanner that
Expand Down Expand Up @@ -249,7 +249,6 @@ jobs:
python-version: "3.14"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install --require-hashes -r requirements.txt
pip install -r requirements-dev.txt
# A pull request pays for what it touched; the weekly run pays for
Expand Down Expand Up @@ -320,13 +319,20 @@ jobs:
python-version: ${{ matrix.python-version }}
cache: pip
- name: Install dependencies
# 🔴 No `pip install --upgrade pip`, here or anywhere else in this repository.
# That line fetched an UNPINNED pip from the index and then used it to check
# the hashes on the next line - the one unverified link in a hash-checked
# chain, and the last place anybody would look. The pip that checks them is
# the one `setup-python` shipped with the interpreter, and that action is
# pinned by SHA. Guarded by tests/test_version_and_release.py.
run: |
python -m pip install --upgrade pip
pip install --require-hashes -r requirements.txt
pip install -r requirements-dev.txt
# diff-cover only: the rest of that file is for the analysis jobs, and
# installing it whole here would drag semgrep onto both matrix legs.
pip install "$(grep -E '^diff-cover==' requirements-lint.txt)"
# The whole lint file rather than diff-cover picked out of it by grep. A
# hash cannot travel through `$(grep ...)`: it hands pip a bare pin, and
# pip stays out of hash-checking mode. semgrep and pip-audit moved to
# requirements-scan.txt, so this no longer drags an engine onto both legs.
pip install --require-hashes -r requirements-lint.txt

# ONE run of the whole suite, under coverage. testpaths=["tests"] (pyproject)
# means this already includes the suites that used to own separate steps:
Expand Down Expand Up @@ -504,7 +510,6 @@ jobs:
cache: pip
- name: Install dependencies
run: |
python -m pip install --upgrade pip
# One hash-checked resolution over both files: the runtime pins and
# the freezer's whole closure. Nothing here may resolve to bytes
# that are not written down.
Expand Down
1 change: 0 additions & 1 deletion .github/workflows/pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ jobs:

- name: Install the test dependencies
run: |
python -m pip install --upgrade pip
pip install --require-hashes -r requirements.txt
pip install -r requirements-dev.txt

Expand Down
43 changes: 25 additions & 18 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,38 @@ on:
tags:
- "v*"

# The default token only reads the repo; creating a Release and uploading assets
# needs write. Scoped to this workflow, not the whole repository.
# Read-only at the top, and the job below raises exactly what it needs. Same rule as
# ci.yml, pages.yml and scorecard.yml already follow; this file was the last one
# breaking it, and OpenSSF Scorecard named it (Token-Permissions, 0/10, 2026-08-19).
#
# 🔴 Not cosmetic while there is only one job. A permission written here is granted
# to every job ADDED to this file later - a job whose author has no reason to scroll
# back up, in the one workflow that can publish an asset under this project's name.
permissions:
contents: write
# For the SBOM attestation. `id-token` mints the short-lived OIDC token that
# signs it, `attestations` writes the result to the repository's attestation
# store. Both are required by actions/attest and neither grants anything else;
# a release that skipped them would still publish an SBOM, just an unsigned one
# that anybody could swap.
#
# NOT here on purpose: `artifact-metadata: write`, which that action's README
# lists as a third permission. It is needed to create the artifact STORAGE
# RECORD, and `create-storage-record` requires `push-to-registry`, which
# defaults to false and is not used here (read in the action's own action.yml,
# 2026-08-12). Granting a permission we do not need would contradict the line
# above it.
id-token: write
attestations: write
contents: read

jobs:
release:
name: Build and publish the Windows executable
runs-on: windows-latest
timeout-minutes: 30
permissions:
# Creating a Release and uploading its assets. The default token only reads.
contents: write
# For the SBOM attestation. `id-token` mints the short-lived OIDC token that
# signs it, `attestations` writes the result to the repository's attestation
# store. Both are required by actions/attest and neither grants anything else;
# a release that skipped them would still publish an SBOM, just an unsigned one
# that anybody could swap.
#
# NOT here on purpose: `artifact-metadata: write`, which that action's README
# lists as a third permission. It is needed to create the artifact STORAGE
# RECORD, and `create-storage-record` requires `push-to-registry`, which
# defaults to false and is not used here (read in the action's own action.yml,
# 2026-08-12). Granting a permission we do not need would contradict the line
# above it.
id-token: write
attestations: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

Expand All @@ -45,7 +53,6 @@ jobs:

- name: Install dependencies
run: |
python -m pip install --upgrade pip
# One hash-checked resolution over both files - the same command the
# build job in ci.yml runs, so a release cannot be built from a
# different set of bytes than the one CI proved.
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ The format follows [Keep a Changelog](https://keepachangelog.com/); versions fol
Accents are optional: `opoznienie` finds `Opóźnienie`. A setting that lives in the Settings
window is named rather than missed.

- **You can now check where a download came from, not just that it is unchanged.** Every
release archive carries a signed build attestation, so one command answers "was this really
built from that source, by that workflow":
`gh attestation verify BeanNetworkTester-v0.5.0-windows-x64.zip -R donislawdev/BeanNetworkTester`.
A checksum proves the file matches the release page. This proves the release page itself came
out of this repository's own workflow, from a specific commit. The same command also verifies
the SBOM shipped beside the archive.

### Changed

- **Traffic in Connections is shown in the unit that fits the number.** A big flow used to read
Expand Down
3 changes: 2 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ testable on any OS.
```bash
pip install --require-hashes -r requirements.txt # runtime, pinned to exact artefacts
pip install -r requirements-dev.txt
pip install -r requirements-lint.txt # ruff and mypy (semgrep on Linux and macOS)
pip install --require-hashes -r requirements-lint.txt # ruff, mypy and diff-cover
pip install -r requirements-scan.txt # semgrep (Linux and macOS) and pip-audit
python -m pytest tests # full suite - no Windows, no driver, no admin rights
python smoke_gui.py # GUI smoke with a fake tkinter
ruff check # F and B fail a pull request, S and ASYNC are a report
Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1178,9 +1178,11 @@ Every job in that workflow, and what a red one means:
than run it. **ruff** fails a pull request on a dead-code or bug-shape finding (`F` and `B`) and
reports the security family (`S`, `ASYNC`) as annotations that never block. **mypy** type-checks
the package. **semgrep** scans with its default registry ruleset and a finding at ERROR, HIGH or
CRITICAL fails the run, while everything below that is printed in full. The three tool versions
are pinned in `requirements-lint.txt`, so a new release of a linter cannot redden a pull request
that changed nothing.
CRITICAL fails the run, while everything below that is printed in full. All three tool versions are
pinned, so a new release of a linter cannot redden a pull request that changed nothing: ruff and
mypy in `requirements-lint.txt`, which CI installs by exact file hashes, and semgrep in
`requirements-scan.txt`, pinned to a version only. Semgrep is the one place where a pin cannot
promise a stable answer anyway, because it downloads its rules when it runs.

One step is worth knowing about because no unit test can do its job: a **GUI render check on real
Tk** under a virtual screen, at the minimum supported 1366x768, **in every language**. It builds
Expand Down
6 changes: 4 additions & 2 deletions README.pl.md
Original file line number Diff line number Diff line change
Expand Up @@ -1034,8 +1034,10 @@ uruchamiają. **ruff** wywraca pull requesta na martwym kodzie i na kształtach
a rodzinę bezpieczeństwa (`S`, `ASYNC`) tylko wypisuje w diffie i nigdy nie blokuje. **mypy**
sprawdza typy w pakiecie. **semgrep** skanuje domyślnym zestawem reguł z rejestru, przy czym
znalezisko na poziomie ERROR, HIGH albo CRITICAL wywraca przebieg, a wszystko niżej ląduje w logu.
Wersje tych trzech narzędzi są przypięte w `requirements-lint.txt`, więc nowe wydanie lintera nie
zaczerwieni pull requesta, w którym nic się nie zmieniło.
Wersje wszystkich trzech narzędzi są przypięte, więc nowe wydanie lintera nie zaczerwieni pull
requesta, w którym nic się nie zmieniło: ruff i mypy w `requirements-lint.txt`, który CI instaluje
po dokładnych skrótach plików, a semgrep w `requirements-scan.txt`, przypięty tylko wersją. Akurat
przy semgrepie przypięcie i tak nie obiecuje stałej odpowiedzi, bo reguły pobiera przy starcie.

Jeden krok wart jest osobnego zdania, bo żaden test jednostkowy go nie zastąpi: **render GUI na
prawdziwym Tk** pod wirtualnym ekranem, w minimalnej wspieranej rozdzielczości 1366x768 i **w
Expand Down
Loading
Loading