integrity_scanner_fuer_stat.../BEDIENUNGSANLEITUNG.md
Your Name 54ff1a6060 docs: Bedienungsanleitung um cloak-check, check-ext-links und check-assets ergänzen
Neue Abschnitte 17–19 für Cloaking-Erkennung, externe Link-Prüfung und
Asset-Hashing. Befehlstabelle, Scoring-Tabelle, Erkennungsübersicht und
Verzeichnisstruktur aktualisiert.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 09:32:30 +02:00

701 lines
22 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 die statische Website **bredelar.info** gegen SEO-Spam und
unbefugte Manipulationen. Der Scanner läuft lokal im Verantwortungsbereich des Betreibers.
---
## 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)
---
## 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 (dort liegt `config.yaml`).
---
## 3. Erstinbetriebnahme
### Schritt 1: Ersten Crawl durchführen
```bash
python -m scanner init
```
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)
### Schritt 2: Ausgabe manuell prüfen
Wichtig vor dem nächsten Schritt:
- Alle externen Domains prüfen. Legitime Domains in `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 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 scan
```
Ergebnis erscheint direkt im Terminal. Der vollständige Report liegt in `reports/`.
### Automatischer Scan (Cron)
```bash
# Cron-Eintrag hinzufügen
crontab -e
```
Eintrag:
```cron
# Website-Scan täglich um 03:00 Uhr
0 3 * * * cd /home/dschlueter/Python_Programs/integrity_scanner_fuer_statische_Webseiten && /home/dschlueter/Python_Programs/integrity_scanner_fuer_statische_Webseiten/venv/bin/python -m scanner scan >> logs/cron.log 2>&1
```
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 --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 |
### 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
# Direkt die Datei öffnen
ls reports/
cat reports/20260612_030000/report.md
```
### 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. Report lesen: `python -m scanner report`
2. Markierte URLs im Browser aufrufen und Quelltext prüfen
3. Ist die Änderung legitim?
- Ja: `python -m scanner 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. Report sichern: `cp -r reports/$(ls reports/ | tail -1) ~/alarm-$(date +%Y%m%d)/`
3. Website im Browser aufrufen und nach sichtbaren Fremdinhalten suchen.
4. Quelltext der betroffenen Seiten prüfen (insbesondere `display:none`-Bereiche).
5. Hoster kontaktieren, SSH-/FTP-Zugriffslogs prüfen.
6. Dateien mit Backup vergleichen (diff oder rsync --dry-run).
7. Nach vollständiger Bereinigung:
```bash
python -m scanner scan # muss GREEN ergeben
python -m scanner approve --rebuild --note "Nach Angriff bereinigt $(date +%Y-%m-%d)"
```
---
## 10. Automatisierung per Cron-Job
### Cron-Eintrag
```bash
crontab -e
```
```cron
# Website-Scan täglich um 03:00 Uhr
0 3 * * * cd /home/dschlueter/Python_Programs/integrity_scanner_fuer_statische_Webseiten && /home/dschlueter/Python_Programs/integrity_scanner_fuer_statische_Webseiten/venv/bin/python -m scanner scan >> logs/cron.log 2>&1
```
### Log prüfen
```bash
tail -50 logs/cron.log
tail -f logs/scanner.log # detaillierteres Log des Scanners selbst
```
### 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
### 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 |
### 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:`)
```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+'
```
---
## 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
```
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 (approve-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
report.md
report.json
YYYYMMDD_HHMMSS_ext-links/ Externer-Links-Report
report.md
report.json
YYYYMMDD_HHMMSS_assets/ Asset-Check-Report
report.md
report.json
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?
Nicht täglich — der Doppel-Crawl belastet den Server doppelt. Empfehlung: monatlich
oder bei konkretem Verdacht.
---
## 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?
Wöchentlich oder monatlich reicht. Nicht im täglichen Cron — zu viele Requests an
externe Server.
---
## 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 (einmalig)
```bash
python -m scanner crawl # aktuellen Snapshot anlegen (falls noch keiner vorhanden)
python -m scanner approve-assets # SHA-256-Hashes aller Assets als Baseline speichern
```
### 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?
Wöchentlich oder nach bekannten Website-Updates. Nicht täglich — lädt alle Assets herunter
und ist entsprechend langsam.