ci(pins): verouderingsbewaking op de gepinde scanners, een poort tegen stil uiteenlopen, en semgrep meteen bij #808

Merged
brenno merged 4 commits from ci/pins-verouderingsbewaking-802 into main 2026-07-24 16:48:26 +00:00
Owner

Closes #802

Sinds #799/#800 staan gitleaks, trufflehog en semgrep op een vaste versie. Goed —
maar een pin zonder tegenhanger veroudert ongemerkt, en voor déze drie is dat
niet cosmetisch: een geheimenscanner die stilstaat eindigt netjes op 0 terwijl
hij de sleutelvormen mist die ná hem zijn bedacht. Groen omdat hij niet wist waar
hij naar moest kijken — dezelfde faalvorm als een historie-scan op een ondiepe
kloon.

Er bestond één monitor, make check-actions, en die keek uitsluitend naar
Actions met een exacte versie. Een versie in een run:-blok viel erbuiten.

Wat erin zit

1. De monitor is verbreed en heet ernaar. .github/pinned-actions.json
.github/pinned-ci-versions.json, tool/check_pinned_actions.dart
tool/check_pinned_versions.dart, make check-actionsmake check-pins.
Hernoemd in plaats van er stilzwijgend binaries bij te schuiven: een manifest dat
"pinned-actions" heet en scanners bevat, laat zijn naam liegen — zoals #802
vroeg.

Twee lijsten, omdat een uses:-pin en een binary die een run:-blok binnenhaalt
op dezelfde manier verouderen en er niet op lijken. Twee bronnen ook: een
GitHub-release draagt tag_name, PyPI draagt info.version. Semgrep wordt bij
PyPI opgehaald en niet bij zijn GitHub-release, want de workflow installeert hem
met pip — "de laatste" moet betekenen: de laatste die díe weg kan bereiken.

Een half ingevulde ingang valt nu om met exit 2 in plaats van overgeslagen te
worden. Overslaan zou een pin opleveren die niemand bewaakt, en dat is precies
het ding dat deze tool moet voorkomen.

2. De offline helft is géén advies maar een poort.
test/pinned_versions_manifest_test.dart draait in de suite en houdt het
manifest tegen béide workflows. Drie richtingen zijn het voor de hand liggende
soort. De vierde niet, en die is de belangrijkste: elke *_VERSION:-pin die in
.github/workflows/ of .forgejo/workflows/ opduikt en niet in het manifest
staat, valt om.
Zonder die richting kon het manifest verouderen door
wegláting — een vierde scanner erbij, het manifest vergeten, en er kijkt weer
niemand. Dat is #802 opnieuw, één niveau hoger.

Hij scant álle workflowbestanden in beide mappen, niet alleen die het manifest
zelf noemt: een nieuw bestand is precies hoe een pin ongemerkt binnenkomt.

3. Documentatie — CHECKS.md (tabelregel, sectie, en de nieuwe test bij de
klassewachters), SECURITY_DESIGN.md (sprak van "Pinned CI Actions" met één
ingang; klopt sinds #799/#800 niet meer), CHANGELOG.

Hoe het getoetst is

  • De test drie keer rood zien staan, niet alleen groen: een gitleaks-versie
    die alleen in ci.yml werd bijgewerkt (rood, mét de bestandsnaam), een
    verzonnen HADOLINT_VERSION in scans.yml (rood, "nothing monitors this"), en
    trivy-action één minor vooruit zonder manifest (rood). Daarna alle zes weer
    groen.
  • make check-pins live gedraaid, tegen de echte API's. Zie hieronder.
  • dart run tool/check_pinned_versions.dart --offline: exit 0, manifest goed
    gevormd.
  • Alle interne ankers en relatieve bestandslinks in CHECKS.md nagelopen: geen
    kapotte.
  • make check groen.

Wat de monitor meteen vond, en meteen is opgelost

De eerste live run, vóór de laatste commit:

  aquasecurity/trivy-action@v0.36.0   up to date (v0.36.0)
  gitleaks@8.30.1                     up to date (v8.30.1)
  trufflehog@3.95.9                   up to date (v3.95.9)
  semgrep@1.170.0                     BEHIND — latest is 1.171.0

Is er een hogere, dan gaat alles mee — een melding die blijft staan is geen
controle maar een lijstje, en dan draai je alsnog niet de laatste. Vandaar de
laatste commit: semgrep naar 1.171.0 in de pin van scans.yml, in de pin van
ci.yml, in het manifest, én op de werkbank (brew upgrade semgrep). Die
laatste is geen bijzaak: dat CI en de werkbank dezelfde scanner draaien is de
reden dat deze pins bestaan, dus een pin die vooruitloopt op de machine breekt
precies wat hij moest bewaken.

Nagemeten in plaats van aangenomen, want een nieuwe scannerversie mag stilletjes
iets anders vinden: make sast op 1.171.0 geeft 0 bevindingen over 709
bestanden met 3 regels
, identiek aan 1.170.0. Daarna:

  aquasecurity/trivy-action@v0.36.0   up to date (v0.36.0)
  gitleaks@8.30.1                     up to date (v8.30.1)
  trufflehog@3.95.9                   up to date (v3.95.9)
  semgrep@1.171.0                     up to date (1.171.0)

OK — every pinned CI version is on its latest release.

De manifest-test bleef daarbij groen, wat precies is wat hij moet bewijzen: het
getal ging op alle drie de plekken tegelijk mee.

Bewaker

Geraakt: uitgaand verkeer / een nieuwe partij, en een publieke belofte.

  • Nieuw in het pad: pypi.org. De tool sprak alleen api.github.com. Het is
    een leesverzoek zonder inloggegevens, alleen wanneer een ontwikkelaar
    make check-pins met de hand draait — niet in de app, niet in make check,
    niet in enig pad van een gebruiker. Zelfde categorie als deps-outdated. De
    reden staat in het manifest genoteerd in plaats van stil gelaten.
  • De belofte. SECURITY_DESIGN.md zegt nu dat de scanners op veroudering
    bewaakt worden. Dat is getoetst en waar: de tool is live gedraaid en meldde
    een echte achterstand. Geen onbetaalde belofte.
  • Bestandsformaat, opslag en app-afhankelijkheden: niet geraakt. Geen wijziging
    in pubspec.yaml, dus geen SBOM.

Botsing, hardop: een adviserende controle die blijft rammelen wordt na een maand
behang. Het antwoord daarop is de bump doen — pin én lokale installatie samen,
zoals in de laatste commit — en niet de monitor dempen. Zou blijken dat deze
vier zó vaak uitkomen dat het gerammel de melding waardeloos maakt, dan is de
bespreekbare aanpassing een drempel (bijvoorbeeld: alleen melden bij een minor-
of majorverschil), niet het weghalen van de bewaking.

Closes #802 Sinds #799/#800 staan gitleaks, trufflehog en semgrep op een vaste versie. Goed — maar een pin zonder tegenhanger veroudert ongemerkt, en voor déze drie is dat niet cosmetisch: een geheimenscanner die stilstaat eindigt netjes op 0 terwijl hij de sleutelvormen mist die ná hem zijn bedacht. Groen omdat hij niet wist waar hij naar moest kijken — dezelfde faalvorm als een historie-scan op een ondiepe kloon. Er bestond één monitor, `make check-actions`, en die keek uitsluitend naar Actions met een exacte versie. Een versie in een `run:`-blok viel erbuiten. ## Wat erin zit **1. De monitor is verbreed en heet ernaar.** `.github/pinned-actions.json` → `.github/pinned-ci-versions.json`, `tool/check_pinned_actions.dart` → `tool/check_pinned_versions.dart`, `make check-actions` → `make check-pins`. Hernoemd in plaats van er stilzwijgend binaries bij te schuiven: een manifest dat "pinned-actions" heet en scanners bevat, laat zijn naam liegen — zoals #802 vroeg. Twee lijsten, omdat een `uses:`-pin en een binary die een `run:`-blok binnenhaalt op dezelfde manier verouderen en er niet op lijken. Twee bronnen ook: een GitHub-release draagt `tag_name`, PyPI draagt `info.version`. Semgrep wordt bij PyPI opgehaald en niet bij zijn GitHub-release, want de workflow installeert hem met `pip` — "de laatste" moet betekenen: de laatste die díe weg kan bereiken. Een half ingevulde ingang valt nu om met exit 2 in plaats van overgeslagen te worden. Overslaan zou een pin opleveren die niemand bewaakt, en dat is precies het ding dat deze tool moet voorkomen. **2. De offline helft is géén advies maar een poort.** `test/pinned_versions_manifest_test.dart` draait in de suite en houdt het manifest tegen béide workflows. Drie richtingen zijn het voor de hand liggende soort. De vierde niet, en die is de belangrijkste: **elke `*_VERSION:`-pin die in `.github/workflows/` of `.forgejo/workflows/` opduikt en niet in het manifest staat, valt om.** Zonder die richting kon het manifest verouderen door wegláting — een vierde scanner erbij, het manifest vergeten, en er kijkt weer niemand. Dat is #802 opnieuw, één niveau hoger. Hij scant álle workflowbestanden in beide mappen, niet alleen die het manifest zelf noemt: een nieuw bestand is precies hoe een pin ongemerkt binnenkomt. **3. Documentatie** — CHECKS.md (tabelregel, sectie, en de nieuwe test bij de klassewachters), SECURITY_DESIGN.md (sprak van "Pinned CI Actions" met één ingang; klopt sinds #799/#800 niet meer), CHANGELOG. ## Hoe het getoetst is - **De test drie keer rood zien staan**, niet alleen groen: een gitleaks-versie die alleen in `ci.yml` werd bijgewerkt (rood, mét de bestandsnaam), een verzonnen `HADOLINT_VERSION` in `scans.yml` (rood, "nothing monitors this"), en `trivy-action` één minor vooruit zonder manifest (rood). Daarna alle zes weer groen. - **`make check-pins` live gedraaid**, tegen de echte API's. Zie hieronder. - **`dart run tool/check_pinned_versions.dart --offline`**: exit 0, manifest goed gevormd. - **Alle interne ankers en relatieve bestandslinks in CHECKS.md** nagelopen: geen kapotte. - `make check` groen. ## Wat de monitor meteen vond, en meteen is opgelost De eerste live run, vóór de laatste commit: ``` aquasecurity/trivy-action@v0.36.0 up to date (v0.36.0) gitleaks@8.30.1 up to date (v8.30.1) trufflehog@3.95.9 up to date (v3.95.9) semgrep@1.170.0 BEHIND — latest is 1.171.0 ``` **Is er een hogere, dan gaat alles mee** — een melding die blijft staan is geen controle maar een lijstje, en dan draai je alsnog niet de laatste. Vandaar de laatste commit: semgrep naar 1.171.0 in de pin van `scans.yml`, in de pin van `ci.yml`, in het manifest, én op de werkbank (`brew upgrade semgrep`). Die laatste is geen bijzaak: dat CI en de werkbank dezelfde scanner draaien is de reden dat deze pins bestaan, dus een pin die vooruitloopt op de machine breekt precies wat hij moest bewaken. Nagemeten in plaats van aangenomen, want een nieuwe scannerversie mag stilletjes iets anders vinden: `make sast` op 1.171.0 geeft **0 bevindingen over 709 bestanden met 3 regels**, identiek aan 1.170.0. Daarna: ``` aquasecurity/trivy-action@v0.36.0 up to date (v0.36.0) gitleaks@8.30.1 up to date (v8.30.1) trufflehog@3.95.9 up to date (v3.95.9) semgrep@1.171.0 up to date (1.171.0) OK — every pinned CI version is on its latest release. ``` De manifest-test bleef daarbij groen, wat precies is wat hij moet bewijzen: het getal ging op alle drie de plekken tegelijk mee. ## Bewaker Geraakt: **uitgaand verkeer / een nieuwe partij**, en een **publieke belofte**. - **Nieuw in het pad: `pypi.org`.** De tool sprak alleen `api.github.com`. Het is een leesverzoek zonder inloggegevens, alleen wanneer een ontwikkelaar `make check-pins` met de hand draait — niet in de app, niet in `make check`, niet in enig pad van een gebruiker. Zelfde categorie als `deps-outdated`. De reden staat in het manifest genoteerd in plaats van stil gelaten. - **De belofte.** SECURITY_DESIGN.md zegt nu dat de scanners op veroudering bewaakt worden. Dat is getoetst en waar: de tool is live gedraaid en meldde een echte achterstand. Geen onbetaalde belofte. - Bestandsformaat, opslag en app-afhankelijkheden: niet geraakt. Geen wijziging in `pubspec.yaml`, dus geen SBOM. Botsing, hardop: een adviserende controle die blijft rammelen wordt na een maand behang. Het antwoord daarop is de bump doen — pin én lokale installatie samen, zoals in de laatste commit — en niet de monitor dempen. Zou blijken dat deze vier zó vaak uitkomen dat het gerammel de melding waardeloos maakt, dan is de bespreekbare aanpassing een drempel (bijvoorbeeld: alleen melden bij een minor- of majorverschil), niet het weghalen van de bewaking.
`make check-actions` keek uitsluitend naar Actions met een exacte versie, en er
stond er precies één in. De drie scanners uit #799/#800 staan gepind in een
`run:`-blok en vielen daar dus buiten: niets merkte het als die getallen twee
jaar bleven staan.

Voor déze drie is dat niet cosmetisch. Een geheimenscanner die stilstaat eindigt
netjes op 0 terwijl hij de sleutelvormen mist die ná hem zijn bedacht — groen
omdat hij niet wist waar hij naar moest kijken, dezelfde faalvorm als een
historie-scan op een ondiepe kloon. Niet-pinnen is geen alternatief; wat
ontbrak was de tegenhanger van de pin.

Hernoemd in plaats van er stilzwijgend binaries bij te schuiven: een manifest
dat "pinned-actions" heet en scanners bevat, laat zijn naam liegen. Dus
`.github/pinned-ci-versions.json`, `tool/check_pinned_versions.dart`,
`make check-pins`, met twee lijsten — `actions` voor `uses:`-pins, `tools` voor
binaries die een `run:`-blok binnenhaalt.

Twee bronnen, want de drie leveren niet hetzelfde uit: een GitHub-release draagt
`tag_name`, PyPI draagt `info.version`. Semgrep wordt bij PyPI opgehaald en niet
bij zijn GitHub-release, omdat de workflow hem met `pip` installeert — "de
laatste" moet betekenen: de laatste die díe weg kan bereiken.

Een half ingevulde ingang laat het gereedschap nu vallen met exit 2 in plaats van
hem over te slaan. Overslaan zou een pin opleveren die niemand bewaakt, en dat is
precies wat dit ding moet voorkomen.

Live gedraaid: trivy-action, gitleaks en trufflehog staan op hun laatste versie,
en semgrep is één release achter (1.170.0 tegen 1.171.0). Die bump zit hier
bewust niet in — zie de PR-tekst.

Refs #802

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`make check-pins` beantwoordt "is er iets nieuwers", maar opent geen enkele
workflow — dus de vraag "is het getal in dit manifest nog het getal dat de
workflow gebruikt" bleef onbeantwoord. Dat is juist de vraag die stil misgaat:
de drie scannerversies staan in twéé workflowbestanden, dus een bump heeft drie
landingsplekken en twee daarvan vergeet je.

Deze test draait offline in de suite en houdt het manifest tegen beide workflows
in vier richtingen. Drie ervan zijn het voor de hand liggende soort. De vierde
niet, en die is de belangrijkste: elke `*_VERSION:`-pin die in
`.github/workflows/` of `.forgejo/workflows/` opduikt en niet in het manifest
staat, valt hier om. Zonder die richting kon het manifest verouderen door
wegláting — een vierde scanner erbij, het manifest vergeten, en er kijkt weer
niemand. Dat is #802 opnieuw, één niveau hoger.

Hij scant daarom álle workflowbestanden in beide mappen, niet alleen de bestanden
die het manifest zelf al noemt: een nieuw bestand is precies hoe een pin
ongemerkt binnenkomt.

Drie mutaties gedraaid om te zien dat hij bijt, niet alleen dat hij groen staat:
een gitleaks-versie die alleen in ci.yml bijgewerkt werd (rood, mét de
bestandsnaam), een verzonnen HADOLINT_VERSION in scans.yml (rood, "nothing
monitors this"), en trivy-action één minor vooruit zonder manifest (rood). Daarna
alle zes weer groen.

Refs #802

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
docs(pins): de hernoemde monitor, de poort ernaast, en waarom het uitmaakt
All checks were successful
scans / scans (pull_request) Successful in 3m23s
01f518d241
CHECKS.md: de tabelregel en de sectie op `make check-pins` gezet, met de twee
soorten pins, de twee bronnen (GitHub-release vs PyPI) en waarom semgrep bij PyPI
wordt opgehaald. Plus de scheiding die makkelijk vervaagt: de netwerkvraag is
adviserend, de offline vraag — klopt het manifest nog met de workflows — is een
harde poort in de suite. De nieuwe test staat bij de klassewachters, want dat is
wat hij is: hij vangt niet één vergeten bump maar de hele familie.

SECURITY_DESIGN.md sprak van "Pinned CI Actions" met één ingang; dat klopt sinds
#799/#800 niet meer. Bijgewerkt mét de datum en de reden, zoals de correctie van
22-07 er vlak boven.

CHANGELOG kreeg de ingang. De oude ingang over `make check-actions` blijft staan
— het is een dagboek — maar krijgt een gedateerde verwijzing, anders wijst hij
naar een doel en een bestand die niet meer bestaan.

Alle interne ankers en relatieve bestandslinks in CHECKS.md nagelopen: geen
kapotte.

Refs #802

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
brenno changed title from ci(pins): verouderingsbewaking op de gepinde scanners, plus een poort tegen stil uiteenlopen to ci(pins): verouderingsbewaking op de gepinde scanners, een poort tegen stil uiteenlopen, en semgrep meteen bij 2026-07-24 16:38:10 +00:00
chore(pins): semgrep 1.170.0 → 1.171.0, overal tegelijk
Some checks failed
scans / scans (pull_request) Has been cancelled
d058f3f536
De eerste bevinding van de monitor uit de vorige commits, meteen weggewerkt. Is
er een hogere versie, dan gaat álles mee — een melding die blijft staan is geen
controle maar een lijstje, en dan draai je alsnog niet de laatste.

"Alles" is hier vier plekken: de pin in `.forgejo/workflows/scans.yml` (de
workflow die écht draait), de pin in `.github/workflows/ci.yml`, het getal in
`.github/pinned-ci-versions.json`, en de installatie op de werkbank
(`brew upgrade semgrep`). Die laatste hoort erbij en is geen bijzaak: dat CI en
de werkbank dezelfde scanner draaien is de reden dat deze pins bestaan, dus een
pin die vooruitloopt op de machine breekt precies wat hij moest bewaken.

Nagemeten in plaats van aangenomen, want een nieuwe scannerversie mag stilletjes
iets anders vinden: `make sast` op 1.171.0 geeft 0 bevindingen over 709 bestanden
met 3 regels — identiek aan 1.170.0. `make check-pins` staat nu op vier keer "up
to date", en de manifest-test blijft groen omdat het getal op alle drie de
plekken tegelijk mee ging (wat hij precies moet bewijzen).

Refs #802

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
brenno force-pushed ci/pins-verouderingsbewaking-802 from d058f3f536
Some checks failed
scans / scans (pull_request) Has been cancelled
to 7f4b18d1d5
All checks were successful
scans / scans (pull_request) Successful in 3m20s
2026-07-24 16:44:12 +00:00
Compare
brenno merged commit 606f5a295d into main 2026-07-24 16:48:26 +00:00
Sign in to join this conversation.
No description provided.