Geheimen in Git voorkomen: pre-commit hooks met gitleaks

Geheimen in Git voorkomen via drie lagen: .gitignore, pre-commit hook met gitleaks en CI-scan als vangnet, plus rotatie als het toch misgaat.

· 20 september, 2026 · 6 min lezen
In het kort
  • Één laag is niet genoeg: combineer .gitignore, pre-commit hook en CI-scan als vangnet.
  • Gitleaks in een pre-commit hook vangt het gros van accidentele commits vóór ze de branch verlaten.
  • False positives zijn onvermijdelijk — bouw een .gitleaksignore met bewuste uitzonderingen.
  • Als een geheim al gepusht is: roteer eerst, force-push en BFG later. History rewriten is niet genoeg.
  • Werkt op elke stack: WordPress-thema, Laravel-project of Node-app krijgen dezelfde hook.
In dit artikel

Geheimen in Git voorkomen is één van die dingen waar iedereen het over eens is en waar bijna niemand een systeem voor heeft. Eén gecommite API-key of .env-bestand is genoeg om een sleutel te rouleren, een leverancier te bellen en jezelf een half weekend te veroorzaken. Het probleem: zodra je pusht, is de history publiek toegankelijk voor iedereen met leesrechten. Force-push helpt maar deels — mirrors, forks, CI-artifacts en cached blobs bij je Git-host houden een kopie in leven. Dit artikel behandelt een drie-lagen-aanpak (.gitignore, pre-commit hook, CI-scan), plus wat te doen als het tóch gebeurt.

Stack-agnostisch: dit werkt voor WordPress-thema-repo’s, Laravel-apps, Statamic-projecten, Node-services en alles daartussen.

Werkplek voor het voorkomen van geheimen in Git met pre-commit hooks

Waarom één laag nooit genoeg is

Geen enkele van de drie lagen hieronder is op zichzelf voldoende:

  • .gitignore vergeet je onvermijdelijk een keer bij te werken (nieuwe secret-file, nieuwe extensie).
  • Pre-commit hooks kunnen worden overgeslagen met git commit --no-verify — bewust of per ongeluk.
  • CI-scans vangen alles, maar pas nádat de commit al in de branch staat en je hem moet gaan wegwerken.

Alleen de drie lagen samen leveren een stack die in de praktijk niet-triviaal te omzeilen is. Dat is defense-in-depth voor secrets management. Voor context: goede lokale .env-hygiëne op de server hoort bij .env-configuratie op de server, maar bij Git-repo’s ligt het probleem eerder in de commit-flow dan op de server.

Geheimen in Git voorkomen: pre-commit hooks met gitleaks

Drie beschermingslagen tegen secrets in Git: .gitignore, pre-commit, CI

Laag 1 — .gitignore correct opzetten

De eerste vangrail is triviaal maar wordt structureel onderschat. Een basale .gitignore voor een gemengde stack:

# Env en secrets
.env
.env.*
!.env.example

# Sleutels en certs
*.pem
*.key
*.crt
id_rsa
id_rsa.pub

# Credential-bestanden
credentials.json
service-account*.json
aws.credentials

# WordPress
wp-config.php

# IDE-configuraties met tokens
.vscode/settings.json
.idea/workspace.xml

Verifieer altijd met git check-ignore -v .env voordat je commit. Als het commando niets teruggeeft, wordt het bestand niet genegeerd — dat is de dagelijkse valkuil bij nieuwe project-templates.

Belangrijk: .env in .gitignore zetten helpt níet als het bestand al eerder is gecommit. git rm --cached .env haalt het uit de index, maar de history bevat het nog steeds. Zie laag 3-reactieve stappen verderop.

Laag 2 — pre-commit hook lokaal met gitleaks

De volgende laag: automatisch scannen bij elke commit. Het pre-commit framework (oorsprong bij Yelp, community-maintained) is een dunne wrapper die hooks configureert per repo, met een gedeelde config. Installatie eenmalig systeembreed (pip install pre-commit of brew install pre-commit) plus per repo een .pre-commit-config.yaml:

repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.30.1
    hooks:
      - id: gitleaks

Activeren met pre-commit install in de repo (eenmalig). Vanaf dat moment scant gitleaks op GitHub elke commit op ruim 160 built-in secret-patronen (AWS-keys, Stripe-keys, private RSA keys, generic high-entropy strings, etc.).

Trufflehog is een goed alternatief, met name als je last hebt van false positives — de --only-verified optie doet een live-check tegen de bijhorende API’s en drukt false positives in de praktijk sterk. Nadeel: die verificatie kost tijd en werkt niet in air-gapped omgevingen.

Laag 3 — CI/CD-scan in pipeline

Pre-commit is best-effort — iemand kan git commit --no-verify gebruiken, of vergeten pre-commit install uit te voeren op een nieuwe checkout. De CI-scan is de vangrail die niet omzeilbaar is (mits je merge protection goed staat).

Voor GitHub Actions:

name: Gitleaks
on: [push, pull_request]
jobs:
  gitleaks:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: gitleaks/gitleaks-action@v2

Zet daarna in de branch protection rules: “Require status checks to pass before merging” met de gitleaks-check als vereist. Vanaf dat moment kan geen enkele PR met een detected secret worden gemerged, ongeacht wat de developer lokaal doet.

Voor GitLab: gitleaks --source . --report-format sarif --report-path gitleaks.sarif in je .gitlab-ci.yml. Voor Bitbucket: vergelijkbaar patroon.

.gitleaksignore en false positives

Elke gitleaks-hit heeft een Fingerprint (commit-SHA plus regel plus rule-id). Bewuste test-data of documented example-tokens verplaats je naar een .gitleaksignore-bestand:

# Test fixture, geen echte key
abc123def456...:tests/fixtures/stripe-response.json:stripe-access-token:12

Inline uitzonderen kan ook: voeg # gitleaks:allow toe aan de regel met de “hit”. Gebruik dit spaarzaam — de allow-annotaties zijn een audit-risico bij een security-review.

Voor eigen patronen (interne API-keys, custom-token-formaten) maak je een gitleaks.toml in de repo:

[extend]
useDefault = true

[[rules]]
id = "lypii-internal-token"
description = "Interne project-token"
regex = '''lypii_[a-z0-9]{32}'''
tags = ["key", "internal"]

useDefault = true is cruciaal: zonder deze regel vervangt jouw config de 160+ built-in rules in plaats van ze aan te vullen.

Wat als een secret al gecommit is

Stappen na een secret leak in Git: rotate, filter-repo, force-push, opschonen

Ontdek je een leak in de history, dan is de volgorde van stappen belangrijker dan de snelheid van reageren. In mijn ervaring is de meest gemaakte fout dat men eerst de history probeert te herschrijven en pas daarna de sleutel roteert — daarmee heeft de aanvaller een venster van uren tot dagen om de nog-geldige sleutel te gebruiken.

Stap 1: roteer de sleutel of het wachtwoord in het externe systeem. Dit is de belangrijkste stap. AWS-key: nieuwe key aanmaken, oude direct deactiveren. Stripe-key: rollen via dashboard. Wachtwoord: wijzigen in de bron. Zolang de gecompromitteerde credential geldig is, blijft de leak actief — óók als je hem uit de Git-history rewrit. Zie key-rotatie na een leak voor de bredere incident-response-context.

Stap 2: verwijder de commit uit history. Met git filter-repo (moderne aanpak) of BFG Repo-Cleaner (oudere maar simpelere aanpak):

git filter-repo --path .env --invert-paths

Of voor een specifieke string in blobs:

git filter-repo --replace-text <(echo 'oude_secret==>REDACTED')

Stap 3: force-push naar remote. git push --force-with-lease origin main — plus alle andere branches waar de secret in stond.

Stap 4: controleer mirrors, forks en CI-artifacts. Fork-eigenaren zien de rewrite niet automatisch. Open forks moeten óók de rewrite doen, of anders geïnformeerd worden. CI-artifacts (log-bestanden, build-outputs) op GitHub/GitLab bevatten mogelijk de secret in leesbare vorm; die opschonen via de repo-instellingen. Pull requests met de secret in de diff sluiten en cache-bare blobs melden bij support van je Git-host voor een volledige purge.

Belangrijk: zelfs met alle vier stappen is er geen garantie dat de secret niet gecopied is door een aanvaller die de repo tussentijds monitorde. Roteren blijft de enige echte remediation; de history-rewrite is opruimen achteraf.

Multi-stack noot

De aanpak werkt identiek op elke stack. Verschillen zitten alleen in wélke files je in .gitignore zet:

  • WordPress-thema of -plugin: wp-config.php, .env (bij Bedrock/Sage), debug.log.
  • Laravel: .env, storage/, bootstrap/cache/, oauth-*.key.
  • Statamic: .env, storage/, bootstrap/cache/.
  • Node/Next.js: .env.local, .env.production, node_modules/.
  • Custom PHP: config/local.php, secrets/, *.credentials.

De pre-commit hook en CI-scan zijn overal hetzelfde. Alleen de eerste laag (.gitignore) is stack-specifiek.

Wanneer schakel je Lypii in

Setup van pre-commit hook plus CI-scan per repo kost ongeveer 1 tot 2 uur, afhankelijk van hoe veel bestaande false positives moeten worden weggewerkt.

  • Fix €70 per repo — pre-commit installatie, gitleaks-config, CI-scan aanzetten, .gitleaksignore opzetten voor bestaande false positives.
  • Support-abonnement €95 per maand — inclusief periodieke history-scan (retro-actief zoeken naar leaks in oude commits), rule-updates en trufflehog-verificatie waar zinvol.
  • Uur-tarief €105 — voor incident-response bij een actieve leak: rotatie-coördinatie, filter-repo runs, mirror-opschoning.

Verbeteren boven herbouwen: dit is toevoegen aan je bestaande repo-workflow, geen migratie.

Multi-stack opmerking: de defense-in-depth-aanpak (.gitignore, pre-commit, CI) werkt hetzelfde op WordPress-, Laravel-, Statamic-, Node- en custom-repos. Alleen laag 1 (.gitignore) verschilt per stack.

Serhii Lypii
Gratis meedenken

Pre-commit én CI-scan tegen secrets in Git?

Wil je gitleaks als pre-commit hook lokaal én een CI-scan in de pipeline die merges blokkeert bij een hit? Ik zet het op per repo, inclusief .gitleaksignore-beleid. Fix vanaf €70 per repo, support €95 per maand.

Vraag een secrets-scan setup aan
Vond je dit nuttig? Deel het:
FAQ

Veelgestelde vragen over geheimen in Git voorkomen

Wat is het verschil tussen `git rm --cached` en `git filter-repo`?
git rm --cached haalt een bestand uit de index en toekomstige commits, maar laat de history intact — de secret staat er nog. git filter-repo herschrijft de history en verwijdert het bestand overal. Voor secrets: altijd filter-repo (of BFG), plus rotatie.
Kan ik gitleaks lokaal draaien zonder pre-commit framework?
Ja. gitleaks detect --source . --verbose scant de working directory. Voor CI: gitleaks detect --source . --report-format sarif --report-path gitleaks.sarif. Het pre-commit framework is een dunne wrapper die dit automatisch aanroept per commit.
Werkt dit ook voor grote monorepos?
Ja, met wat afstellen. Gitleaks scant standaard alle files, wat op een grote monorepo minuten kan duren. Configureer [extend.path] in gitleaks.toml of gebruik --config met een dedicated ruleset per subdirectory. Voor pre-commit: alleen gewijzigde files scannen (staat standaard aan).
Wat als ik geen CI heb?
Dan is de pre-commit hook je enige laag na .gitignore — dat is beter dan niets, maar zwakker dan een verplichte scan op push. Overweeg voor kleine projecten een self-hosted GitHub Actions runner of gebruik een gratis CI-tier bij GitHub/GitLab; de setup is een uur werk.
Wat kost het als Lypii dit voor mij regelt?
Per repo: fix van €70 voor de basis-setup (pre-commit + CI + .gitleaksignore). Support-abonnement €95 per maand voor structureel onderhoud en retro-scans. Uur-tarief €105 voor incident-response bij een actieve leak, inclusief rotatie-coördinatie en history-rewrite.
Serhii Lypii
Over de auteur

Serhii Lypii

Freelance webmaster · Terneuzen

Sinds 2012 werk ik met websites en leverde ik ruim 210 projecten op. Ik help mkb-ondernemers met onderhoud, snelheid, beveiliging en praktische automatisering. Nuchter, zonder hype.

Ik schreef dit artikel op en werkte het voor het laatst bij op .

Meer over Lypii
Even sparren over je site?

Loop je ergens tegenaan of twijfel je over je site? Ik denk graag mee. Kies hieronder hoe je contact opneemt.

Reactie binnen 4 uur op werkdagen
Navigatie
Home Over mij Kennisbank
Contact
+31 6 39 56 93 03 serhii@lypii.nl WhatsApp
Stuur een bericht