Bisher war das KI-Level hart auf Gelb gedeckelt — auch injizierte
Pornografie oder strafbare/diffamierende Inhalte lösten nie Rot aus.
Das unterschätzt echte Notfälle (Sicherheits- + Rechts-/Reputationsrisiko).
Neu: kategoriebasierter Grave-Override in score_ai_findings. Nur Kategorien
aus red_categories (default ["pornography","defamation_illegal"]) lösen Rot
aus, und nur bei Schwere ≥ red_min_severity (high) UND Konfidenz ≥
red_min_confidence (0.9). Alle anderen Kategorien (Spam, Off-Topic,
Propaganda, Widerspruch) bleiben höchstens Gelb. Die Punktsumme allein
ergibt weiterhin nie Rot. red_categories: [] stellt das alte Verhalten her.
Begründung für die konservative Schwelle: LLM-Konfidenzen sind nicht
perfekt kalibriert — nur die gravierendsten Fälle dürfen die volle
ROT-Dringlichkeit ("Dienstleister informieren, nichts freigeben") tragen.
Rot fließt über die bestehende _worst_level-Verdrahtung automatisch ins
Gesamt-Level + Exit-Code 2; gilt auch für Bildfunde (OCR).
Tests: 237 grün (+7 Rot-Gate, bestehender Cap-Test auf Punktsumme-Logik
umgestellt). Config + Template + README + Bedienungsanleitung aktualisiert.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bisher: Konnte die KI kein Verdikt liefern (alle Modellstufen scheitern),
ging der Inhalt still als grün durch — für ein Sicherheitswerkzeug ein
Falsch-Negativ-Risiko ("konnte nicht prüfen" ≠ "sauber").
Zwei Maßnahmen:
1. Retry: _classify wiederholt die ganze Modell-Kette bei Komplettausfall
bis max_retries (Default 1) mit Backoff — fängt transiente Aussetzer ab.
2. Tracking: nicht prüfbare Inhalte landen in ai_result["unchecked"] (statt
stillem skip) und werden sichtbar in Terminal, Report (Markdown), diff-only
und E-Mail — jeweils mit URL. score_ai_findings nimmt sie als Grund auf;
mit unchecked_level="yellow" heben sie das Level auf mindestens Gelb an
(Default "warn" = nur Hinweis, Ampel unberührt).
Config: max_retries, retry_backoff_seconds, unchecked_level (ai_analysis).
Tests: 230 grün (+ Retry-Recovery, unchecked-Tracking, unchecked_level).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reads robots.txt Sitemap directive and fetches sitemap.xml / sitemap_index.xml
before crawling. All listed URLs are added to the crawl queue so pages
without any inbound links are still scanned. Configurable via crawl.sitemap.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds crawl.exclude_paths config option (list of URL path prefixes).
Matching URLs are excluded at two levels:
- Crawler: not fetched at all (saves HTTP requests)
- Differ: filtered from both baseline and current before comparison,
so legacy baseline entries under excluded paths don't appear as
"missing URLs" after the option is added retroactively
Example config:
crawl:
exclude_paths: ["/rss/", "/feed/", "/news/archiv/"]
8 new tests (176 total).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
Neue Befehle:
- `python -m scanner check-assets` — SHA-256-Hashes aller JS/CSS/Bild-Assets
aus dem letzten Snapshot fetchen und mit Baseline vergleichen
- `python -m scanner approve-assets` — aktuelle Hashes als neue Baseline setzen
Scoring: geändertes JS 40 Pkt, CSS 30 Pkt, Bild/PDF 20 Pkt.
Asset-Baseline liegt in data/baseline/asset_hashes.json.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Neuer Befehl `python -m scanner cloak-check`:
- Crawlt die Website zweimal: einmal mit Browser-UA, einmal als Googlebot
- Vergleicht Text, Links und Response-Header (Vary: User-Agent)
- Scoring: Bot-Only-Link 60 Pkt (RED), extra Text >100 Zeichen 40 Pkt,
Vary: User-Agent ohne Inhaltsdiff 25 Pkt
- Eigener Report unter reports/<ts>_cloak/
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Echte Bugs:
- extractor: JSON-LD-Crash bei Nicht-Dict-Items (isinstance-Guard)
- baseline/__main__: Crawl-Fehler ins Snapshot-Manifest -> erscheinen im Report
- __main__: Whitelist-/Header-/Webshell-Checks nur noch auf status==200
- crawler: Noise-Param-Regex auf Voll-Key (view=/value= nicht mehr verworfen)
- differ/__main__: unerwartetes JSON-LD via eigenem Kanal, auch auf
unveraenderten Seiten erkannt, kein Re-Alert auf bereits genehmigte Typen
Aufgeraeumt:
- checker: check_internal_paths, find_broken_pages, canonical_is_hijacked,
check_jsonld_types entfernt (nicht verdrahtet)
- allowed_paths.yaml-Logik und tote Scoring-Keys entfernt
- tote Imports entfernt
Neuer aktiver Schutz:
- Webshell-/Backdoor-Dateinamen-Check verdrahtet (suspicious_filename: 60).
Regex wortgrenzen-verankert gegen Fehlalarm auf PSEMailerAntispam.js
Effizienz/Struktur:
- crawler nutzt requests.Session (keep-alive)
- Cache-Buster-Normalisierung an einer Stelle (Extraktion) -> stabile
Snapshots, differ wieder reiner Set-Diff
Tests: 111 gruen (neu: test_checker.py + Regressionstests)
CLAUDE.md aktualisiert
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Neue 4xx/5xx-Seiten (nicht in Baseline) werden bewertet (+20 Pkt) und
lösen ab Schwelle einen Alert aus. Bekannte kaputte Links (in Baseline
vorhanden) erscheinen im Report als [bekannt] ohne Score-Beitrag.
Motivation: erster Crawl fand bredelar.info/www.stadtmarketing-marsberg.de
(404) — CMS-Fehler durch Schema-losen href. Soll dauerhaft sichtbar sein.
Änderungen: checker.find_broken_pages(), differ.compare_snapshots() mit
new_broken_urls/known_broken_urls, score_diff(), Report-Abschnitt,
config-Default new_broken_link:20, 4 neue Tests.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PyYAML parst auskommentierte Listen als None statt []. Da der Key
vorhanden ist, greift .get(key, []) nicht. Drei Fixes:
- extractor.py, differ.py: `or []` statt Default-Argument
- config.py: _deep_update überspringt None-Override auf List-Defaults
Tritt auf bei `ignore_selectors` und `ignore_patterns` in config.yaml.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>