Ein Token oder API-Key ist in einem Commit gelandet, der Branch war öffentlich sichtbar, und jetzt ist die Frage nicht mehr Prävention, sondern Aufräumen. Dieser Beitrag setzt dort an, wo Secret Scanning und Push Protection nicht mehr greifen, nämlich wenn das Secret schon in der Historie steht. Wie es dort hineingekommen ist, steht in einem früheren Beitrag zu Secrets im Code. Hier geht es nur um die Bereinigung danach.
Warum eine gelöschte Datei im neuen Commit nicht reicht
Der naheliegende erste Reflex ist, die Datei zu löschen und den Commit zu pushen. Das ändert den aktuellen Stand, aber nicht die Historie. GitHub beschreibt das in der Anleitung zum Entfernen sensibler Daten so: Die Commits mit dem Secret bleiben „in any clones or forks of your repository“ erreichbar, und ausdrücklich gilt: „You cannot remove sensitive data from other users‘ clones of your repository.“ Jeder, der das Repo vor deinem Löschcommit geklont hat, kann die alte Datei weiterhin aus seiner lokalen Historie herausholen, unabhängig davon, was du im Hauptbranch tust.
Was git filter-repo technisch tut
git filter-repo beschreibt sich selbst im README als „a versatile tool for rewriting history“. Es arbeitet nicht auf dem Arbeitsverzeichnis, sondern auf jedem einzelnen Commit: Pfade, Blobs oder Commit-Inhalte, die zu den angegebenen Filterregeln passen, werden aus der gesamten Historie entfernt, nicht nur aus dem aktuellen Stand. Dabei ändern sich die Commit-Hashes aller betroffenen Commits und aller Nachfolger. Das Tool erwartet deshalb ausdrücklich einen frischen Klon und bricht sonst ab („detecting and bailing if we’re not in a fresh clone“), und es räumt nach dem Lauf automatisch den alten Datenmüll weg und kompaktiert das Repository („automatically remove old cruft and repack the repository“).
Was der BFG Repo-Cleaner technisch tut
Der BFG arbeitet nach demselben Grundprinzip, mit anderem Werkzeug. Laut Projektseite „will update your commits and all branches and tags so they are clean“, nachdem du Regeln wie das Entfernen großer Dateien oder das Ersetzen von Text (etwa dem Secret-String) angegeben hast. Der BFG läuft typischerweise auf einem Mirror-Klon, prüft jeden Blob und jeden Tree, und markiert Treffer zur Entfernung. Die tatsächliche Löschung der alten Objekte übernimmt danach git gc, wie die Projektseite explizit festhält. Für dich heißt das in der Praxis: BFG ist je nach Anwendungsfall einfacher bei klar abgrenzbaren Mustern (große Dateien, bekannte Secret-Strings), git filter-repo ist das feinere Werkzeug, wenn du Pfade, Autoren oder Commit-Metadaten gezielt umschreiben willst.
Warum danach ein Force-Push nötig ist
Weil sich die Commit-Hashes geändert haben, ist der neue Verlauf kein Vorfahre des alten mehr. Ein normaler git push lehnt genau das ab: Laut Git-Dokumentation wird ein Push verweigert, der keinen Fast-Forward darstellt, „git push will refuse to update a branch that is not an ancestor of the commit being pushed“. Erst --force hebt das auf, mit einer klaren Warnung direkt aus der Dokumentation: Es „can cause the remote repository to lose commits“ und wirkt auf alle gepushten Refs gleichzeitig.
Was das für Mitarbeitende, Forks und CI-Caches bedeutet
Der Force-Push überschreibt Branches, Tags und Refs auf dem Remote vollständig. Jeder, der lokal noch auf dem alten Stand arbeitet, muss neu klonen, sonst bringt ein normaler Pull oder Push die alten Commits zurück. Die BFG-Dokumentation sagt das wörtlich für Kolleg:innen mit bestehenden Klonen: Sie sollen „ditch their old copies of the repo and do fresh clones“. Für Forks gilt dasselbe nicht automatisch: GitHub weist ausdrücklich darauf hin, dass ein Commit, der in einem Fork existiert, dort „will continue to be accessible“, bis der Fork-Owner selbst nachzieht. CI-Systeme mit Checkout-Caches oder gepinnten Commit-Referenzen sollten diese Caches invalidieren, weil sie sonst weiterhin auf alte, längst „entfernte“ Commits zugreifen können.
Die Pflicht-Fußnote: Rewrite ersetzt nie die Rotation
Ein bereinigter Verlauf ist kein Beweis dafür, dass das Secret unschädlich ist. GitHub formuliert es so: „If you only rewrite your history and force push it, the commits with sensitive data may still be accessible elsewhere“, etwa in Logs, Caches, Forks oder der CI-Historie. Deshalb muss das Secret selbst unabhängig vom History-Rewrite widerrufen oder erneuert werden. Warum statische Deploy-Keys dafür ohnehin ein Ablaufdatum brauchen, steht in einem eigenen Beitrag. History-Rewrite ersetzt die Rotation nicht, er räumt nur den sichtbarsten Teil des Problems auf.
Das Leistungsspektrum ist gestaffelt und baut auf Delivery-Transparenz auf. Es läuft über drei Felder: Implementation (Delivery- und Nachweiskontrollen in die Pipeline einziehen), AI Governance (Nachvollziehbarkeit dort schaffen, wo KI-gestützte Schritte in Build und Deployment Freigaben und Verantwortung verwischen) und Maintenance (die Übergabefrage: wem ein System nach dem Go-Live gehört und woran man merkt, dass es niemandem gehört). Ein festes Angebot daraus entsteht erst, wenn klar ist, was du wirklich brauchst. Kontakt: mm@mhm-dl.de.
Über das Anmeldeformular für den Newsletter trägst du deine E-Mail-Adresse ein, kreuzt die Einwilligung an und bestätigst die Anmeldung danach per Mail. Mit der Anmeldung willigst du ein, dass der Newsletter unter anderem folgende Themen behandelt: Schulungen zu Docker, Kubernetes, CI/CD, Git-Workflows und DevSecOps-Werkzeugen sowie die Anforderungen aus NIS2, CRA und DORA und deren technische Umsetzung.
Kommentar verfassen