<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Claude on Kabelbruch</title><link>https://kabelbruch.de/tags/claude/</link><description>Recent content in Claude on Kabelbruch</description><image><title>Kabelbruch</title><url>https://kabelbruch.de/og-image.png</url><link>https://kabelbruch.de/og-image.png</link></image><generator>Hugo</generator><language>de-DE</language><lastBuildDate>Sun, 01 Nov 2026 22:15:00 +0100</lastBuildDate><atom:link href="https://kabelbruch.de/tags/claude/index.xml" rel="self" type="application/rss+xml"/><item><title>Mein Secret-Scanner hat angeschlagen – aus dem falschen Grund</title><link>https://kabelbruch.de/posts/secret-scanner-ki-assistent-passwort/</link><pubDate>Sun, 01 Nov 2026 22:15:00 +0100</pubDate><guid>https://kabelbruch.de/posts/secret-scanner-ki-assistent-passwort/</guid><description>Mein Secret-Scanner stoppte das Home-Assistant-Backup wegen eines Fehlalarms. Dahinter lag ein echtes Passwort im Protokoll meines KI-Assistenten.</description><content:encoded><![CDATA[<p>Nachts um drei blockiert mein Backup-Skript einen Commit: mögliche Zugangsdaten im Klartext. Der Treffer war ein Fehlalarm – aber beim Nachsehen kam ein echtes Passwort zum Vorschein, das der Scanner seit über zwei Wochen übersehen hatte. Abgelegt hatte es mein KI-Assistent.</p>
<h2 id="worum-gehts">Worum geht&rsquo;s?</h2>
<p>Meine Home-Assistant-Konfiguration landet jede Nacht um 03:00 Uhr als Commit in einem privaten Repository auf meinem eigenen Gitea. Das ist praktisch: Ich sehe jede Änderung, kann zurückspringen und habe neben den normalen Backups noch eine lesbare Historie.</p>
<p>Weil in so einer Konfiguration schnell mal ein Passwort landet, hat das Backup-Skript ein kleines <strong>Sicherheitsnetz</strong>: Bevor es committet, sucht es in den neuen Zeilen nach Dingen, die wie Zugangsdaten aussehen. Findet es etwas, bricht es ab und schickt mir eine Push-Nachricht. So ein Werkzeug nennt man <strong>Secret-Scanner</strong> – „Secrets“ sind alles, was nicht in fremde Hände gehört: Passwörter, Tokens, API-Schlüssel.</p>
<p>Am 1. November hat es zum ersten Mal ernsthaft angeschlagen. Und die Geschichte dahinter ist lehrreicher, als ich dachte – über meinen Scanner, über Logdateien und über KI-Assistenten im Homelab.</p>
<h2 id="voraussetzungen">Voraussetzungen</h2>
<ul>
<li><strong>Home Assistant OS</strong> 18.4 mit Home Assistant Core 2026.10.4</li>
<li><strong>Gitea</strong> im Homelab, privates Repository für die HA-Konfiguration</li>
<li><strong>App „Home Assistant MCP Server“</strong> (ha-mcp) 9.0.0 – damit kann ein KI-Assistent Home Assistant bedienen und konfigurieren</li>
<li><strong>Claude</strong> als KI-Assistent</li>
<li>Beteiligt am eigentlichen Fund: die App <strong>Ring-MQTT with Video Streaming</strong> und <strong>Frigate</strong> 0.18.0 als Videoaufzeichnung</li>
</ul>
<p>Kurz zu <strong>MCP</strong>: Das <em>Model Context Protocol</em> ist eine Schnittstelle, über die ein KI-Assistent Werkzeuge benutzen kann. Bei mir heißt das: Claude kann über den MCP-Server in Home Assistant Automationen anlegen, Entitäten umbenennen, Apps konfigurieren und vieles mehr. Das spart mir viel Klickerei – und ist der Grund, warum es diesen Artikel gibt.</p>
<h2 id="das-backup-mit-sicherheitsnetz">Das Backup mit Sicherheitsnetz</h2>
<p>Die Automation „Konfiguration nach Gitea sichern“ ruft um 03:00 Uhr einen <code>shell_command</code> auf und wertet dessen Rückgabewert aus. Der Eintrag in der <code>configuration.yaml</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">shell_command</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">git_autocommit</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;sh /config/shell/git_autocommit.sh&#34;</span><span class="w">
</span></span></span></code></pre></div><p>Und das Skript selbst:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="cp">#!/bin/sh
</span></span></span><span class="line"><span class="cl"><span class="c1"># Auto-Commit der HA-Konfiguration nach Gitea – mit Klartext-Secret-Prüfung</span>
</span></span><span class="line"><span class="cl"><span class="nv">LOG</span><span class="o">=</span>/config/.git_autocommit.log
</span></span><span class="line"><span class="cl"><span class="nv">PATTERN</span><span class="o">=</span><span class="s1">&#39;(password|passwort|token|api_?key|secret|network_key|client_id)[^:]*:&#39;</span>
</span></span><span class="line"><span class="cl"><span class="nv">EXCL</span><span class="o">=</span><span class="s1">&#39;:(exclude)shell/git_autocommit.sh&#39;</span>
</span></span><span class="line"><span class="cl"><span class="o">{</span>
</span></span><span class="line"><span class="cl">  <span class="nb">echo</span> <span class="s2">&#34;=== </span><span class="k">$(</span>date <span class="s1">&#39;+%Y-%m-%d %H:%M:%S&#39;</span><span class="k">)</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="nb">cd</span> /config <span class="o">||</span> <span class="nb">exit</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl">  git add -A
</span></span><span class="line"><span class="cl">  <span class="k">if</span> git diff --cached --quiet<span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">    <span class="nb">echo</span> <span class="s2">&#34;Keine Änderungen.&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="nb">exit</span> <span class="m">0</span>
</span></span><span class="line"><span class="cl">  <span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Sicherheitsnetz: neu hinzugefügte Zeilen mit Zugangsdaten im Klartext?</span>
</span></span><span class="line"><span class="cl">  <span class="nv">LEAKS</span><span class="o">=</span><span class="k">$(</span>git diff --cached -U0 -- . <span class="s2">&#34;</span><span class="nv">$EXCL</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">    <span class="p">|</span> grep -E <span class="s1">&#39;^\+&#39;</span> <span class="p">|</span> grep -vE <span class="s1">&#39;^\+\+\+&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">    <span class="p">|</span> grep -iE <span class="s2">&#34;</span><span class="nv">$PATTERN</span><span class="s2">&#34;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl">    <span class="p">|</span> grep -vE <span class="s1">&#39;!secret|REDACTED&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="o">[</span> -n <span class="s2">&#34;</span><span class="nv">$LEAKS</span><span class="s2">&#34;</span> <span class="o">]</span><span class="p">;</span> <span class="k">then</span>
</span></span><span class="line"><span class="cl">    <span class="nb">echo</span> <span class="s2">&#34;ABBRUCH: mögliche Zugangsdaten im Klartext gefunden in:&#34;</span>
</span></span><span class="line"><span class="cl">    git diff --cached --name-only -i -G <span class="s2">&#34;</span><span class="nv">$PATTERN</span><span class="s2">&#34;</span> -- . <span class="s2">&#34;</span><span class="nv">$EXCL</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">    git reset -q
</span></span><span class="line"><span class="cl">    <span class="nb">exit</span> <span class="m">2</span>
</span></span><span class="line"><span class="cl">  <span class="k">fi</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  git commit -q -m <span class="s2">&#34;Auto-Commit </span><span class="k">$(</span>date <span class="s1">&#39;+%Y-%m-%d %H:%M&#39;</span><span class="k">)</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">  git push -q <span class="o">&amp;&amp;</span> <span class="nb">echo</span> <span class="s2">&#34;Push OK.&#34;</span>
</span></span><span class="line"><span class="cl"><span class="o">}</span> &gt;&gt; <span class="s2">&#34;</span><span class="nv">$LOG</span><span class="s2">&#34;</span> 2&gt;<span class="p">&amp;</span><span class="m">1</span>
</span></span></code></pre></div><p>Die Idee in Kurzform:</p>
<ul>
<li><code>git add -A</code> nimmt alle Änderungen auf.</li>
<li><code>git diff --cached -U0</code> zeigt nur die geänderten Zeilen, ohne Kontext. Davon interessieren mich nur die <strong>neu hinzugefügten</strong> (sie beginnen mit <code>+</code>).</li>
<li>Darin sucht das Skript nach <strong>Schlüsselnamen</strong> wie <code>password</code>, <code>token</code> oder <code>api_key</code>, gefolgt von einem Doppelpunkt – egal ob in YAML (<code>password: geheim</code>) oder JSON (<code>&quot;api_key&quot;: &quot;…&quot;</code>).</li>
<li>Zeilen mit <code>!secret</code> sind ausgenommen, denn so verweist Home Assistant korrekt auf die <code>secrets.yaml</code>.</li>
<li>Bei einem Treffer: alles zurücksetzen, Exit-Code 2. Die Automation macht daraus eine Push-Nachricht aufs Handy und eine Meldung in Home Assistant.</li>
</ul>
<p>Seit Anfang Oktober lief das unauffällig jede Nacht durch.</p>
<h2 id="0300-uhr-commit-blockiert">03:00 Uhr: „Commit blockiert“</h2>
<p>Am 1. November um 03:00 Uhr kam diese Meldung aufs Handy:</p>
<blockquote>
<p>Gitea-Backup fehlgeschlagen – Commit blockiert: mögliche Zugangsdaten im Klartext gefunden.</p>
</blockquote>
<p>Laut Log betraf es eine einzige Datei: <code>.ha_mcp/logs/mcp_usage.jsonl</code>.</p>
<p>Dahinter steckt das <strong>Nutzungsprotokoll des MCP-Servers</strong>. Bei meiner Installation schreibt er jeden Aufruf eines Werkzeugs als JSON-Zeile in diese Datei – mit Zeitstempel, Werkzeugname und <strong>allen Parametern</strong>. Die Datei liegt unter <code>/config</code>, also genau in dem Verzeichnis, das mein Backup nach Gitea schiebt. Sie war schon in 16 Commits gelandet und mit nach Gitea gepusht worden.</p>
<h2 id="die-nachschau-nur-die-namen-nicht-die-werte">Die Nachschau: nur die Namen, nicht die Werte</h2>
<p>Die naheliegende Reaktion wäre, die Datei zu öffnen und durchzulesen. Ich bin einen anderen Weg gegangen: Wenn darin Zugangsdaten stehen, sollen die nicht auch noch in einem Terminal-Verlauf oder einem Chat landen.</p>
<p>Deshalb habe ich nur die <strong>Namen</strong> der Parameter auswerten lassen, also welches Werkzeug mit welchem Feld aufgerufen wurde, und dann geschaut, welche Namen nach Zugangsdaten klingen. Ergebnis:</p>
<ul>
<li><strong><code>passive</code></strong>, rund 80-mal: ein ganz normales Feld bei Zonen. Enthält zufällig „pass“.</li>
<li><strong><code>confirm_token</code></strong>: eine Bestätigungsmarke, die der MCP-Server bei bestimmten Änderungen verlangt. Kein Zugangsdatum.</li>
<li>Ein paar Treffer in Beschreibungen und Suchbegriffen, in denen eines der Stichworte als normales Wort vorkam.</li>
<li>Und <strong>ein echter Fund:</strong> <code>ha_manage_app</code> mit dem Feld <strong><code>options.livestream_pass</code></strong> vom <strong>14. Oktober</strong>.</li>
</ul>
<p>Zwei Treffer ließen sich am Namen allein nicht beurteilen: ein Dienstaufruf mit freiem Inhalt (<code>data.payload</code>) und ein roher WebSocket-Befehl (<code>ws_command</code>). Die habe ich mir selbst im Terminal angesehen, ohne sie irgendwo hineinzukopieren. Beide waren harmlos.</p>
<h2 id="wie-das-passwort-ins-protokoll-kam">Wie das Passwort ins Protokoll kam</h2>
<p>Am 14. Oktober habe ich meine Ring-Klingel zusätzlich in Frigate eingebunden, damit bei Bewegung oder Klingeln eine Aufnahme startet. Dafür bietet die App Ring-MQTT ein <strong>RTSP-Gateway</strong> an: Es stellt den Kamerastream im Heimnetz bereit, geschützt mit Benutzername und Passwort. RTSP ist das Protokoll, mit dem Kameras ihr Bild übertragen.</p>
<p>Eingerichtet hat das <strong>Claude über MCP</strong>. Das Werkzeug <code>ha_manage_app</code> hat die Optionen der App gesetzt: den Benutzer <code>frigate</code> – und das Passwort im Feld <code>livestream_pass</code>. Funktioniert hat das tadellos. Nur hat der MCP-Server den Aufruf eben vollständig protokolliert, inklusive Passwort im Klartext. Und von dort ist es mit dem nächsten nächtlichen Backup nach Gitea gewandert.</p>
<p>Der MCP-Server hat dabei nichts falsch gemacht. Er protokolliert, was man ihm gibt. Der Fehler lag darin, ein Passwort überhaupt durch den KI-Assistenten zu schleusen.</p>
<h2 id="der-eigentliche-knackpunkt-mein-scanner-hat-es-nie-gesehen">Der eigentliche Knackpunkt: Mein Scanner hat es nie gesehen</h2>
<p>Jetzt wird es interessant. Das Passwort stand seit dem <strong>14. Oktober</strong> in der Datei, und die Datei ging regelmäßig durch meinen Scanner. Angeschlagen hat er aber erst am <strong>1. November</strong>. Warum?</p>
<p>Ein Blick auf das Suchmuster beantwortet das:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="nv">PATTERN</span><span class="o">=</span><span class="s1">&#39;(password|passwort|token|api_?key|secret|network_key|client_id)[^:]*:&#39;</span>
</span></span></code></pre></div><p>Das Feld hieß <code>livestream_pass</code>. Darin steckt weder <code>password</code> noch <code>token</code> noch <code>secret</code> – nur <code>pass</code>. Die Zeile ist also seit Mitte Oktober jede Nacht anstandslos durchgerutscht.</p>
<p>Angeschlagen hat der Scanner am 1. November wegen <strong>einer anderen Zeile</strong>, die in dieser Nacht neu dazukam. Welche genau, kann ich nicht mehr sagen – mein Log speichert nur die Dateinamen. Wahrscheinlich war es einer der Fehlalarme wie <code>confirm_token</code>, der auf das Muster <code>token</code> passt.</p>
<p>Mit anderen Worten: <strong>Mein Secret-Scanner hat angeschlagen – aber aus dem falschen Grund.</strong> Das echte Passwort habe ich nur gefunden, weil ein Fehlalarm mich dazu gebracht hat, genauer hinzuschauen.</p>
<h2 id="aufräumen">Aufräumen</h2>
<h3 id="passwort-ändern-statt-historie-umschreiben">Passwort ändern statt Historie umschreiben</h3>
<p>Das Passwort steckt jetzt in der Git-Historie. Dafür gibt es zwei Wege:</p>
<ol>
<li><strong>Die Historie umschreiben</strong>, zum Beispiel mit <code>git filter-repo</code>, und per Force-Push nach Gitea schieben. Das ist aufwendig, fehleranfällig und hilft nur dann wirklich, wenn auch alle anderen Kopien des Repos bereinigt werden.</li>
<li><strong>Das Passwort ändern.</strong> Dann steht in der Historie nur noch ein wertloser alter Wert.</li>
</ol>
<p>Ich habe mich für Weg 2 entschieden. Das Repository ist privat und liegt auf meinem eigenen Gitea. Das läuft nur im Homelab und ist aus dem Internet nicht erreichbar. Und ein geändertes Passwort ist die einzige Maßnahme, die sicher wirkt, egal wo das alte noch herumliegt. Neu gesetzt habe ich es in Ring-MQTT und in der Konfiguration von Frigate, danach Frigate neu gestartet.</p>
<h3 id="das-protokoll-aus-dem-backup-nehmen">Das Protokoll aus dem Backup nehmen</h3>
<p>Das MCP-Protokoll hat im Backup nichts verloren. Es ist Laufzeitdatum, keine Konfiguration. Also kommt der Ordner in die <code>.gitignore</code>:</p>
<pre tabindex="0"><code class="language-gitignore" data-lang="gitignore"># HA-MCP-Server: Laufzeitdaten und Nutzungsprotokoll (Inhalte der Tool-Aufrufe)
.ha_mcp/
</code></pre><p>Weil Git die Dateien schon kennt, reicht das allein nicht. Sie müssen zusätzlich aus dem Index entfernt werden – die Dateien selbst bleiben dabei in Home Assistant liegen, Git verfolgt sie nur nicht mehr:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">cd</span> /config
</span></span><span class="line"><span class="cl">git rm -r --cached .ha_mcp
</span></span></code></pre></div><p>Den Commit dazu macht die Automation beim nächsten Lauf selbst. Ich habe sie von Hand angestoßen.</p>
<h3 id="prüfen-ob-es-wirklich-durchgelaufen-ist">Prüfen, ob es wirklich durchgelaufen ist</h3>
<p>Hier bin ich noch einmal kurz gestolpert. Bei Erfolg meldet sich die Automation nämlich <strong>gar nicht</strong> – keine Push-Nachricht, nichts. Im Trace der Automation stand Rückgabewert <code>0</code> und dass der Fehlerzweig nicht ausgeführt wurde. Das heißt aber nur: Das Skript ist fehlerfrei durchgelaufen. Ob es dabei tatsächlich etwas committet hat, steht da nicht.</p>
<p>Der echte Beleg ist der Commit in Gitea:</p>
<p><img alt="Der Commit in Gitea: .gitignore ergänzt, die Dateien unter .ha_mcp entfernt" loading="lazy" src="/posts/secret-scanner-ki-assistent-passwort/gitea-commit.png"></p>
<p>Drei Dateien: die <code>.gitignore</code> mit dem neuen Eintrag und die beiden Dateien unter <code>.ha_mcp/</code>, die nicht mehr verfolgt werden. Den Diff der Protokolldatei habe ich übrigens nicht aufgeklappt – der hätte den alten Inhalt samt Passwort angezeigt.</p>
<h3 id="die-neue-regel">Die neue Regel</h3>
<p>Die wichtigste Änderung ist keine Konfiguration, sondern eine Regel für mich:</p>
<p><strong>Zugangsdaten gebe ich nicht mehr an den KI-Assistenten weiter.</strong> Wenn eine App oder Integration ein Passwort braucht, richtet der Assistent alles andere ein – und das Passwort trage ich selbst in der Oberfläche von Home Assistant ein.</p>
<h2 id="stolperfallen--learnings">Stolperfallen &amp; Learnings</h2>
<p><strong>Eine Liste von Schlüsselwörtern findet nur, woran man vorher gedacht hat.</strong> <code>password</code> ja, <code>pass</code> nein. <code>pwd</code>, <code>psk</code>, Zugangsdaten in URLs nach dem Muster <code>benutzer:passwort@host</code> oder ein Token ohne verräterischen Feldnamen rutschen genauso durch. Ein selbstgebauter Scanner ist besser als keiner – aber man sollte ihm nicht mehr zutrauen, als er kann.</p>
<p><strong>Logs speichern oft mehr, als man denkt.</strong> Ein Nutzungsprotokoll mit „allen Parametern“ ist für die Fehlersuche Gold wert. Es ist aber auch eine Sammelstelle für alles, was man je an das Werkzeug übergeben hat. Und es lag genau dort, wo mein Backup alles einsammelt.</p>
<p><strong>KI-Assistenten sind Werkzeuge mit Gedächtnis.</strong> Was ich einem Assistenten gebe, steht danach mindestens im Chat und je nach Werkzeug noch an weiteren Stellen. Bei Passwörtern sollte die Antwort deshalb immer lauten: Das mache ich selbst.</p>
<p><strong>Ein Fehlalarm ist kein Grund, das Sicherheitsnetz abzuschalten.</strong> Ich hätte das Muster auch schnell lockern oder die Datei einfach durchwinken können. Dann wäre das echte Passwort weiter unentdeckt geblieben.</p>
<p><strong>„Exit 0“ ist kein Beweis.</strong> Ein grüner Trace heißt nur, dass nichts schiefging. Ob das passiert ist, was passieren sollte, sieht man erst am Ergebnis – hier am Commit in Gitea.</p>
<h2 id="fazit">Fazit</h2>
<p>Mein Sicherheitsnetz hat funktioniert – nur nicht so, wie ich dachte. Den echten Fund hat es zweieinhalb Wochen übersehen und dann über einen Fehlalarm doch noch ans Licht gebracht. Das Passwort ist geändert, das Protokoll fliegt aus dem Backup, und ich habe eine neue Regel im Umgang mit meinem KI-Assistenten. Ein bisschen Glück war dabei. Beim nächsten Mal möchte ich mich darauf nicht verlassen müssen.</p>
<h2 id="was-als-nächstes-kommt">Was als Nächstes kommt</h2>
<ul>
<li><strong>Das Suchmuster verbessern:</strong> Kürzel wie <code>pass</code>, <code>pwd</code> und <code>psk</code> aufnehmen und Zugangsdaten in URLs erkennen. Oder gleich ein richtiges Werkzeug wie <code>gitleaks</code> einsetzen, das mit vielen Regeln und der Zufälligkeit von Zeichenketten arbeitet. Ob und wie das unter Home Assistant OS sinnvoll läuft, muss ich mir noch ansehen.</li>
<li><strong>Die Automation gesprächiger machen:</strong> Bei Erfolg soll sie die alte Fehlermeldung von selbst wegräumen. Bisher bleibt die stehen, bis ich sie wegklicke.</li>
<li><strong>Eine kleine Lücke im Skript schließen:</strong> Scheitert einmal der Push, bleibt der Commit lokal liegen. Gibt es am nächsten Tag keine neuen Änderungen, wird er nicht nachgeschoben.</li>
</ul>
]]></content:encoded></item></channel></rss>