feat: Laientauglichkeit — Auto-Modus, Klartext-Ausgabe, einfachere Doku
Ziel: ein einziger täglicher Befehl genügt, jede Meldung ist auch ohne IT-Wissen verständlich. Auto-Modus (ein Cron-Job genügt für alles): - data/state.json merkt sich, wann die Wochen-Prüfungen zuletzt liefen (baseline.py: load_state/save_state/is_check_due/mark_check_run) - `scan` führt fällige Zusatzprüfungen (Tarnung/externe Links/Dateien) automatisch mit aus; Gesamt-Ampel = schlechtestes Teilergebnis; EIN kombinierter Report + EIN Alert - neuer Config-Abschnitt periodic_checks (Default: wöchentlich, an) Klartext (scanner/plain.py): - Ampel 🟢/🟡/🔴 + Sätze ohne Fachbegriffe, aus den strukturierten Befunden - Report: Klartext oben, "Technische Details (für Ihren Dienstleister)" unten - Terminal-Ausgabe und E-Mail (alerter.py) ebenso umgestellt Komfort: - approve --all/--rebuild legt die Datei-Überwachung automatisch mit an (Opt-out: --skip-assets); approve-assets bleibt für den Sonderfall - status zeigt Ampel zuoberst + wann die Wochen-Prüfungen zuletzt liefen - Onboarding-Text in einfacher Sprache, wenn noch kein Vergleichsstand existiert Doku: README + BEDIENUNGSANLEITUNG mit "In 3 Schritten"-Einstieg. Tests: test_state.py, test_plain.py (Fachbegriff-Assertion), Auto-Asset- Baseline-Test. 165 Tests grün. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
54ff1a6060
commit
bed20db8bd
11 changed files with 856 additions and 202 deletions
179
scanner/plain.py
Normal file
179
scanner/plain.py
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
"""
|
||||
Klartext-Schicht: übersetzt technische Befunde in Sätze, die auch ein
|
||||
Website-Betreiber ohne IT-Wissen versteht.
|
||||
|
||||
Alle Texte hier sind bewusst laienverständlich. Fachbegriffe (Score, Canonical,
|
||||
JSON-LD, Meta-Refresh, Hash) gehören NICHT in dieses Modul — die technischen
|
||||
Details stehen weiterhin im Detail-Abschnitt des Reports.
|
||||
"""
|
||||
|
||||
# Ampel-Überschrift je Risiko-Level
|
||||
_AMPEL = {
|
||||
"green": ("🟢", "Alles in Ordnung."),
|
||||
"yellow": ("🟡", "Bitte einmal nachschauen."),
|
||||
"red": ("🔴", "Achtung — bitte zeitnah prüfen oder Ihren Dienstleister informieren."),
|
||||
}
|
||||
|
||||
|
||||
def ampel(level: str) -> tuple[str, str]:
|
||||
"""Symbol und Überschrift für ein Risiko-Level (green/yellow/red)."""
|
||||
return _AMPEL.get(level, ("⚪", "Status unbekannt."))
|
||||
|
||||
|
||||
def ampel_zeile(level: str) -> str:
|
||||
"""Einzeilige Ampel-Darstellung, z. B. '🟡 Bitte einmal nachschauen.'"""
|
||||
symbol, satz = ampel(level)
|
||||
return f"{symbol} {satz}"
|
||||
|
||||
|
||||
def _zaehle_seiten_befunde(diff: dict) -> dict:
|
||||
"""Zählt die Befund-Kategorien über alle geänderten Seiten zusammen."""
|
||||
z = {
|
||||
"hidden": 0, "meta_refresh": 0, "canonical": 0,
|
||||
"comment_links": 0, "scripts": 0, "grosser_text": 0,
|
||||
}
|
||||
for pd in diff.get("page_diffs", []):
|
||||
if pd.get("new_hidden_content"):
|
||||
z["hidden"] += 1
|
||||
if pd.get("new_meta_refresh"):
|
||||
z["meta_refresh"] += 1
|
||||
if pd.get("canonical_changed"):
|
||||
z["canonical"] += 1
|
||||
if pd.get("new_comment_links"):
|
||||
z["comment_links"] += 1
|
||||
if pd.get("new_inline_scripts"):
|
||||
z["scripts"] += 1
|
||||
if pd.get("added_chars", 0) > 200:
|
||||
z["grosser_text"] += 1
|
||||
return z
|
||||
|
||||
|
||||
def klartext_befunde(
|
||||
diff: dict | None = None,
|
||||
cloak_diff: dict | None = None,
|
||||
asset_diff: dict | None = None,
|
||||
ext_links: dict | None = None,
|
||||
) -> list[str]:
|
||||
"""
|
||||
Erzeugt eine Liste laienverständlicher Sätze aus den strukturierten Befunden.
|
||||
Leere Befunde → eine beruhigende Meldung.
|
||||
"""
|
||||
diff = diff or {}
|
||||
saetze: list[str] = []
|
||||
|
||||
# --- Eindeutige Angriffszeichen zuerst ---
|
||||
if diff.get("suspicious_filenames"):
|
||||
saetze.append(
|
||||
"Es wurde auf eine Datei mit verdächtigem Namen verlinkt, wie sie Angreifer "
|
||||
"für Schadprogramme verwenden. Das ist ein ernstes Warnzeichen."
|
||||
)
|
||||
|
||||
if cloak_diff:
|
||||
bot_links = sum(len(f.get("extra_links", [])) for f in cloak_diff.get("findings", []))
|
||||
bot_text = any(f.get("extra_text_chars", 0) > 100 for f in cloak_diff.get("findings", []))
|
||||
if bot_links:
|
||||
saetze.append(
|
||||
"Ihre Website zeigt der Google-Suchmaschine andere Links als normalen "
|
||||
"Besuchern. Das ist eine typische Methode von Spam-Angriffen."
|
||||
)
|
||||
elif bot_text:
|
||||
saetze.append(
|
||||
"Ihre Website zeigt der Google-Suchmaschine zusätzlichen Text, den normale "
|
||||
"Besucher nicht sehen. Bitte prüfen lassen."
|
||||
)
|
||||
|
||||
# --- Verdächtige Inhaltsänderungen ---
|
||||
z = _zaehle_seiten_befunde(diff)
|
||||
if z["hidden"]:
|
||||
saetze.append(
|
||||
f"Auf {z['hidden']} Seite(n) wurde unsichtbarer Text oder unsichtbare Links "
|
||||
"gefunden. So versteckt man üblicherweise Spam vor dem Auge des Besuchers."
|
||||
)
|
||||
if z["meta_refresh"]:
|
||||
saetze.append(
|
||||
f"{z['meta_refresh']} Seite(n) leiten Besucher jetzt automatisch auf eine "
|
||||
"andere Adresse weiter. Wenn Sie das nicht selbst eingerichtet haben, ist es verdächtig."
|
||||
)
|
||||
if z["scripts"]:
|
||||
saetze.append(
|
||||
f"Auf {z['scripts']} Seite(n) wurde neuer, auffälliger Programmcode eingebaut. "
|
||||
"Bitte von Ihrem Dienstleister prüfen lassen."
|
||||
)
|
||||
if z["comment_links"]:
|
||||
saetze.append(
|
||||
f"Auf {z['comment_links']} Seite(n) wurden versteckte Links im Quelltext gefunden."
|
||||
)
|
||||
if z["canonical"]:
|
||||
saetze.append(
|
||||
f"Bei {z['canonical']} Seite(n) wurde die hinterlegte 'Original-Adresse' geändert. "
|
||||
"Damit versuchen Angreifer manchmal, Ihre Seite bei Google umzuleiten."
|
||||
)
|
||||
|
||||
# --- Fremde Adressen / neue Seiten ---
|
||||
neue_domains = diff.get("new_external_domains", [])
|
||||
if neue_domains:
|
||||
beispiele = ", ".join(neue_domains[:3])
|
||||
mehr = f" (und {len(neue_domains) - 3} weitere)" if len(neue_domains) > 3 else ""
|
||||
saetze.append(
|
||||
f"Es tauchen Verweise auf neue fremde Internet-Adressen auf: {beispiele}{mehr}. "
|
||||
"Das kann harmlos sein (z. B. ein neuer Partner-Link) oder ein Hinweis auf einen Eingriff."
|
||||
)
|
||||
|
||||
neue_seiten = diff.get("new_internal_urls", [])
|
||||
if neue_seiten:
|
||||
saetze.append(
|
||||
f"Es sind {len(neue_seiten)} neue Unterseite(n) hinzugekommen. Haben Sie oder Ihr "
|
||||
"Dienstleister die angelegt?"
|
||||
)
|
||||
fehlende = diff.get("missing_internal_urls", [])
|
||||
if fehlende:
|
||||
saetze.append(
|
||||
f"{len(fehlende)} Seite(n), die es früher gab, sind nicht mehr auffindbar."
|
||||
)
|
||||
|
||||
if z["grosser_text"]:
|
||||
saetze.append(
|
||||
f"Auf {z['grosser_text']} Seite(n) wurde ein größerer Textblock neu hinzugefügt."
|
||||
)
|
||||
|
||||
# --- Datei-Veränderungen (Assets) ---
|
||||
if asset_diff and asset_diff.get("changed"):
|
||||
n = len(asset_diff["changed"])
|
||||
saetze.append(
|
||||
f"{n} Datei(en) (z. B. Bilder, Skripte oder Stylesheets) haben sich verändert. "
|
||||
"Nach einem geplanten Update ist das normal — sonst bitte prüfen lassen."
|
||||
)
|
||||
|
||||
# --- Kaputte Links (rein informativ) ---
|
||||
neue_kaputt = diff.get("new_broken_urls", [])
|
||||
if neue_kaputt:
|
||||
saetze.append(
|
||||
f"{len(neue_kaputt)} interne Link(s) führen ins Leere (Seite nicht gefunden)."
|
||||
)
|
||||
if ext_links and ext_links.get("broken"):
|
||||
saetze.append(
|
||||
f"{ext_links['broken']} Link(s) zu anderen Websites funktionieren nicht mehr. "
|
||||
"Das ist meist harmlos — die fremde Seite wurde gelöscht oder umbenannt."
|
||||
)
|
||||
|
||||
if not saetze:
|
||||
saetze.append("Es hat sich nichts Verdächtiges verändert.")
|
||||
|
||||
return saetze
|
||||
|
||||
|
||||
def was_tun(level: str) -> list[str]:
|
||||
"""Konkrete, laienverständliche Handlungsempfehlung je Level."""
|
||||
if level == "red":
|
||||
return [
|
||||
"Öffnen Sie Ihre Website im Browser und schauen Sie, ob etwas Fremdes zu sehen ist.",
|
||||
"Informieren Sie die Person oder Firma, die Ihre Website betreut.",
|
||||
"Geben Sie nichts frei, solange der Verdacht nicht geklärt ist.",
|
||||
]
|
||||
if level == "yellow":
|
||||
return [
|
||||
"Schauen Sie sich die unten genannten Punkte einmal an.",
|
||||
"Wenn die Änderung von Ihnen oder Ihrem Dienstleister gewollt war, ist alles in Ordnung.",
|
||||
"Gewollte Änderungen bestätigen Sie mit: python -m scanner approve --all",
|
||||
]
|
||||
return ["Sie müssen nichts tun."]
|
||||
Loading…
Add table
Add a link
Reference in a new issue