integrity_scanner_fuer_stat.../BEDIENUNGSANLEITUNG.md
Dieter Schlüter fe132e9e96 docs: KI-Score-Gewichte in Konfigurationsreferenz ergänzt
Abschnitt 12 (Score-Gewichte) listete die ai_*-Punktwerte nicht.
Nun vollständig, mit Hinweis auf die Gelb-Deckelung.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 00:32:31 +02:00

965 lines
34 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Bedienungsanleitung — Website Integrity Scanner
Überwachungswerkzeug für statische Websites gegen SEO-Spam und unbefugte Manipulationen.
Der Scanner läuft lokal im Verantwortungsbereich des Betreibers.
---
## Für Einsteiger in 3 Schritten
Sie brauchen kein IT-Wissen. Das Programm zeigt eine **Ampel** und erklärt alles in
einfachen Worten.
**Schritt 1 — Ersten Scan durchführen** (Scanner legt das Zielverzeichnis automatisch an):
```
python -m scanner https://ihre-website.de init
```
**Schritt 2 — Erststand speichern** (einmalig, wenn die Website in Ordnung ist):
```
python -m scanner https://ihre-website.de approve --all --note "Erststand geprüft"
```
**Schritt 3 — täglich prüfen** (am besten automatisch per Cron, siehe Abschnitt 10):
```
python -m scanner https://ihre-website.de scan
```
**Bei 🟡 oder 🔴 nachschauen:**
```
python -m scanner https://ihre-website.de report
```
Was die Ampel bedeutet:
| Ampel | Bedeutung | Was tun? |
|---|---|---|
| 🟢 | Alles in Ordnung | Nichts. |
| 🟡 | Bitte nachschauen | Bericht lesen; wenn die Änderung gewollt war, mit `approve --all` bestätigen. |
| 🔴 | Achtung | Website ansehen und die Person/Firma informieren, die Ihre Website betreut. |
> **Wichtig:** Die selteneren Prüfungen (Tarnung, externe Links, Dateien) laufen
> **automatisch einmal pro Woche** beim täglichen `scan` mit. Sie müssen dafür **nichts**
> extra einrichten — ein einziger täglicher Aufruf genügt für alles.
>
> Beim ersten `approve --all` wird zusätzlich die **Datei-Überwachung** automatisch
> eingerichtet (Bilder, Skripte, Stylesheets). Auch dafür ist kein weiterer Schritt nötig.
Alles Weitere in dieser Anleitung richtet sich an **Fortgeschrittene** und an
**Ihren Dienstleister**.
---
## Inhaltsverzeichnis
1. [Grundprinzip](#1-grundprinzip)
2. [Voraussetzungen und Installation](#2-voraussetzungen-und-installation)
3. [Erstinbetriebnahme](#3-erstinbetriebnahme)
4. [Täglicher Betrieb](#4-täglicher-betrieb)
5. [Befehle im Überblick](#5-befehle-im-überblick)
6. [Ergebnis-Level und Exit-Codes](#6-ergebnis-level-und-exit-codes)
7. [Berichte lesen](#7-berichte-lesen)
8. [Änderungen freigeben (approve)](#8-änderungen-freigeben-approve)
9. [Vorgehen bei einem Alarm](#9-vorgehen-bei-einem-alarm)
10. [Automatisierung per Cron-Job](#10-automatisierung-per-cron-job)
11. [E-Mail-Benachrichtigung einrichten](#11-e-mail-benachrichtigung-einrichten)
12. [Konfigurationsreferenz](#12-konfigurationsreferenz)
13. [Externe Domains verwalten](#13-externe-domains-verwalten)
14. [Rausch-Unterdrückung konfigurieren](#14-rausch-unterdrückung-konfigurieren)
15. [Was der Scanner erkennt](#15-was-der-scanner-erkennt)
16. [Verzeichnisstruktur](#16-verzeichnisstruktur)
17. [Cloaking-Erkennung](#17-cloaking-erkennung)
18. [Externe Links auf Erreichbarkeit prüfen](#18-externe-links-auf-erreichbarkeit-prüfen)
19. [Binärdateien und Assets prüfen](#19-binärdateien-und-assets-prüfen)
20. [KI-gestützte Inhaltsanalyse](#20-ki-gestützte-inhaltsanalyse)
---
## 1. Grundprinzip
Der Scanner arbeitet nach dem **Baseline-Diff-Prinzip**:
```
Initiale Sichtprüfung → Baseline festlegen
Täglicher Scan
Aktueller Zustand ──vergleiche── genehmigte Baseline
Abweichungen → Score → GREEN / YELLOW / RED
```
- Die **Baseline** ist ein einmal manuell geprüfter und freigegebener Referenzzustand.
Sie wird **niemals automatisch** aktualisiert.
- Jede Abweichung erhält Punkte. Der Gesamt-Score entscheidet über das Alarm-Level.
- Legitime Änderungen (z.B. neuer Artikel) müssen explizit per `approve` freigegeben werden.
---
## 2. Voraussetzungen und Installation
```bash
# Python 3.10 oder neuer erforderlich
python3 --version
# Virtuelle Umgebung anlegen und aktivieren
cd /home/dschlueter/Python_Programs/integrity_scanner_fuer_statische_Webseiten
python3 -m venv venv
source venv/bin/activate
# Abhängigkeiten installieren
pip install -r requirements.txt
```
Alle Befehle müssen aus dem Projektverzeichnis ausgeführt werden. Alternativ steht das
Wrapper-Script `scanner.sh` zur Verfügung, das automatisch ins Projektverzeichnis wechselt:
```bash
./scanner.sh https://ihre-website.de scan
# oder nach Ablage in ~/bin/:
scanner https://ihre-website.de scan
```
---
## 3. Erstinbetriebnahme
Die URL-Syntax legt das Zielverzeichnis beim ersten Aufruf **automatisch** an —
kein manuelles Anlegen von Verzeichnissen oder Konfigurieren von Pfaden nötig.
### Schritt 1: Ersten Crawl durchführen
```bash
python -m scanner https://ihre-website.de init
```
Der Scanner legt beim ersten Aufruf automatisch `ihre-website.de/` mit einer
angepassten `config.yaml` an und beginnt sofort mit dem Crawl.
Die Ausgabe zeigt:
- alle gefundenen internen URLs
- alle externen Domains (mit Hinweis, ob in der Whitelist)
- potenzielle Auffälligkeiten (Hidden Content, Meta-Refresh, verdächtige Scripts)
- Crawl-Fehler (Seiten, die nicht erreichbar waren)
Außerdem erscheint ein Hinweis, welche Zeilen in `.gitignore` noch einzutragen sind.
### Schritt 2: Ausgabe manuell prüfen
Wichtig vor dem nächsten Schritt:
- Alle externen Domains prüfen. Legitime Domains in `ihre-website.de/config/allowed_external.yaml`
eintragen (siehe [Abschnitt 13](#13-externe-domains-verwalten)).
- Auffälligkeiten im Browser nachschauen.
### Schritt 3: Baseline freigeben
Nur wenn alles sauber ist:
```bash
python -m scanner https://ihre-website.de approve --all --note "Initiale Baseline, geprüft am $(date +%Y-%m-%d)"
```
Ab jetzt ist der tägliche Betrieb möglich.
---
## 4. Täglicher Betrieb
### Manueller Scan
```bash
python -m scanner https://ihre-website.de scan
```
Ergebnis erscheint direkt im Terminal. Der vollständige Report liegt in `ihre-website.de/reports/`.
### Automatischer Scan (Cron)
```bash
crontab -e
```
Je eine Zeile pro überwachter Website einfügen (mit leichtem Zeitversatz):
```cron
# bredelar.info — täglich 03:00 Uhr
0 3 * * * cd /home/dschlueter/Python_Programs/integrity_scanner_fuer_statische_Webseiten && python -m scanner --config bredelar.info/config.yaml scan >> bredelar.info/logs/cron.log 2>&1
# jamulix.de — täglich 03:15 Uhr
15 3 * * * cd /home/dschlueter/Python_Programs/integrity_scanner_fuer_statische_Webseiten && python -m scanner --config jamulix.de/config.yaml scan >> jamulix.de/logs/cron.log 2>&1
# www.bergbauspuren-bredelar.de — täglich 03:30 Uhr
30 3 * * * cd /home/dschlueter/Python_Programs/integrity_scanner_fuer_statische_Webseiten && python -m scanner --config www.bergbauspuren-bredelar.de/config.yaml scan >> www.bergbauspuren-bredelar.de/logs/cron.log 2>&1
```
Danach prüfen: `crontab -l`
Der Exit-Code (0/1/2) ist für Cron-basierte Alarmierung nutzbar (z.B. mit `cronitor` oder
systemd-Timern mit `OnFailure=`).
---
## 5. Befehle im Überblick
Alle Befehle werden mit `python -m scanner <befehl>` aufgerufen.
Globale Optionen: `--config path/to/config.yaml`, `--verbose` (`-v`).
| Befehl | Zweck |
|---|---|
| `init` | Erstcrawl, Ausgabe aller Funde, noch kein Baseline-Update |
| `crawl` | Crawlt die Website, speichert Snapshot (kein Vergleich) |
| `check` | Vergleicht letzten Snapshot mit Baseline, erstellt Report |
| `scan` | `crawl` + `check` in einem Schritt (für den Cron-Betrieb) |
| `approve --all` | Alle neuen Snapshot-URLs in die Baseline übernehmen |
| `approve --url URL` | Einzelne URL freigeben |
| `approve --rebuild` | Gesamte Baseline ersetzen (nach großem Redesign) |
| `report` | Letzten Report im Terminal anzeigen |
| `report --diff-only` | Nur Textdiffs geänderter Seiten — farbig, kompakt |
| `report --format json` | Letzten Report als JSON anzeigen |
| `status` | Baseline-Datum, letzter Scan, aktueller Risk-Level |
| `cloak-check` | Doppel-Crawl: Browser-UA vs. Googlebot (Cloaking-Erkennung) |
| `check-ext-links` | Externe Links auf Erreichbarkeit prüfen (4xx/5xx) |
| `check-assets` | Binärdateien (JS/CSS/Bilder) auf Inhaltsänderung prüfen |
| `approve-assets` | Aktuelle Asset-Hashes als neue Baseline setzen |
| `test-alert` | Test-E-Mail senden ohne echten Scan — prüft SMTP-Konfiguration |
| `ai-dismiss --all` | KI-Funde als geprüft/akzeptiert quittieren (Fehlalarm) |
### Beispiele
```bash
# Einzelne Seite freigeben (z.B. nach Impressums-Update)
python -m scanner approve --url https://bredelar.info/impressum/ --note "Impressum aktualisiert Juni 2026"
# Alle Änderungen auf einmal freigeben (nur nach vollständiger Prüfung!)
python -m scanner approve --all --note "Redesign vom 2026-06-12 freigegeben"
# Baseline nach komplettem Umbau neu aufbauen
python -m scanner approve --rebuild --note "Komplettes Redesign v2, alle Seiten neu geprüft"
# Bestimmten Snapshot freigeben (statt den neuesten)
python -m scanner approve --all --snapshot data/snapshots/20260612_030000
# Aktuellen Stand prüfen
python -m scanner status
# Letzten Report anzeigen
python -m scanner report
```
---
## 6. Ergebnis-Level und Exit-Codes
| Level | Score | Exit-Code | Bedeutung |
|---|---|---|---|
| **GREEN** | 019 | `0` | Keine Auffälligkeiten |
| **YELLOW** | 2059 | `1` | Warnung — manuelle Prüfung empfohlen |
| **RED** | ≥ 60 | `2` | Alarm — sofortige Prüfung notwendig |
Die Schwellenwerte sind in `config.yaml` unter `thresholds` anpassbar.
---
## 7. Berichte lesen
Reports liegen in `reports/YYYYMMDD_HHMMSS/` als `report.md` und `report.json`.
```bash
# Neuesten Report im Terminal lesen
python -m scanner report
# Nur Diffs geänderter Seiten anzeigen (farbig, kompakt)
python -m scanner report --diff-only
# Direkt die Datei öffnen
ls reports/
cat reports/20260612_030000/report.md
```
`--diff-only` zeigt ausschließlich was sich verändert hat: Textdiffs farbig (`+` grün,
`-` rot, `@@` cyan), neue/fehlende URLs, Link-Änderungen, Hidden Content, Meta-Refresh.
Farben werden automatisch deaktiviert wenn die Ausgabe in eine Pipe oder Datei umgeleitet wird.
### Aufbau des Reports
| Abschnitt | Inhalt |
|---|---|
| **Zusammenfassung** | Kennzahlen auf einen Blick |
| **Risikobewertung** | Alle Gründe für den Score (was hat wie viele Punkte ausgelöst) |
| **Neue externe Domains** | Domains, die bisher nicht in der Baseline waren |
| **Neue interne URLs** | Seiten, die neu dazugekommen sind |
| **Fehlende URLs** | Seiten, die in der Baseline stehen, jetzt aber nicht mehr gefunden wurden |
| **Geänderte Seiten** | Detailansicht: Textänderungen, Link-Diffs, Meta-Änderungen |
| **Kaputte Links** | `[NEU]` = noch nicht in Baseline, `[bekannt]` = schon bekannt, wird nicht bewertet |
| **Unerwartetes JSON-LD** | Strukturierte Daten mit unbekanntem `@type` |
| **Verdächtige Dateinamen** | Links auf Dateien mit Webshell-typischen Namen |
| **Whitelist-Verstösse** | Externe Domains, die nicht in `allowed_external.yaml` stehen |
| **Fehlende Security-Header** | Informativ (kein Score) — nur auf HTTP-200-Seiten |
| **Crawl-Fehler** | URLs, die der Crawler nicht laden konnte |
| **Nächste Schritte** | Abhängig vom Level: konkrete Handlungsempfehlungen |
---
## 8. Änderungen freigeben (approve)
Wann `approve` nötig ist:
- Nach einer legitimen Inhaltsänderung (neuer Artikel, aktualisiertes Impressum, ...)
- Nach einem Redesign
- Nach Bereinigung eines echten Angriffs
### Einzelne URL freigeben
Wenn nur eine bestimmte Seite geändert wurde:
```bash
python -m scanner approve --url https://bredelar.info/veranstaltungen/ \
--note "Veranstaltungskalender aktualisiert"
```
### Alle Änderungen freigeben
Nach Prüfung aller markierten URLs:
```bash
python -m scanner approve --all --note "Monatliches Update geprüft und freigegeben"
```
**Achtung**: `--all` übernimmt alles aus dem letzten Snapshot. Nur anwenden, wenn jede
gemeldete Änderung manuell gesichtet wurde.
### Komplette Baseline neu aufbauen
Nach einem großen Redesign oder nach Bereinigung eines Angriffs:
```bash
python -m scanner approve --rebuild --note "Nach Angriff bereinigt, neues Redesign"
```
`--rebuild` ersetzt die gesamte bestehende Baseline durch den aktuellen Snapshot.
---
## 9. Vorgehen bei einem Alarm
### YELLOW — Warnung
1. Diffs sichten: `python -m scanner https://ihre-website.de report --diff-only`
2. Vollständigen Report lesen: `python -m scanner https://ihre-website.de report`
3. Markierte URLs im Browser aufrufen und Quelltext prüfen
4. Ist die Änderung legitim?
- Ja: `python -m scanner https://ihre-website.de approve --url <URL> --note "Grund"`
- Nein: Abschnitt [Angriff](#angriff) lesen
### RED — Alarm
Der Score ist ≥ 60. Typische Auslöser: neue externe Domain (50 Pkt.) + Hidden Content
(40 Pkt.), Webshell-Dateiname (60 Pkt.), Meta-Refresh (40 Pkt.).
#### Angriff
1. **Nicht sofort approve aufrufen** — das würde den kompromittierten Zustand als neue
Baseline festschreiben.
2. Diffs sichten: `python -m scanner https://ihre-website.de report --diff-only`
3. Report sichern: `cp -r ihre-website.de/reports/$(ls ihre-website.de/reports/ | tail -1) ~/alarm-$(date +%Y%m%d)/`
4. Website im Browser aufrufen und nach sichtbaren Fremdinhalten suchen.
5. Quelltext der betroffenen Seiten prüfen (insbesondere `display:none`-Bereiche).
6. Hoster kontaktieren, SSH-/FTP-Zugriffslogs prüfen.
7. Dateien mit Backup vergleichen (diff oder rsync --dry-run).
8. Nach vollständiger Bereinigung:
```bash
python -m scanner https://ihre-website.de scan # muss GREEN ergeben
python -m scanner https://ihre-website.de approve --rebuild --note "Nach Angriff bereinigt $(date +%Y-%m-%d)"
```
---
## 10. Automatisierung per Cron-Job
### Cron-Einträge einrichten
```bash
crontab -e
```
Je eine Zeile pro überwachter Website einfügen — mit **Zeitversatz von mindestens 15 Minuten**
zwischen den einzelnen Scans. Ein Scan lädt alle Seiten einer Website herunter; laufen mehrere
Scans gleichzeitig, konkurrieren sie um Netzwerk und CPU und können sich gegenseitig verlangsamen
oder die überwachten Server unnötig belasten.
```cron
# bredelar.info — täglich 03:00 Uhr
0 3 * * * cd /home/dschlueter/Python_Programs/integrity_scanner_fuer_statische_Webseiten && python -m scanner --config bredelar.info/config.yaml scan >> bredelar.info/logs/cron.log 2>&1
# jamulix.de — täglich 03:15 Uhr
15 3 * * * cd /home/dschlueter/Python_Programs/integrity_scanner_fuer_statische_Webseiten && python -m scanner --config jamulix.de/config.yaml scan >> jamulix.de/logs/cron.log 2>&1
# www.bergbauspuren-bredelar.de — täglich 03:30 Uhr
30 3 * * * cd /home/dschlueter/Python_Programs/integrity_scanner_fuer_statische_Webseiten && python -m scanner --config www.bergbauspuren-bredelar.de/config.yaml scan >> www.bergbauspuren-bredelar.de/logs/cron.log 2>&1
```
Danach prüfen ob die Einträge gespeichert wurden:
```bash
crontab -l
```
**Neue Website hinzufügen:**
1. Ersten Scan starten — Verzeichnis und config.yaml werden automatisch angelegt:
```bash
python -m scanner https://neue-seite.de init
```
2. Externe Domains prüfen und in `neue-seite.de/config/allowed_external.yaml` eintragen
3. Baseline freigeben: `python -m scanner https://neue-seite.de approve --all --note "Erststand geprüft"`
4. Weiteren Cron-Eintrag ergänzen — mit 15 Minuten Abstand zum vorherigen (z.B. 03:45 Uhr)
5. `neue-seite.de/` in `.gitignore` eintragen
### Log prüfen
```bash
tail -50 bredelar.info/logs/cron.log
tail -50 jamulix.de/logs/cron.log
```
### Log-Rotation (optional)
Datei `/etc/logrotate.d/integrity-scanner`:
```
/home/dschlueter/Python_Programs/integrity_scanner_fuer_statische_Webseiten/*/logs/*.log {
daily
rotate 30
compress
missingok
notifempty
}
```
---
## 11. E-Mail-Benachrichtigung einrichten
### Konfiguration testen ohne echten Scan
```bash
python -m scanner https://ihre-website.de test-alert
```
Sendet eine Test-Nachricht und prüft dabei SMTP-Host, Zugangsdaten und Empfängeradresse.
Sinnvoll nach jeder Änderung an der SMTP-Konfiguration.
Optional: `test-alert --level red` sendet eine Nachricht mit rotem Alarm-Level.
### config.yaml anpassen
```yaml
alerting:
min_level: "yellow" # oder "red" — nur bei rotem Alarm mailen
email:
enabled: true
smtp_host: "mail.linix.de" # SMTP-Server des Providers
smtp_port: 587
smtp_tls: true
smtp_user: "scanner@linix.de"
smtp_password_env: "SCANNER_SMTP_PASSWORD"
from: "scanner@bredelar.info"
to:
- "dieter.schlueter@linix.de"
```
### Passwort als Umgebungsvariable setzen
**Niemals** das Passwort direkt in `config.yaml` eintragen. Stattdessen:
```bash
# Temporär (bis zum nächsten Login)
export SCANNER_SMTP_PASSWORD="geheimesPasswort"
# Dauerhaft in ~/.bashrc oder ~/.profile
echo 'export SCANNER_SMTP_PASSWORD="geheimesPasswort"' >> ~/.bashrc
```
Für den Cron-Job die Variable in der Crontab oder in `/etc/environment` setzen:
```cron
SCANNER_SMTP_PASSWORD=geheimesPasswort
0 3 * * * cd /home/dschlueter/...
```
---
## 12. Konfigurationsreferenz
Alle Einstellungen in `config.yaml`. Fehlende Werte werden aus den Defaults übernommen.
### Kern-Einstellungen
| Schlüssel | Standard | Bedeutung |
|---|---|---|
| `target` | — | Zu überwachende URL (Pflicht) |
| `user_agent` | `integrity-scanner/1.0` | HTTP User-Agent |
| `request_timeout` | `20` | Timeout pro Seite in Sekunden |
### Crawl-Einstellungen (`crawl:`)
| Schlüssel | Standard | Bedeutung |
|---|---|---|
| `max_pages` | `200` | Maximale Anzahl gecrawlter Seiten |
| `delay_seconds` | `1.0` | Pause zwischen Requests (Server-Schonung) |
| `skip_extensions` | `.jpg`, `.pdf` etc. | Dateitypen, die nicht gecrawlt werden |
| `sitemap` | `true` | Sitemap.xml auslesen um Seiten ohne eingehende Links zu finden |
### Score-Gewichte (`scoring:`)
| Ereignis | Standard-Punkte |
|---|---|
| Verdächtiger Dateiname (Webshell) | 60 |
| Cloaking: Link nur im Bot-Crawl sichtbar | 60 |
| Neue externe Domain | 50 |
| Hidden Content (CSS-verstecktes Element mit Links/Text) | 40 |
| Meta-Refresh | 40 |
| Cloaking: signifikant mehr Text im Bot-Crawl (>100 Zeichen) | 40 |
| Geänderte JavaScript-Datei (`check-assets`) | 40 |
| Unerwartete Canonical-URL | 35 |
| Unerwartetes JSON-LD `@type` | 35 |
| Geänderte CSS-Datei (`check-assets`) | 30 |
| Neue interne URL | 30 |
| Neues verdächtiges Inline-Script | 30 |
| Link in HTML-Kommentar | 25 |
| Cloaking: `Vary: User-Agent` Header (ohne Inhaltsdiff) | 25 |
| Großer Textblock (>200 Zeichen) | 20 |
| Fehlende interne URL | 20 |
| Neuer kaputte Link (4xx/5xx, nicht in Baseline) | 20 |
| Geänderte Bild-/Binärdatei (`check-assets`) | 20 |
KI-Inhaltsanalyse (nur wenn aktiviert; Gesamt-Level aus KI ist auf **Gelb gedeckelt**):
| Ereignis (KI) | Standard-Punkte |
|---|---|
| Pornografie/sexuell expliziter Inhalt (`ai_pornography`) | 50 |
| Diffamierende/strafbare Aussage (`ai_defamation_illegal`) | 50 |
| Politische Propaganda (`ai_propaganda`) | 40 |
| Versteckter Spam (`ai_hidden_spam`) | 40 |
| Problematischer Bildinhalt — Untergrenze (`ai_suspicious_image`) | 40 |
| Themenfremde Werbung (`ai_off_topic_commercial`) | 30 |
| Widersprüchliche Aussage (`ai_contradiction`) | 20 |
Alle Werte sind in `config.yaml` unter `scoring:` anpassbar.
### Alarm-Schwellen (`thresholds:`)
```yaml
thresholds:
yellow: 20 # ab hier: YELLOW
red: 60 # ab hier: RED
large_text_block_chars: 200 # ab dieser Zeichenanzahl gilt ein Textblock als "groß"
```
### Erlaubte JSON-LD-Typen (`allowed_jsonld_types:`)
Alle `@type`-Werte, die in den strukturierten Daten der Website vorkommen dürfen.
Unbekannte Typen lösen einen Fund aus (35 Punkte). Aktuell konfiguriert:
`Organization`, `WebSite`, `WebPage`, `Article`, `BreadcrumbList`, `LocalBusiness`,
`Event`, `FAQPage` u.a.
### Security-Header (`security_headers:`)
Liste der Header, die auf jeder Seite erwartet werden. Fehlende Header werden im Report
gemeldet, aber **nicht** bewertet (kein Score-Einfluss).
---
## 13. Externe Domains verwalten
Datei: `config/allowed_external.yaml`
```yaml
# Legitime externe Domains — eine pro Zeile
- fonts.googleapis.com
- www.kloster-bredelar.de
```
Neue externe Domain erscheint im Scanner:
1. Domain im Browser prüfen — gehört sie zur Website?
2. Wenn legitim: in `config/allowed_external.yaml` eintragen
3. `python -m scanner approve --url <betroffene-Seite>` oder `--all`
Wenn eine Domain **nicht** in der Liste steht, löst jedes Vorkommen 50 Punkte aus
(YELLOW-Alarm, bei weiteren Funden RED).
---
## 14. Rausch-Unterdrückung konfigurieren
Manche Inhalte ändern sich automatisch (Datumsangaben, Zeitstempel) und würden sonst
bei jedem Scan einen Fund auslösen. Zwei Mechanismen:
### CSS-Selektoren ignorieren (`ignore_selectors:`)
Elemente werden vor dem Diff vollständig aus dem DOM entfernt:
```yaml
normalization:
ignore_selectors:
- "#last-modified"
- ".timestamp"
- ".cookie-banner"
```
Datei: direkt in `config.yaml` oder in `config/ignore_rules.yaml` (kommentierte Beispiele
dort vorhanden).
### Text-Muster ignorieren (`ignore_patterns:`)
Reguläre Ausdrücke, die im normalisierten Text durch `[IGNORED]` ersetzt werden:
```yaml
normalization:
ignore_patterns:
- '\b\d{1,2}\.\s*(?:Januar|Februar|März|April|Mai|Juni|Juli|August|September|Oktober|November|Dezember)\s+\d{4}\b'
- '\b\d{1,2}\.\d{1,2}\.\d{4}\b'
- 'Stand:\s+\S+'
# WordPress Contact Form 7: Platzhalter-Text rotiert bei jedem Seitenaufruf
- 'Kommentar oder Nachricht \* \w+'
```
Das Muster wird auf beiden Seiten des Vergleichs (Baseline und aktueller Snapshot)
angewendet — falsch-positive Alarme durch dynamische CMS-Inhalte werden so unterdrückt.
---
## 15. Was der Scanner erkennt
### Erkennt der Scanner ...
| Prüfung | Erkannt? | Anmerkung |
|---|---|---|
| Neuen Text auf einer Seite | Ja | Ab 200 Zeichen bewertet |
| Versteckten Text (CSS display:none etc.) | Ja | 40 Punkte |
| Neue externe Domain | Ja | 50 Punkte |
| Meta-Refresh-Weiterleitung | Ja | 40 Punkte |
| Webshell-Dateien (shell.php, c99 etc.) | Ja | 60 Punkte |
| Links in HTML-Kommentaren | Ja | 25 Punkte |
| Geänderte Canonical-URL | Ja | 35 Punkte |
| Unbekanntes JSON-LD `@type` | Ja | 35 Punkte |
| `eval()`, `atob()`, `XMLHttpRequest` in Scripts | Ja | 30 Punkte |
| Kaputte interne Links (neu) | Ja | 20 Punkte |
| Kaputte interne Links (bekannt) | Ja, informativ | Kein Score |
| Fehlende Security-Header | Ja, informativ | Kein Score |
| Kaputte **externe** Links (4xx/5xx) | Ja, mit `check-ext-links` | Kein Score, nur Report |
| UA-basiertes Cloaking (Googlebot sieht anderen Inhalt) | Ja, mit `cloak-check` | Score 4060 |
| Veränderungen in JS/CSS/Bildern (Inhalt) | Ja, mit `check-assets` | Score 2040 |
| Problematischer Text (Pornografie, Propaganda, Spam, Off-Topic) | Ja, mit KI-Analyse | Score 2050, auf Gelb gedeckelt |
| Problematische Bildinhalte + Text im Bild (OCR) | Ja, mit KI-Analyse | Score ≥ 40, auf Gelb gedeckelt |
| IP-basiertes Cloaking | Nein | Nur Google Search Console kann das erkennen |
| Serverseitige Code-Änderungen | Nein | Nur sichtbare HTML-Ausgabe |
### Grenzen des Scanners
- **IP-basiertes Cloaking** wird nicht erkannt: Wenn der Server je nach Herkunfts-IP
unterschiedliche Inhalte liefert (nur für echte Googlebot-IPs), sieht der Scanner immer
den normalen Inhalt. Ergänzung: Google Search Console → URL-Prüfung.
- Serverseitig injizierter Code, der nur unter bestimmten Bedingungen aktiv wird, ist
nur per `cloak-check` (UA-Variante) teilweise erkennbar.
---
## 16. Verzeichnisstruktur
```
scanner.sh Wrapper-Script (ausführbar, startet python -m scanner)
meine-seite.de/ Template für neue Targets
ihre-website.de/ Automatisch angelegtes Zielverzeichnis (ein Ordner pro Site)
config.yaml Hauptkonfiguration
config/
allowed_external.yaml Whitelist erlaubter externer Domains
ignore_rules.yaml Kommentierte Beispiele für Rausch-Unterdrückung
data/
baseline/ Freigegebene Referenz (NIEMALS automatisch geändert)
manifest.json Wer/was/wann freigegeben hat
pages/ Pro URL eine JSON-Datei (16-stelliger Hex-Name)
asset_hashes.json SHA-256-Hashes aller freigegebenen Assets
snapshots/ Ein Verzeichnis pro Crawl-Durchlauf
YYYYMMDD_HHMMSS/
manifest.json Zeitstempel, Seitenzahl, Fehler
pages/ Extrahierte Seiten-Daten
logs/
scanner.log Detailliertes Laufzeit-Log
cron.log Ausgabe der Cron-Ausführungen
reports/
YYYYMMDD_HHMMSS/ Normaler Scan-Report
report.md
report.json
YYYYMMDD_HHMMSS_cloak/ Cloaking-Check-Report
YYYYMMDD_HHMMSS_ext-links/ Externer-Links-Report
YYYYMMDD_HHMMSS_assets/ Asset-Check-Report
scanner/ Python-Paket (Quellcode)
tests/ Automatisierte Tests
```
### Tests ausführen
```bash
pytest tests/ -v # alle Tests
pytest tests/test_differ.py -v # einzelnes Testmodul
pytest tests/test_differ.py::TestScoreDiff::test_green_for_no_changes -v # einzelner Test
```
---
## 17. Cloaking-Erkennung
Cloaking bedeutet: der Server liefert Suchmaschinen-Bots absichtlich anderen Inhalt als
normalen Besuchern — ein klassisches Parasite-SEO-Angriffsmuster.
### Befehl
```bash
python -m scanner cloak-check
```
Der Scanner crawlt die Website zweimal nacheinander: einmal mit dem normalen Browser-User-Agent,
einmal als `Googlebot/2.1`. Unterschiede im Text, in den Links oder im `Vary`-Header werden
gemeldet.
### Scoring
| Fund | Punkte |
|---|---|
| Link nur im Bot-Crawl sichtbar | 60 (RED) |
| Bot sieht >100 Zeichen mehr Text | 40 |
| `Vary: User-Agent` Header ohne Inhaltsdiff | 25 |
### Report
Der Report liegt unter `reports/<ts>_cloak/report.md` und zeigt pro auffälliger Seite:
- ob `Vary: User-Agent` gesetzt ist
- welche Links nur der Bot sieht
- wie viele Zeichen mehr Text der Bot bekommt
- einen Text-Diff (bis zu 20 Zeilen)
### Grenzen
Erkennt nur **UA-basiertes** Cloaking. IP-basiertes Cloaking (Server prüft die
Herkunfts-IP des Crawlers) ist damit nicht erkennbar — dafür ist die Google Search Console
das richtige Werkzeug (URL-Prüfung → Live testen).
### Wann ausführen?
**Läuft automatisch wöchentlich** beim täglichen `scan` mit (steuerbar über
`periodic_checks` in der `config.yaml`). Der manuelle Aufruf ist nur bei konkretem
Verdacht nötig.
---
## 18. Externe Links auf Erreichbarkeit prüfen
Der tägliche `scan` erkennt *neue* externe Domains, prüft aber nicht ob bestehende externe
Links noch funktionieren. Dafür gibt es:
```bash
python -m scanner check-ext-links
```
### Was geprüft wird
Alle externen `<a>`-Links aus dem letzten Snapshot werden per HEAD-Request abgefragt
(Fallback auf GET bei HTTP 405). Gemeldet werden:
- **Kaputte Links** (HTTP 4xx/5xx) — mit Angabe, auf welchen Seiten sie verlinkt sind
- **Verbindungsfehler** (Timeout, DNS-Fehler) — ebenfalls mit Quelladressen
### Scoring
Kaputte externe Links werden **nicht bewertet** (kein Score-Einfluss). Das Ergebnis ist
rein informativ — externe Links brechen regelmäßig ohne Angriffsbezug.
Exit-Code: `0` wenn alle Links erreichbar, `1` wenn mindestens ein kaputtes oder
fehlendes Link gefunden wurde.
### Report
Der Report liegt unter `reports/<ts>_ext-links/report.md`.
### Wann ausführen?
**Läuft automatisch wöchentlich** beim täglichen `scan` mit (steuerbar über
`periodic_checks` in der `config.yaml`). Ein manueller Aufruf ist nur bei Bedarf nötig.
---
## 19. Binärdateien und Assets prüfen
Der tägliche `scan` erkennt Änderungen im HTML-Text und an Link-URLs, aber nicht ob der
*Inhalt* einer Bilddatei oder eines JavaScript-Files ausgetauscht wurde. Dafür:
### Ersteinrichtung
Die Asset-Baseline wird **automatisch** beim ersten `approve --all` angelegt — kein
separater Schritt nötig. Nur wenn die Asset-Baseline manuell neu gesetzt werden soll:
```bash
python -m scanner https://ihre-website.de approve-assets --note "Manuelle Baseline nach CDN-Wechsel"
```
### Regelmäßige Prüfung
```bash
python -m scanner check-assets
```
Alle verlinkten JS-, CSS- und Bilddateien aus dem letzten Snapshot werden heruntergeladen
und per SHA-256 gehasht. Abweichungen von der gespeicherten Baseline werden gemeldet.
### Scoring
| Dateityp | Punkte |
|---|---|
| JavaScript (`.js`) | 40 — YELLOW |
| CSS (`.css`, Stylesheets) | 30 — YELLOW |
| Bilder, PDFs, sonstige | 20 — YELLOW |
| 2× JS geändert | 80 — RED |
### Änderungen freigeben
Nach einem legitimen Update (neues CDN-Bundle, neues Logo):
```bash
python -m scanner approve-assets --note "CDN-Update nach Redesign Juli 2026"
```
`approve-assets` fetcht alle Assets frisch und setzt die neuen Hashes als Baseline.
### Report
Der Report liegt unter `reports/<ts>_assets/report.md` und zeigt pro geänderter Datei:
- den Dateityp
- die ersten 16 Zeichen des alten und neuen SHA-256-Hash
- die Größenänderung in Bytes
### Wann ausführen?
**Läuft automatisch wöchentlich** beim täglichen `scan` mit (steuerbar über
`periodic_checks` in der `config.yaml`). Die Datei-Vergleichsgrundlage wird zudem beim
ersten `approve --all` automatisch angelegt — `approve-assets` ist nur für den manuellen
Sonderfall gedacht.
---
## 20. KI-gestützte Inhaltsanalyse
Eine **optionale** Ergänzung zum Integritäts-Kern. Sie prüft Text und Bilder **semantisch**
auf problematische Inhalte, die kein strukturelles Signal hinterlassen:
- sexuell anstößige Inhalte / Pornografie
- politische Propaganda
- diffamierende oder strafbare Aussagen
- versteckter Werbe-/Spam-Text
- Werbung, die thematisch nicht zur Website passt
- in sich widersprüchliche Aussagen
- problematische Bildinhalte **und** Text in Bildern (OCR)
Die KI ist **Ergänzung, nicht Kern**: Ist sie deaktiviert (Standard), verhält sich der
Scanner exakt wie bisher.
### Das Kostengate (warum das günstig ist)
Jeder Text und jedes Bild bekommt einen Fingerabdruck (Hash). Das Ergebnis der KI-Prüfung
wird zu diesem Fingerabdruck in `data/ai_ledger.json` gespeichert. Bei jedem weiteren Scan
gilt:
- **Inhalt unverändert** → gleicher Hash → Ergebnis kommt aus dem Cache → **keine** Abfrage,
keine Kosten.
- **Inhalt neu oder geändert** → eine günstige OpenRouter-Abfrage, Ergebnis wird gespeichert.
Dadurch kann die Analyse bei **jedem** Scan mitlaufen und kostet an den meisten Tagen 0 €.
### Wirkung auf die Ampel
KI-Funde sind **auf Gelb gedeckelt** — sie lösen niemals allein ROT aus. ROT bleibt den
harten Integritäts-Signalen (Webshell, versteckte Inhalte) vorbehalten. Nur Funde mit
ausreichender Sicherheit (Konfidenz ≥ `ai_confidence_min`, Standard 0,7) und mindestens
mittlerer Schwere werden gewertet — das hält Fehlalarme niedrig.
Fehlt der API-Key oder schlägt eine Abfrage fehl, wird die Analyse **übersprungen** und der
Scan läuft unverändert weiter. Die KI kann den normalen Betrieb nie blockieren.
### Aktivieren
```yaml
ai_analysis:
enabled: true
api_key_env: "OPENROUTER_API_KEY"
site_context: "Heimat- und Vereinswebsite über die Bergbaugeschichte in Bredelar."
ai_confidence_min: 0.7
attempt_timeout: 30 # Zeitlimit je Modell-Versuch → eskaliert bei Langsamkeit
text:
enabled: true
models: # Kette: Stufe 1+2 free, Stufe 3 günstig bezahlt
- "qwen/qwen3-next-80b-a3b-instruct:free"
- "meta-llama/llama-3.3-70b-instruct:free"
- "google/gemini-2.5-flash-lite"
min_chars: 200 # nur Seitentexte ab dieser Größe
max_pages_per_scan: 20 # Kostendeckel pro Lauf
image:
enabled: true
models: # multimodal: Inhalt + OCR
- "google/gemma-4-31b-it:free"
- "nvidia/nemotron-nano-12b-v2-vl:free"
- "google/gemini-2.5-flash-lite"
max_images_per_scan: 15
```
**Wichtig — `site_context`:** Die KI bewertet die **thematische Passung** zur hier
beschriebenen Website, nicht einzelne Schlüsselwörter. Ein Angreifer kann Heimat- oder
Ortsbegriffe einstreuen, um simple Wortlisten zu täuschen — gegen die semantische
Themenprüfung hilft das nicht. Je präziser der `site_context`, desto besser die Trefferquote.
### API-Key setzen
Das Passwort/Token **niemals** in `config.yaml` eintragen, nur als Umgebungsvariable:
```bash
# Dauerhaft in ~/.bashrc
echo 'export OPENROUTER_API_KEY="sk-or-..."' >> ~/.bashrc
```
Für den Cron-Betrieb die Variable wie bei `SCANNER_SMTP_PASSWORD` in der Crontab oder in
`/etc/environment` hinterlegen.
### Fehlalarm quittieren
Wurde legitimer Inhalt fälschlich als auffällig markiert:
```bash
python -m scanner https://ihre-website.de ai-dismiss --all # alle offenen Funde
python -m scanner https://ihre-website.de ai-dismiss --hash <fp> # gezielt einen Fund
```
Der Fingerabdruck `<fp>` steht im Report bei jedem KI-Fund. Ein `approve --all` oder
`approve --url` quittiert die zugehörigen KI-Funde automatisch mit (freigegebener Inhalt
gilt als geprüft).
### Modelle (OpenRouter) und Eskalation
Pro Modalität wird eine **Modell-Kette mit zweifacher Eskalation** abgearbeitet. Schlägt ein
Modell fehl (Fehler, Rate-Limit) oder antwortet es langsamer als `attempt_timeout` Sekunden,
wird automatisch zur nächsten Stufe gewechselt. Stufe 1 und 2 sind kostenlose Modelle, Stufe 3
ist ein günstiges Bezahlmodell — so bleibt die Prüfung in der Regel kostenlos und ist trotzdem
zuverlässig. Welche Stufe das Verdikt geliefert hat, wird im Ledger und Report festgehalten.
| Zweck | Stufe 1 (free) | Stufe 2 (free) | Stufe 3 (bezahlt) |
|---|---|---|---|
| Text/Semantik | `qwen3-next-80b-a3b-instruct:free` | `llama-3.3-70b-instruct:free` | `gemini-2.5-flash-lite` |
| Bild + OCR | `gemma-4-31b-it:free` | `nemotron-nano-12b-v2-vl:free` | `gemini-2.5-flash-lite` |
Hinweis: Kostenlose Modelle liefern nicht immer ein gültiges Ergebnis (sie unterstützen das
strikte JSON-Format nicht durchgehend). In dem Fall greift automatisch die nächste Stufe — das
zuverlässige Bezahlmodell auf Stufe 3 fängt solche Fälle ab.
Audio- und Video-Prüfung sind als abschaltbare Erweiterungspunkte vorbereitet
(`ai_analysis.audio` / `ai_analysis.video`, default deaktiviert) und können aktiviert werden,
sobald eine Website solche Mediendateien direkt einbindet.
### Status prüfen
```bash
python -m scanner https://ihre-website.de status
```
zeigt bei aktivierter KI, wie viele Inhalte im Cache liegen und wie viele offene Funde es gibt.