integrity_scanner_fuer_stat.../BEDIENUNGSANLEITUNG.md
Dieter Schlüter 0aaf53135a docs: comprehensive documentation update
README.md:
- Workflow examples updated to URL-first syntax throughout
- report --diff-only added to Incident and Legitimate-Change workflows
- scanner.sh symlink hint added to installation section
- Directory structure overhauled: per-site layout, scanner.sh, template dir
- Removed report --diff-only from "not implemented" extensions list

BEDIENUNGSANLEITUNG.md:
- Section 2: scanner.sh wrapper usage added
- Section 4: manual scan uses URL-first syntax
- Section 9 (alarm): report --diff-only as first investigation step,
  updated all commands to URL-first syntax
- Section 16: directory structure reflects per-site layout + scanner.sh
- Section 19: asset baseline setup now correctly described as automatic

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 22:34:25 +02:00

28 KiB
Raw Blame History

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
  2. Voraussetzungen und Installation
  3. Erstinbetriebnahme
  4. Täglicher Betrieb
  5. Befehle im Überblick
  6. Ergebnis-Level und Exit-Codes
  7. Berichte lesen
  8. Änderungen freigeben (approve)
  9. Vorgehen bei einem Alarm
  10. Automatisierung per Cron-Job
  11. E-Mail-Benachrichtigung einrichten
  12. Konfigurationsreferenz
  13. Externe Domains verwalten
  14. Rausch-Unterdrückung konfigurieren
  15. Was der Scanner erkennt
  16. Verzeichnisstruktur
  17. Cloaking-Erkennung
  18. Externe Links auf Erreichbarkeit prüfen
  19. Binärdateien und Assets prüfen

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

# 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:

./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

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).
  • Auffälligkeiten im Browser nachschauen.

Schritt 3: Baseline freigeben

Nur wenn alles sauber ist:

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

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)

crontab -e

Je eine Zeile pro überwachter Website einfügen (mit leichtem Zeitversatz):

# 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

Beispiele

# 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.

# 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:

python -m scanner approve --url https://bredelar.info/veranstaltungen/ \
  --note "Veranstaltungskalender aktualisiert"

Alle Änderungen freigeben

Nach Prüfung aller markierten URLs:

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:

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 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:
    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

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.

# 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:

crontab -l

Neue Website hinzufügen:

  1. Ersten Scan starten — Verzeichnis und config.yaml werden automatisch angelegt:
    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

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

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

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:

# 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:

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

Alle Werte sind in config.yaml unter scoring: anpassbar.

Alarm-Schwellen (thresholds:)

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

# 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:

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:

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
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

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

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.


Der tägliche scan erkennt neue externe Domains, prüft aber nicht ob bestehende externe Links noch funktionieren. Dafür gibt es:

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:

python -m scanner https://ihre-website.de approve-assets --note "Manuelle Baseline nach CDN-Wechsel"

Regelmäßige Prüfung

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):

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.