<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="de"><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://ckoys.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://ckoys.github.io/" rel="alternate" type="text/html" hreflang="de" /><updated>2026-10-02T11:38:07+00:00</updated><id>https://ckoys.github.io/feed.xml</id><title type="html">ckoys</title><subtitle>Notizen zu Security, Tools und dem, was beim Bauen schiefgeht.</subtitle><entry><title type="html">Persönliche Daten wirklich aus einem GitHub-Repo entfernen</title><link href="https://ckoys.github.io/20261002/github-daten-entfernen" rel="alternate" type="text/html" title="Persönliche Daten wirklich aus einem GitHub-Repo entfernen" /><published>2026-10-02T10:00:00+00:00</published><updated>2026-10-02T10:00:00+00:00</updated><id>https://ckoys.github.io/20261002/github-daten-entfernen</id><content type="html" xml:base="https://ckoys.github.io/20261002/github-daten-entfernen"><![CDATA[<p>Man muss es sich mal auf der Zunge zergehen lassen: Da arbeitet man beruflich in der IT-Security, predigt Datensparsamkeit, und auf dem eigenen, längst vergessenen Blog steht seit 2021 ein Impressum mit Wohnadresse und Handynummer. Öffentlich. Für jeden. Das Repo dahinter natürlich auch öffentlich, die Daten steckten also nicht nur in der Seite, sondern brav in jedem Commit seit dem ersten Tag.</p>

<p>Datei löschen, fertig? Ach bitte. Hier der Weg, der tatsächlich funktioniert, inklusive der zwei Stellen, an denen ich zielsicher falsch abgebogen bin.</p>

<h2 id="warum-git-rm-nichts-bringt">Warum <code class="language-plaintext highlighter-rouge">git rm</code> nichts bringt</h2>

<p>Ein Commit, der die Datei löscht, entfernt sie aus dem aktuellen Stand. Schön. Jeder ältere Commit enthält sie trotzdem weiterhin, und jeder kann ihn auf GitHub aufrufen. Git vergisst nämlich nichts, das ist ja gerade der Witz an Git. Bei einem Jekyll-Blog kommt hinzu, dass oft auch der gebaute <code class="language-plaintext highlighter-rouge">_site/</code>-Ordner mit eingecheckt ist. Die Daten liegen dann zusätzlich als HTML im Feed, in der Sitemap und in der gerenderten Seite.</p>

<p>Bei mir standen sie an rund 70 Stellen der History, verteilt über Markdown, HTML und <code class="language-plaintext highlighter-rouge">feed.xml</code>. Gründlich war ich damals, das muss man mir lassen. Nur leider an der falschen Stelle.</p>

<h2 id="schritt-1-aktuellen-stand-bereinigen">Schritt 1: aktuellen Stand bereinigen</h2>

<p>Zuerst ein normaler Commit, der die Datei entfernt und alles aufräumt, was nicht ins Repo gehört (<code class="language-plaintext highlighter-rouge">_site/</code>, Caches, <code class="language-plaintext highlighter-rouge">.DS_Store</code>). So ist die Seite schon sauber, bevor die History umgeschrieben wird.</p>

<p>Vorher ein Backup:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone <span class="nt">--mirror</span> <span class="nb">.</span> ../repo-backup.git
</code></pre></div></div>

<h2 id="schritt-2-history-mit-git-filter-repo-umschreiben">Schritt 2: History mit git-filter-repo umschreiben</h2>

<p><a href="https://github.com/newren/git-filter-repo">git-filter-repo</a> ist das Werkzeug, das GitHub selbst empfiehlt. <code class="language-plaintext highlighter-rouge">filter-branch</code> und BFG darf man getrost in Rente schicken.</p>

<p>Zwei Dinge auf einmal: Pfade komplett entfernen und Textstellen in allen übrigen Dateien ersetzen. Die Ersetzungen kommen in eine Datei, eine Regel pro Zeile:</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Musterstraße 1==&gt;[entfernt]
+49 151-00000000==&gt;[entfernt]
12345 Musterstadt==&gt;[entfernt]
</code></pre></div></div>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git filter-repo <span class="nt">--force</span> <span class="se">\</span>
  <span class="nt">--invert-paths</span> <span class="se">\</span>
  <span class="nt">--path</span> _posts/2021-01-01-Impressum.md <span class="se">\</span>
  <span class="nt">--path</span> _site/20210101/ <span class="se">\</span>
  <span class="nt">--replace-text</span> ../replacements.txt
</code></pre></div></div>

<p>Wichtig: Die Regeln greifen auf jede Datei, auch auf Binärdateien. Deshalb vorher prüfen, in welchem Kontext ein Muster vorkommt (<code class="language-plaintext highlighter-rouge">git log --all -p | rg 'muster'</code>), und lieber lange, eindeutige Muster nehmen als eine nackte Postleitzahl.</p>

<p><code class="language-plaintext highlighter-rouge">filter-repo</code> entfernt danach absichtlich das Remote <code class="language-plaintext highlighter-rouge">origin</code>, damit man nicht aus Versehen pusht. Sehr fürsorglich. Also neu eintragen und ganz bewusst force-pushen:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git remote add origin https://github.com/USER/REPO.git
git push <span class="nt">--force</span> <span class="nt">-u</span> origin main
</code></pre></div></div>

<p>Prüfen:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git log <span class="nt">--all</span> <span class="nt">-p</span> | rg <span class="nt">-c</span> <span class="s1">'Musterstraße|00000000'</span> <span class="o">||</span> <span class="nb">echo </span>sauber
</code></pre></div></div>

<h2 id="schritt-3-github-selbst-aufräumen-lassen">Schritt 3: GitHub selbst aufräumen lassen</h2>

<p>Hier dachte ich, ich wäre fertig. Natürlich nicht. Der alte Commit war nach dem Force-Push weiterhin über seine SHA abrufbar:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gh api repos/USER/REPO/commits/&lt;alte-sha&gt;
</code></pre></div></div>

<p>Liefert fröhlich den alten Commit zurück. GitHub hält unerreichbare Commits und gecachte Ansichten vor, bis jemand sie aktiv entfernt. Und dieser Jemand ist nicht man selbst, sondern der Support.</p>

<p><strong>Falle Nummer eins:</strong> Ich habe zuerst das Formular für „Private Information“ geöffnet. Klingt ja passend. Ist es aber nicht: Das ist für den Fall gedacht, dass <em>jemand anderes</em> deine Daten veröffentlicht hat. Sich selbst zu melden ist zwar eine originelle Idee, führt aber nirgendwohin.</p>

<p>Der richtige Weg steht in der GitHub-Doku <a href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/removing-sensitive-data-from-a-repository">Removing sensitive data from a repository</a>, Abschnitt „Fully removing the data from GitHub“:</p>

<ol>
  <li><a href="https://support.github.com">support.github.com</a> öffnen, Konto wählen, Kategorie <strong>Repositorys</strong>.</li>
  <li>Ticket mit diesen Angaben:
    <ul>
      <li>Owner und Repo-Name</li>
      <li>Anzahl betroffener Pull Requests</li>
      <li>die <strong>First Changed Commits</strong> aus <code class="language-plaintext highlighter-rouge">filter-repo</code></li>
    </ul>
  </li>
</ol>

<p><strong>Falle Nummer zwei:</strong> Die First Changed Commits, die der Support haben will, standen bei mir nicht in der Terminal-Ausgabe. Wo sind sie dann? In einer Datei, wo sonst:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cat</span> .git/filter-repo/first-changed-commits
</code></pre></div></div>

<p>Und als Bonus ein Stolperstein im Formular selbst: Nach dem ersten „Weiter“ zeigt GitHub eine Seite mit Lösungsvorschlägen aus dem Community-Forum. Wer dort denkt, das Ticket sei raus, irrt (ich spreche aus Erfahrung). Erst „Weiter zur Ticketerstellung“, dann „Type of Issue“ wählen, dann „Submit“. Dann ist es raus. Wirklich.</p>

<h2 id="schritt-4-was-außerhalb-von-github-liegt">Schritt 4: was außerhalb von GitHub liegt</h2>

<ul>
  <li><strong>Forks:</strong> Wer das Repo geforkt hat, hat die Daten. Bei mir: 0 Forks. Manchmal ist es eben ein Segen, dass sich niemand für den eigenen Blog interessiert.</li>
  <li><strong>Wayback Machine:</strong> Über <code class="language-plaintext highlighter-rouge">https://web.archive.org/cdx/search/cdx?url=domain.tld*</code> sieht man, welche Seiten archiviert sind. Bei mir nur Snapshots von 2020, das Impressum war nie drin.</li>
  <li><strong>Lokale Kopien:</strong> Das Backup aus Schritt 1 und die Datei mit den Ersetzungsregeln enthalten die Daten im Klartext. Beides löschen, sobald alles verifiziert ist.</li>
</ul>

<h2 id="was-ich-daraus-mitnehme">Was ich daraus mitnehme</h2>

<p>Persönliche Daten gehören nicht in ein Git-Repo. Auch nicht in ein „privates Spielprojekt“, das ganz zufällig öffentlich ist. Wer ein Impressum braucht, verlinkt besser auf eine zentrale Seite, die man an genau einer Stelle pflegt.</p>

<p>Und der Force-Push ist nicht das Ende der Bereinigung, sondern ungefähr die Mitte. Wer das für übertrieben hält, darf gern mal <code class="language-plaintext highlighter-rouge">gh api</code> auf die eigene alte SHA loslassen. Viel Spaß.</p>]]></content><author><name></name></author><category term="Security" /><category term="Git" /><summary type="html"><![CDATA[Man muss es sich mal auf der Zunge zergehen lassen: Da arbeitet man beruflich in der IT-Security, predigt Datensparsamkeit, und auf dem eigenen, längst vergessenen Blog steht seit 2021 ein Impressum mit Wohnadresse und Handynummer. Öffentlich. Für jeden. Das Repo dahinter natürlich auch öffentlich, die Daten steckten also nicht nur in der Seite, sondern brav in jedem Commit seit dem ersten Tag.]]></summary></entry></feed>