SYS/LOG · 2026-07-024 MIN LEESTIJD

Een gate die niet kan falen is een rapport

Elke pipeline die ik overneem heeft een security-scanstap. Bijna geen enkele kan een release tegenhouden. Dat is geen toolingprobleem.

Je dependencies scannen in CI is best practice. Daar is niemand het mee oneens. Je zet er een stap in, die kijkt naar je lockfile, vertelt je over bekende kwetsbaarheden, en je hoort van de vervelende voordat je klant dat doet.

Ik geloof dat. Ik heb het voor anderen ingericht. Het is het juiste om te doen.

En het blijft ook werken

Zet Trivy op de containerbuild en je ontdekt dat de base image al drie jaar een oude libxml2 meesleept. Nuttig.

Zet een dependency-scan op de lockfile en je vindt de transitieve dependency die niemand gekozen heeft, vier niveaus diep, die HTTP-verzoeken doet bij import. Erg nuttig.

Zet een secret-scanner op het pre-receive-pad en je vindt de AWS-sleutel die iemand in 2023 heeft gecommit en in de volgende commit "verwijderd". Buitengewoon nuttig, en een beetje verontrustend.

Drie uit drie. De scanners werken. Ze vinden echte dingen.

De regel onderaan de stap

Als ik een pipeline te zien krijg, is de scanstap dus niet waar ik het probleem verwacht. Je leest zoiets zoals je een alinea leest waar je het toch al mee eens bent.

Zo kwam het dat ik bij een opdracht al tweederde door een GitLab CI-bestand van 400 regels heen was voor ik dit opmerkte:

container_scanning:
  stage: test
  script:
    - trivy image --severity HIGH,CRITICAL "$IMAGE"
  allow_failure: true

allow_failure: true.

Ik keek naar de andere drie. continue-on-error: true op de SAST-job. Een || true achter de dependency-check. En de secret-scanner, de goede, rapporteerde naar een dashboard zonder ingestelde drempel.

Wat de pipeline werkelijk had gedaan

Dit is het getal dat de discussie beëindigde. Ik trok de jobhistorie van de voorgaande twaalf maanden op. De scanstappen hadden 2.310 keer gedraaid. Ze hadden op 2.118 van die runs bevindingen gerapporteerd.

Ze hadden nul keer een release tegengehouden. Geen enkele keer. In een jaar was er geen enkele deploy gestopt door een security-bevinding, en de pipeline was vanaf de eerste commit zo ontworpen dat dat niet kón.

Dat team had geen security-gate. Het had een security-nieuwsbrief met een uitstekende openratio en nul abonnees.

En ik wil eerlijk zijn over hoe dat komt, want het is geen nalatigheid. Iemand heeft allow_failure aangezet tijdens de uitrol, zodat een luidruchtige nieuwe scanner niet meteen iedereen zou blokkeren op dag één. Dat is op dag één de juiste keuze. De fout is dat dag één nooit eindigde. Er stond geen datum bij, geen ticket, geen drempel om naar door te groeien. Een tijdelijke uitzondering zonder vervaldatum is een permanente beslissing die niemand zich herinnert genomen te hebben.

Het deel dat niet over tooling gaat

De oplossing is geen betere scanner. Trivy vond precies de goede dingen. De oplossing is een beslissing, en dat is een beslissing die de tooling niet voor je kan nemen:

Welke bevindingen houden een release tegen?

Die vraag is ongemakkelijk, want elk eerlijk antwoord gaat op enig moment een release blokkeren, waarschijnlijk op een slecht uitkomend moment, waarschijnlijk voor iets waarvan je gaat betogen dat het in jouw context niet exploiteerbaar is. Precies daarom is allow_failure: true zo rustgevend. Het stelt de vraag eindeloos uit.

Schrijf het antwoord op. Het hoeft niet streng te zijn. Het onze begon daar bewust ruim:

  • Critical, met een fix beschikbaar, in een runtime-dependency: blokkeert.
  • Al het andere: rapporteert, en komt op een weeklijst met de naam van een eigenaar erbij.
  • Uitzonderingen: een bestand in de repo, één regel per uitzondering, met een vervaldatum. CI faalt op een verlopen uitzondering.

Meer niet. Drie regels. De eerste release die hij tegenhield was acht dagen later, en het was een echte: een HIGH in de JSON-parser van de publieke API, fix beschikbaar, één versiebump verderop.

SBOM's hebben dezelfde kwaal

Nu we er toch zijn. Een SBOM genereren staat inmiddels als vinkje op nogal wat inkoopformulieren, dus nogal wat pipelines genereren er een, hangen hem aan het artefact, en kijken er nooit meer naar.

Een SBOM die niets leest is een inventarislijst van een magazijn waar niemand komt. De control is niet het document. De control is het ding dat bij admission dat document leest en de deploy weigert als het antwoord niet klopt. In Kubernetes is dat een policy-engine die de attestatie nakijkt voordat de pod wordt toegelaten. Zolang niets het leest en nee kan zeggen, heb je een build-artefact met een bijlage, en een praatplaatje waarop staat dat je aan supply chain security doet.

Terug naar boven

Je dependencies scannen in CI is best practice. Dat vind ik nog steeds.

Maar "we scannen" en "we hebben een control" zijn verschillende beweringen, en maar één ervan overleeft de vraag die ik nu als eerste stel als ik naar een pipeline kijk: wanneer heeft dit voor het laatst een release tegengehouden?

Is het antwoord "nooit", dan heb je geen gate. Dan heb je een rapport. Rapporten zijn prima. Zet er op de architectuurplaat alleen geen hangslotje naast.