From 9622088ab716123dc247d73486ec4945859c366f Mon Sep 17 00:00:00 2001 From: DonislawDev Date: Fri, 25 Sep 2026 00:21:29 +0200 Subject: [PATCH 1/2] chore(release): close the changelogs for 0.7.0 Moves the [Unreleased] block into a dated 0.7.0 section and sets VERSION.txt to 0.7.0. While closing it, every commit since v0.6.0 was checked against its entry. One entry was wrong: the Docs line said the README links the website under the badges, and that link was removed in #185. Added now opens with the Tools tab entry, so the tools listed after it are introduced first, and the entry about shortened options reads as one plain statement. The website's download page now matches the release it points at: it mentions the installer, lists the five files a release carries, and its verify command is the README's. The old command failed for every visitor: it had no --predicate-type, so gh asked for build provenance that a hand-signed archive does not have, and gh does not expand its wildcard ("failed to open local artifact"). test_the_documented_verify_command_matches_what_we_actually_attest now reads the site's commands too: repository, SPDX predicate type, no wildcard, and at least one command present. Four mutations, four caught. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 29 ++++++++++++++++++----------- VERSION.txt | 2 +- site/pages/download/en.html | 28 +++++++++++++++++++--------- site/pages/download/page.json | 4 ++-- site/pages/download/pl.html | 28 +++++++++++++++++++--------- tests/test_version_and_release.py | 29 +++++++++++++++++++++++++++++ 6 files changed, 88 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fac762f..60010bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,16 @@ The format follows [Keep a Changelog](https://keepachangelog.com/); versions fol ## [Unreleased] +## [0.7.0] - 2026-09-25 + ### Added +- **A Tools tab, with a filter tester.** Pick a field from the Control page, type an + expression and a value, and see at once whether they match - and which part of the + expression decided it, for example the `!10.0.5.0/24` that excluded `10.0.5.7`. A value + that is not an address, a port or a PID is reported as that, not as "does not match". + "Use in the Control field" puts the expression into that field. + - **Sockets, on the Tools tab.** Every TCP and UDP socket on this computer, like `netstat -ano`, with no session needed: what is listening, what is connected, its state and the program that owns it. See which port your application listens on before you @@ -20,12 +28,6 @@ The format follows [Keep a Changelog](https://keepachangelog.com/); versions fol on a port nothing seems to use is often in a range Windows set aside, and this shows it at once. Nothing is sent over the network. It is the second tab of the Tools page. -- **A Tools tab, with a filter tester.** Pick a field from the Control page, type an - expression and a value, and see at once whether they match - and which part of the - expression decided it, for example the `!10.0.5.0/24` that excluded `10.0.5.7`. A value - that is not an address, a port or a PID is reported as that, not as "does not match". - "Use in the Control field" puts the expression into that field. - - **Diagnostics, on the Tools tab.** When START will not work, this shows why without a console: the same checks as `--doctor`, each marked OK, Warning or Problem, with a "?" that explains them in plain words. "Clean up the driver" unloads a WinDivert driver that @@ -70,7 +72,7 @@ The format follows [Keep a Changelog](https://keepachangelog.com/); versions fol The run now says it once, and still runs, because impairing one direction on purpose is a perfectly good thing to ask for. -- **Some command-line shortcuts stopped working, and the full flags did not.** Adding the +- **Some shortened command-line options no longer work. The full ones still do.** Adding the upload flags means `--latency` is no longer the only option starting with "latency", so short forms like `--lat`, `--jit`, `--j`, `--cor` and `--spike-p` are now ambiguous and are refused. Every full flag still works, so saved reproduction commands and every example in @@ -92,16 +94,21 @@ The format follows [Keep a Changelog](https://keepachangelog.com/); versions fol ### Docs -- **The README now points at the website.** One link under the badges, and a second after the - quick start for anyone who wants a walkthrough of a single task instead of the full manual. - The guides there cover packet loss, latency, speed limits, aiming at one app and testing with - no internet, each with the numbers worth trying and the command that does it. +- **The README now points at the website.** A link after the quick start leads to the guides, + for anyone who wants a walkthrough of a single task instead of the full manual. They cover + packet loss, latency, speed limits, aiming at a single app and testing with no internet, each + with the numbers worth trying and the command that does it. - **The website's front page now shows the command it was talking about.** It said one command is enough to check that a service survives 10 percent packet loss, and then did not print one, which every other page on the site does. It now shows the command, says that the run stops itself when the time is up, and points at the rehearsal switch that changes no real traffic. +- **The website's download page describes the release as it is.** It mentions the installer + next to the zip and lists every file a release carries. The command it gave for checking a + download failed for everyone. It is now the same command as in the README, which is run + against every published release. + ## [0.6.0] - 2026-09-04 ### Added diff --git a/VERSION.txt b/VERSION.txt index a918a2a..faef31a 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -0.6.0 +0.7.0 diff --git a/site/pages/download/en.html b/site/pages/download/en.html index 1ea701b..3ab82a2 100644 --- a/site/pages/download/en.html +++ b/site/pages/download/en.html @@ -1,7 +1,7 @@

Download Bean Network Tester

-

One archive, no installer. Unpack it, run the executable, and it asks for - administrator rights itself.

+

A zip that runs without installing anything, or an installer for everyone on + the computer. Either way, the program asks for administrator rights itself.

{{cta.download}}

@@ -13,7 +13,9 @@

The facts, in one place

  • Price: free, and free software - GNU GPL v3.
  • Runs on: Windows 10 and Windows 11, 64-bit.
  • Needs: administrator rights, because traffic is captured by a driver.
  • -
  • Install: none. Unpack the archive anywhere and run it.
  • +
  • Install: not needed. Unpack the zip anywhere and run it. Or use the + .msi installer, which adds a Start Menu entry and puts the program on + PATH (it needs administrator rights).
  • Sends data: nowhere. No telemetry, no update check, no network client.
  • Modes: a window, and the same file as a command-line tool.
  • Source: on GitHub, under the same licence.
  • @@ -22,24 +24,32 @@

    The facts, in one place

    What is in the release

    -

    Every release publishes three files, and the two beside the archive are there so you do not - have to take the download on trust:

    +

    A release publishes five files. Two are the program, and the other three are there so you + do not have to take the download on trust:

    • the zip - the program and the driver it uses, together in one folder;
    • -
    • SHA256SUMS.txt - the checksum of that archive;
    • +
    • the .msi installer - the same program, installed for everyone on the + computer, with a Start Menu entry;
    • +
    • SHA256SUMS.txt - the checksums of the zip and the installer;
    • an SBOM - a standard list of every component inside, with versions and - licences, signed against the archive.
    • + licences, signed against the zip; +
    • a .sigstore.json file - that signature as a file, to check it without + asking GitHub.

    Checking that you got what we built

    The checksum answers "did this arrive unchanged". In PowerShell, compare the output with the - line in SHA256SUMS.txt:

    + line in SHA256SUMS.txt (for the installer, put .msi in place of + .zip):

    Get-FileHash .\BeanNetworkTester-*.zip -Algorithm SHA256

    The signature answers a different question - "did this come out of that repository" - and needs the GitHub command-line tool:

    -
    gh attestation verify .\BeanNetworkTester-*.zip --repo donislawdev/BeanNetworkTester
    +
    gh attestation verify BeanNetworkTester-vX.Y.Z-windows-x64.zip --repo donislawdev/BeanNetworkTester --predicate-type https://spdx.dev/Document/v2.3
    +

    Type the name of the zip you downloaded as the first part - gh does not accept a + * there. Keep --predicate-type: without it gh looks for a + different kind of statement and reports that it found none.

    A checksum you copy from the same page as the download proves less than a signature. Both are published, so both are worth a moment.

    diff --git a/site/pages/download/page.json b/site/pages/download/page.json index 2ce2a90..ed89e0c 100644 --- a/site/pages/download/page.json +++ b/site/pages/download/page.json @@ -6,13 +6,13 @@ "slug": "download", "link_text": "Download", "title": "Download Bean Network Tester for Windows", - "description": "Free download for Windows 10 and 11, no installer. What is in the archive, how to check it is the file we built, and what it needs to run." + "description": "Free download for Windows 10 and 11, as a zip or an installer. What is in the release, how to check it is the file we built, and what it needs to run." }, "pl": { "slug": "pobieranie", "link_text": "Pobieranie", "title": "Pobierz Bean Network Tester na Windows", - "description": "Darmowe pobranie na Windows 10 i 11, bez instalatora. Co jest w archiwum, jak sprawdzić, że to nasz plik, i czego program potrzebuje." + "description": "Darmowe pobranie na Windows 10 i 11, jako zip albo instalator. Co jest w wydaniu, jak sprawdzić, że to nasz plik, i czego program potrzebuje." } } } diff --git a/site/pages/download/pl.html b/site/pages/download/pl.html index 07ce42e..d5897b2 100644 --- a/site/pages/download/pl.html +++ b/site/pages/download/pl.html @@ -1,7 +1,7 @@

    Pobierz Bean Network Tester

    -

    Jedno archiwum, bez instalatora. Rozpakuj, uruchom plik wykonywalny, a on sam - poprosi o prawa administratora.

    +

    Archiwum zip, które działa bez instalowania, albo instalator dla wszystkich + użytkowników komputera. W obu przypadkach program sam poprosi o prawa administratora.

    {{cta.download}}

    @@ -13,7 +13,9 @@

    Fakty w jednym miejscu

  • Cena: darmowy, i wolne oprogramowanie - GNU GPL v3.
  • Działa na: Windows 10 i Windows 11, 64-bit.
  • Wymaga: praw administratora, bo ruch przechwytuje sterownik.
  • -
  • Instalacja: żadna. Rozpakuj archiwum gdziekolwiek i uruchom.
  • +
  • Instalacja: niepotrzebna. Rozpakuj zip gdziekolwiek i uruchom. Możesz też + użyć instalatora .msi, który dodaje program do menu Start i do PATH + (wymaga praw administratora).
  • Wysyła dane: nigdzie. Bez telemetrii, bez sprawdzania aktualizacji, bez klienta sieciowego.
  • Tryby: okno i ten sam plik jako narzędzie linii komend.
  • @@ -24,25 +26,33 @@

    Fakty w jednym miejscu

    Co jest w wydaniu

    -

    Każde wydanie publikuje trzy pliki, a te dwa obok archiwum są po to, żebyś nie musiał brać - pobrania na wiarę:

    +

    Wydanie publikuje pięć plików. Dwa to program, a pozostałe trzy są po to, żebyś nie musiał + brać pobrania na wiarę:

    • archiwum zip - program i sterownik, którego używa, razem w jednym katalogu;
    • -
    • SHA256SUMS.txt - suma kontrolna tego archiwum;
    • +
    • instalator .msi - ten sam program, instalowany dla wszystkich + użytkowników komputera, z pozycją w menu Start;
    • +
    • SHA256SUMS.txt - sumy kontrolne archiwum i instalatora;
    • SBOM - standardowa lista każdego komponentu w środku, z wersjami i - licencjami, podpisana wobec archiwum.
    • + licencjami, podpisana wobec archiwum; +
    • plik .sigstore.json - ten podpis jako plik, żeby sprawdzić go bez + pytania GitHuba.

    Sprawdzenie, że dostałeś to, co zbudowaliśmy

    Suma kontrolna odpowiada na pytanie „czy plik dotarł niezmieniony". W PowerShellu porównaj - wynik z linią w SHA256SUMS.txt:

    + wynik z linią w SHA256SUMS.txt (dla instalatora wpisz .msi zamiast + .zip):

    Get-FileHash .\BeanNetworkTester-*.zip -Algorithm SHA256

    Podpis odpowiada na inne pytanie - „czy to wyszło z tego repozytorium" - i wymaga narzędzia linii komend GitHuba:

    -
    gh attestation verify .\BeanNetworkTester-*.zip --repo donislawdev/BeanNetworkTester
    +
    gh attestation verify BeanNetworkTester-vX.Y.Z-windows-x64.zip --repo donislawdev/BeanNetworkTester --predicate-type https://spdx.dev/Document/v2.3
    +

    Jako pierwszą część wpisz nazwę pobranego pliku zip - gh nie przyjmuje tam + *. Zostaw --predicate-type: bez niego gh szuka innego + rodzaju poświadczenia i odpowiada, że żadnego nie znalazł.

    Suma kontrolna skopiowana z tej samej strony, z której pobierasz plik, dowodzi mniej niż podpis. Publikujemy oba, więc oba są warte chwili.

    diff --git a/tests/test_version_and_release.py b/tests/test_version_and_release.py index c975f62..f1127c3 100644 --- a/tests/test_version_and_release.py +++ b/tests/test_version_and_release.py @@ -1030,6 +1030,35 @@ def test_the_documented_verify_command_matches_what_we_actually_attest(): check(f"{readme}: the online command asks for the SPDX predicate", "https://spdx.dev/Document/v2.3" in command, f"({command[:160]})") + # 🔴 The website hands out the same command to people who never open the README, + # and verify-release.yml runs only the README's lines. Until 0.7.0 the download + # page said `gh attestation verify .\BeanNetworkTester-*.zip --repo ...`, which + # failed twice over: no predicate type (the 404 above), and a `*` that gh does not + # expand - in PowerShell it answers "failed to open local artifact" (measured + # 2026-09-25). HTML has no Markdown bold, so a `*` here can only be that pattern. + pages = os.path.join(ROOT, "site", "pages") + on_site = [] + for page in sorted(os.listdir(pages)): + folder = os.path.join(pages, page) + if not os.path.isdir(folder): + continue + for name in sorted(n for n in os.listdir(folder) if n.endswith(".html")): + with open(os.path.join(folder, name), encoding="utf-8") as handle: + on_site += [(f"site/pages/{page}/{name}", ln) + for ln in handle.read().splitlines() + if "gh attestation verify" in ln] + check("the website documents the verify command", bool(on_site)) + for rel, command in on_site: + check(f"{rel}: the command names the repository", "--repo " in command, + f"({command[:120]})") + check(f"{rel}: the command names a predicate type", "--predicate-type " in command, + f"(without it gh asks for SLSA provenance and gets 404: {command[:120]})") + if makes_sbom: + check(f"{rel}: the command asks for the SPDX predicate", + "https://spdx.dev/Document/v2.3" in command, f"({command[:160]})") + check(f"{rel}: the command names a file, not a pattern gh cannot open", + "*" not in command, f"({command[:120]})") + def test_the_published_release_is_checked_by_a_workflow_not_by_a_person(): """Something has to run the README's commands against what people download. From 645735c7e5a17f56a085146bc01042a2fc3b9564 Mon Sep 17 00:00:00 2001 From: DonislawDev Date: Fri, 25 Sep 2026 00:26:52 +0200 Subject: [PATCH 2/2] docs(site): stop calling the signed program unsigned Every release since 0.5.0 is signed, and the website kept saying it was not: the download page said "not signed with a paid certificate" and the questions page "new and unsigned", in both languages. Both now follow the README's SmartScreen section: the program is signed, so Windows names who signed it; the certificate is new, so SmartScreen can still warn for a while; antivirus tools may react to a program that asks for administrator rights and loads a network driver. test_the_pages_never_call_a_signed_program_unsigned keeps it true: while legal.CODESIGN_SHA256 pins a certificate, no source page may say unsigned (English or Polish). It fails rather than skips if the pin is ever emptied, so the test and the pages change together. Three mutations, three caught. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 4 ++++ site/pages/download/en.html | 9 +++++---- site/pages/download/pl.html | 10 ++++++---- site/pages/faq/en.html | 9 +++++---- site/pages/faq/pl.html | 10 ++++++---- tests/test_site.py | 25 +++++++++++++++++++++++++ 6 files changed, 51 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60010bf..f378b7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -109,6 +109,10 @@ The format follows [Keep a Changelog](https://keepachangelog.com/); versions fol download failed for everyone. It is now the same command as in the README, which is run against every published release. +- **The website no longer says the program is unsigned.** It has been signed since 0.5.0. The + download and questions pages now say so, and explain that Windows can still warn for a while + because the certificate is new. + ## [0.6.0] - 2026-09-04 ### Added diff --git a/site/pages/download/en.html b/site/pages/download/en.html index 3ab82a2..4684db1 100644 --- a/site/pages/download/en.html +++ b/site/pages/download/en.html @@ -56,10 +56,11 @@

    Checking that you got what we built

    Windows may warn you

    -

    The executable is not signed with a paid certificate, so SmartScreen flags it as something it - has not seen before. That is a statement about the certificate, not about the file - which is - exactly why the checksum and the signature are published. The - questions page covers this and what the driver needs.

    +

    The program and the installer are signed, so Windows names who signed them instead of saying + "Unknown publisher". The certificate is new, so SmartScreen may still warn for a while - it has + not seen it often yet. That is about the certificate, not about the file, and the two checks + above let you verify the file yourself. The questions page covers this and + what the driver needs.

    diff --git a/site/pages/download/pl.html b/site/pages/download/pl.html index d5897b2..88776da 100644 --- a/site/pages/download/pl.html +++ b/site/pages/download/pl.html @@ -59,10 +59,12 @@

    Sprawdzenie, że dostałeś to, co zbudowaliśmy

    Windows może ostrzegać

    -

    Plik wykonywalny nie jest podpisany płatnym certyfikatem, więc SmartScreen oznacza go jako - coś, czego wcześniej nie widział. To zdanie o certyfikacie, nie o pliku - i właśnie dlatego suma - kontrolna i podpis są opublikowane. Strona z pytaniami - opisuje to oraz czego potrzebuje sterownik.

    +

    Program i instalator są podpisane, więc Windows pokazuje, kto je podpisał, zamiast + nieznanego wydawcy. Certyfikat jest nowy, więc SmartScreen może jeszcze przez jakiś czas + ostrzegać - rzadko go dotąd widział. To zdanie o certyfikacie, nie o pliku, a dwa sprawdzenia + powyżej pozwalają zweryfikować plik samodzielnie. + Strona z pytaniami opisuje to oraz czego potrzebuje + sterownik.

    diff --git a/site/pages/faq/en.html b/site/pages/faq/en.html index e27f0b6..c83a0f8 100644 --- a/site/pages/faq/en.html +++ b/site/pages/faq/en.html @@ -34,10 +34,11 @@

    Does it send anything anywhere?

    Windows warned me about the download. Why?

    -

    Because the executable is new and unsigned, and SmartScreen flags anything it has not seen - before. A code-signing certificate is a yearly cost, not a statement about the file. Every release - publishes a SHA-256 checksum, a list of components, and a signature tying the two to the build that - produced them, so you can verify what you downloaded instead of trusting a green tick.

    +

    The program is signed, so the warning names who signed it instead of saying "Unknown + publisher". SmartScreen can still warn for a while, because the certificate is new and it has not + seen it often yet. Some antivirus tools may also react to a program that asks for administrator + rights and loads a network driver. Every release publishes a SHA-256 checksum and a signed list of + its components, so you can verify what you downloaded instead of trusting a green tick.

    diff --git a/site/pages/faq/pl.html b/site/pages/faq/pl.html index bf988ad..7f644a5 100644 --- a/site/pages/faq/pl.html +++ b/site/pages/faq/pl.html @@ -35,10 +35,12 @@

    Czy program cokolwiek gdzieś wysyła?

    Windows ostrzegł mnie przy pobieraniu. Dlaczego?

    -

    Bo plik jest nowy i niepodpisany, a SmartScreen oznacza wszystko, czego wcześniej nie widział. - Certyfikat do podpisywania kodu to koszt roczny, nie opinia o pliku. Każde wydanie publikuje sumę - kontrolną SHA-256, listę komponentów i podpis wiążący jedno z drugim z buildem, który je wytworzył - - więc możesz sprawdzić, co pobrałeś, zamiast wierzyć zielonemu znaczkowi.

    +

    Program jest podpisany, więc ostrzeżenie pokazuje, kto go podpisał, zamiast nieznanego + wydawcy. SmartScreen może jeszcze przez jakiś czas ostrzegać, bo certyfikat jest nowy i rzadko go + dotąd widział. Niektóre antywirusy mogą też reagować na program, który prosi o prawa + administratora i ładuje sterownik sieciowy. Każde wydanie publikuje sumę kontrolną SHA-256 i + podpisaną listę komponentów, więc możesz sprawdzić, co pobrałeś, zamiast wierzyć zielonemu + znaczkowi.

    diff --git a/tests/test_site.py b/tests/test_site.py index 0e5ac7f..b59c524 100644 --- a/tests/test_site.py +++ b/tests/test_site.py @@ -411,6 +411,31 @@ def glob_pages(code): return glob.glob(os.path.join(SITE, "pages", "*", "%s.html" % code)) +def test_the_pages_never_call_a_signed_program_unsigned(): + """Every release since 0.5.0 is signed, and the site went on saying it was not. + + The download page said "not signed with a paid certificate" and the questions page + "new and unsigned", in both languages, for weeks after the first signed release - + while the README told the right story. Prose that nothing reads is prose that + rots. The fact lives in ``legal.CODESIGN_SHA256``: while it pins a certificate, no + page may say the program is unsigned. Emptying that constant is the day this test + and the pages change together, which is why it fails instead of skipping. + """ + from beantester import legal + check("a signing certificate is pinned", bool(legal.CODESIGN_SHA256), + "(if signing stopped, rewrite this test together with the pages)") + denial = re.compile(r"\bunsigned\b|\bnot signed\b|niepodpisan|nie jest podpisan", re.I) + registry = build_site.load_registry(ROOT) + seen = 0 + for code in build_site.language_codes(registry): + for path in sorted(glob_pages(code)): + seen += 1 + found = denial.findall(_read(path)) + check(f"{os.path.basename(os.path.dirname(path))} [{code}]: " + f"does not call the program unsigned", not found, f"({found})") + check("there are pages to read", seen > 20, f"({seen})") + + def test_the_program_strings_really_reach_the_built_pages(tmp_path): """The other half: every ``{{app.*}}`` a page uses resolves to the program's text.