Automatischer Push: 4 Datei(en) geändert

- .claude/skills/plane-abgleich/SKILL.md
- .vault-tools/geheimnis-scan/LIESMICH.md
- .vault-tools/geheimnis-scan/datei_scan.py
- .vault-tools/geheimnis-scan/push_scan.py

Erzeugt vom PostToolUse-Hook .vault-tools/auto-push/auto_push.py.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
Pascal MichelandClaude Opus 5 committed 2026-10-16 04:00:01 +02:00
1 parent 0fcf9490ed
commit 5c997a23e2
4 files changed
+159 -2

No files matched your search

+50
View File
@@ -0,0 +1,50 @@
---
type: resource
title: "Geheimnis-Scan: Commits und Dateien vor dem Push durchsuchen"
status: active
summary: "Zwei Scanner suchen in ausgehenden Commits oder in einer Textdatei nach Zugangsdaten, Firmennamen, privaten IPs und Mailadressen und zeigen Geheimnis-Werte nur als Länge."
tags: [vault-tools, veroeffentlichung]
created: 2026-10-15
updated: 2026-10-15
---
# Geheimnis-Scan
Für den Blick vor einem Push oder bevor eine Datei ein Repo verlässt. Werte, die wie Geheimnisse aussehen, stehen in der Ausgabe nur als Länge.
| Skript | Durchsucht | Aufruf |
|---|---|---|
| `push_scan.py` | hinzugefügte Zeilen eines Commit-Bereichs | `python -I -X utf8 push_scan.py <repo> <bereich>` |
| `datei_scan.py` | eine Textdatei, etwa ein Transkript oder Export | `python -I -X utf8 datei_scan.py <datei>` |
## Benutzen
```bash
# Ausgehende Commits eines Zweigs, der auf Gitea schon existiert
python -I -X utf8 push_scan.py "D:/Claude/Code Projekte/active - X" origin/main..main
# Neuer Zweig, den Gitea noch nicht kennt
python -I -X utf8 push_scan.py "D:/Claude/Code Projekte/active - X" feature/neu --not --remotes=origin
# Eine Datei vor dem Löschen oder Veröffentlichen
python -I -X utf8 datei_scan.py "D:/pfad/transkript.txt"
```
- `[geheim] …: 0` je Muster heißt: kein Treffer dieser Art.
- `[zaehl] private_ipv4` und `[zaehl] firma` zeigen die Werte im Klartext. Liegen sie in Fixtures oder Plänen, ist das kein Befund; die Datei sagt die Fundstelle nicht, `git grep` danach schon.
- Einen bekannten Kundennamen suchst du getrennt mit `grep -c` und gibst ihn nie aus.
## Was die Skripte sehen und was nicht
- **Fangnetz, kein Nachweis.** Die Muster sind reguläre Ausdrücke für Zuweisungen (`password = "…"`), private Schlüsselblöcke, Token-Präfixe (`ghp_`, `glpat-`), URLs mit Zugangsdaten und Passwort-Hashes. Ein Geheimnis ohne diese Form, etwa ein nackter Schlüssel in einer eigenen Zeile, sehen sie nicht.
- **Firmennamen** nur mit Rechtsform (`GmbH`, `AG`, `KG`, `UG`, `e.V.`). Ein Name ohne Rechtsform fällt durch.
- **`push_scan.py`** liest nur hinzugefügte Zeilen; was ein früherer Commit schon veröffentlicht hat, sieht es nicht.
## Was belegt ist
- 2026-10-15, vor den Pushes der Gitea-Doku-Angleichung (Welle 0): `push_scan.py` über die ausgehenden Commits von elf Repos, 0 Treffer in den Geheimnis-Mustern; der IP-Zähler schlug an Test-Fixtures an (`10.20.x.x` in Wartungsprotokoll), zählt also.
- 2026-10-15: `datei_scan.py` über ein Claude-Transkript von 341 KB: kein Treffer in den Zuweisungen, neun lange Zeichenketten, davon vier aus signierten Download-Links; Firmennamen nur Platzhalter und Arbeitgeber.
- **Offen:** Die Geheimnis-Muster sind nie gegen ein echtes oder absichtlich eingebautes Geheimnis rot gesehen worden. Bis dahin gilt „0 Treffer“ nur als Hinweis.
## Referenzen
- `AI/Regelwerk/veroeffentlichung.md` (im Vault): was vor der Aufnahme einer Datei geprüft wird
- `AI/Regelwerk/pruefwerkzeuge.md` (im Vault): Regressionsnachweis für Prüfskripte
+43
View File
@@ -0,0 +1,43 @@
"""Zählt in einer Textdatei Muster für Zugangsdaten, Firmennamen, Adressen und Mailadressen.
Ersteller: Claude (Opus 5.5) · Erstellt: 2026-10-15 · Doku: LIESMICH.md
Gibt je Muster die Zahl der Treffer und die verschiedenen Treffer aus; Werte, die
wie Geheimnisse aussehen, nur als Länge. Aufruf: python -I -X utf8 datei_scan.py <datei>
"""
import re
import sys
from collections import Counter
from pathlib import Path
text = Path(sys.argv[1]).read_text(encoding="utf-8", errors="replace")
# Muster, deren Treffer nur gezählt und als Länge gezeigt werden
GEHEIM = {
"schluesselwort_mit_wert": r"(?i)\b(pass(?:wor[dt])?|kennwort|pwd|token|secret|api[_-]?key|client[_-]?secret|bearer)\b\s*[:=]\s*['\"]?([^\s'\",;]{6,})",
"private_key_block": r"-----BEGIN [A-Z ]*PRIVATE KEY-----",
"lange_hex_oder_base64": r"\b[A-Za-z0-9+/]{40,}={0,2}\b",
}
# Muster, deren Treffer im Klartext gezeigt werden (Analyse, nicht für Ablage)
OFFEN = {
"firma": r"\b[A-ZÄÖÜ][\w&.-]*(?:\s+[A-ZÄÖÜ][\w&.-]*){0,3}\s+(?:GmbH|AG|KG|SE|e\.V\.|UG|Inc\.?|Ltd\.?)\b",
"mail": r"\b[\w.+-]+@[\w-]+(?:\.[\w-]+)+\b",
"ipv4": r"\b(?:\d{1,3}\.){3}\d{1,3}\b",
"guid": r"\b[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\b",
"domain": r"\bhttps?://([\w.-]+)",
"unc_pfad": r"\\\\[\w.-]+\\[\w$.-]+",
}
print(f"Datei: {len(text)} Zeichen, {text.count(chr(10))} Zeilen")
for name, muster in GEHEIM.items():
treffer = re.findall(muster, text)
werte = [t[-1] if isinstance(t, tuple) else t for t in treffer]
print(f"[geheim] {name}: {len(werte)} Treffer, Längen {sorted(Counter(len(w) for w in werte).items())[:15]}")
if name == "schluesselwort_mit_wert":
for schluessel, n in Counter(t[0].lower() for t in treffer).most_common():
print(f" Schlüssel {schluessel!r}: {n}")
for name, muster in OFFEN.items():
zaehl = Counter(re.findall(muster, text))
print(f"[offen] {name}: {sum(zaehl.values())} Treffer, {len(zaehl)} verschieden")
for wert, n in zaehl.most_common(40):
print(f" {n:4d} {wert}")
+62
View File
@@ -0,0 +1,62 @@
"""Durchsucht die ausgehenden Commits eines Repos (Bereich a..b) nach Geheimnis-Mustern.
Ersteller: Claude (Opus 5.5) · Erstellt: 2026-10-15 · Doku: LIESMICH.md
Gezeigt werden nur hinzugefügte Zeilen. Werte, die wie Geheimnisse aussehen, nur als
Länge; dazu Datei und Muster. Aufruf: python -I -X utf8 push_scan.py <repo> <bereich>
Bereich ohne Basis (neuer Zweig auf dem Remote): "<zweig> --not --remotes=origin".
"""
import re
import subprocess
import sys
from collections import Counter, defaultdict
repo, bereich = sys.argv[1], sys.argv[2:]
log = subprocess.run(
["git", "-C", repo, "log", "-p", "--no-color", "--format=COMMIT %h", *bereich],
capture_output=True, text=True, encoding="utf-8", errors="replace", check=True,
).stdout
# Muster mit Wert: nur Länge ausgeben
GEHEIM = {
"zuweisung": re.compile(r"(?i)\b(pass(?:wor[dt])?|kennwort|pwd|token|secret|api[_-]?key|client[_-]?secret|private[_-]?key)\b[\"']?\s*[:=]\s*[\"']([^\"'\s$<{]{8,})[\"']"),
"privater_schluessel": re.compile(r"-----BEGIN [A-Z ]*PRIVATE KEY-----"),
"gitea_github_token": re.compile(r"\b(?:ghp_[A-Za-z0-9]{30,}|gh[ousr]_[A-Za-z0-9]{30,}|glpat-[A-Za-z0-9_-]{20,})\b"),
"url_mit_zugang": re.compile(r"https?://[^/\s:@]+:[^/\s@]+@"),
"hash_shadow": re.compile(r"\$(?:6|5|y|2[aby])\$[^\s:\"']{20,}"),
}
# Muster ohne Geheimnis: Zählung je Wert
ZAEHLEN = {
"private_ipv4": re.compile(r"\b(?:10\.\d{1,3}|192\.168|172\.(?:1[6-9]|2\d|3[01]))\.\d{1,3}\.\d{1,3}\b"),
"firma": re.compile(r"\b[A-ZÄÖÜ][\w&.-]+(?:\s+[A-ZÄÖÜ][\w&.-]+){0,2}\s+(?:GmbH|AG|KG|UG|e\.V\.)\b"),
}
datei = "?"
commits = 0
geheim = defaultdict(list)
zaehl = {k: Counter() for k in ZAEHLEN}
for zeile in log.splitlines():
if zeile.startswith("COMMIT "):
commits += 1
continue
if zeile.startswith("+++ b/"):
datei = zeile[6:]
continue
if not zeile.startswith("+") or zeile.startswith("+++"):
continue
for name, muster in GEHEIM.items():
for m in muster.finditer(zeile):
wert = m.groups()[-1] if m.groups() else m.group(0)
geheim[name].append((datei, len(wert)))
for name, muster in ZAEHLEN.items():
for m in muster.finditer(zeile):
zaehl[name][m.group(0)] += 1
print(f"Commits im Bereich: {commits}")
for name in GEHEIM:
treffer = geheim.get(name, [])
print(f"[geheim] {name}: {len(treffer)}")
for d, laenge in sorted(set(treffer))[:15]:
print(f" {d} (Wertlänge {laenge})")
for name, c in zaehl.items():
print(f"[zaehl] {name}: {sum(c.values())} Treffer, {len(c)} verschieden: " + ", ".join(f"{w} ×{n}" for w, n in c.most_common(12)))