feat: KI-gestützte Inhaltsanalyse via OpenRouter (hash-gegated)

Optionale semantische Prüfung von Text und Bildern (inkl. OCR) auf
problematische Inhalte ohne Link-Signal: Pornografie, Propaganda,
diffamierende/strafbare Texte, versteckter Spam, themenfremde Werbung,
widersprüchliche Aussagen. Bewertet die thematische Passung zum
deklarierten site_context — eingestreute Heimat-Begriffe täuschen die
Erkennung nicht.

Kern:
- scanner/ai_analyzer.py: run_ai_analysis + score_ai_findings.
  Hash-Gate über data/ai_ledger.json → unveränderte Inhalte = Cache-
  Treffer = kein API-Call. Nur neue/geänderte Inhalte kosten etwas.
- Modell-Kette mit zweifacher Eskalation (Stufe 1+2 free, Stufe 3
  günstig bezahlt); eskaliert bei Fehler ODER Timeout (attempt_timeout).
  Erfolgreiches Modell wird im Ledger vermerkt.
- KI-Funde sind auf GELB gedeckelt — ROT bleibt harten Integritäts-
  Signalen vorbehalten. Graceful degradation: ohne Key/bei Fehler wird
  übersprungen, Scan läuft unverändert weiter.

Integration:
- baseline.py: load/save_ai_ledger, dismiss_ai_entries.
- config.py: ai_analysis-Block + ai_* Scoring-Schlüssel.
- __main__.py: Einhängung in cmd_scan/cmd_check, ai-dismiss-Subcommand,
  approve quittiert zugehörige Funde, status-Anzeige, diff-only + report.
- alerter.py + __main__.py: beanstandete Dateien erscheinen mit URL,
  Begründung und Quittier-Fingerprint in E-Mail UND Markdown-Report.
- plain.py: laienverständliche KI-Sätze.

API-Key nur aus Umgebungsvariable (OPENROUTER_API_KEY). Audio/Video als
abschaltbare Hooks vorbereitet (default aus).

Modelle live gegen OpenRouter verifiziert; Demo auf bredelar.info zeigte
korrekte Erkennung (echter Inhalt clean, eingeschleuster Casino-Spam mit
Heimat-Begriffen als hidden_spam erkannt).

Tests: 227 grün (+26 für ai_analyzer, +1 für E-Mail-KI-Abschnitt).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Dieter Schlüter 2026-06-13 00:04:33 +02:00
commit cae3dbb985
11 changed files with 1301 additions and 7 deletions

View file

@ -71,6 +71,7 @@ Alles Weitere in dieser Anleitung richtet sich an **Fortgeschrittene** und an
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)
---
@ -220,6 +221,7 @@ Globale Optionen: `--config path/to/config.yaml`, `--verbose` (`-v`).
| `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
@ -630,6 +632,8 @@ angewendet — falsch-positive Alarme durch dynamische CMS-Inhalte werden so unt
| 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 |
@ -822,3 +826,128 @@ Der Report liegt unter `reports/<ts>_assets/report.md` und zeigt pro geänderter
`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.