Commit graph

31 commits

Author SHA1 Message Date
78dc79ceab feat: check-assets ↔ KI gekoppelt — byte-getauschte Bilder neu bewerten
Da bekannte Bilder aus Effizienzgründen nicht jeden Scan neu geladen werden
(url_hashes), prüfte die KI einen späteren Byte-Tausch eines bekannten Bildes
nicht von selbst erneut. Lücke geschlossen: Erkennt die Datei-Prüfung
(check-assets) eine geänderte Bilddatei, wird deren URL über
BaselineManager.invalidate_ai_url_hashes aus der Hash-Erinnerung entfernt →
nächster Scan lädt das Bild neu, neue Bytes = neuer Hash = Cache-Miss = die KI
bewertet den Inhalt neu.

Einhängung in cmd_scan (vor run_ai_analysis → Neubewertung noch im selben Scan)
und cmd_check_assets. Integritäts- und Inhaltsprüfung greifen so ineinander.

Tests: 260 grün (+2: invalidate entfernt nur die genannten URLs; geändertes
Bild wird nach Invalidierung neu geladen und beanstandet).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 04:22:38 +02:00
4db5817013 perf: URL→Byte-Hash-Erinnerung — bekannte Bilder nicht erneut laden/analysieren
Bisher: Die Analyse identischer Bilder war bereits dedupliziert (Ledger nach
Byte-Hash indiziert). Aber jede Bild-URL wurde bei jedem Scan ERNEUT
heruntergeladen, um ihren Hash für Gruppierung/Abdeckung zu berechnen — auch
wenn die Bytes längst bekannt und analysiert waren.

Neu: persistente URL→Hash-Map im Ledger (url_hashes). Eine bereits gesehene
Bild-URL wird nicht erneut geladen; Abdeckung umfasst nur noch nie gesehene
URLs. Duplikate (gleiche Bytes an mehreren URLs) werden je URL höchstens einmal
geholt und nie doppelt analysiert. Geänderte/geflaggte Bilder werden weiterhin
IMMER (erneut) geprüft.

Spart Bandbreite/Zeit und schont den überwachten Server (Fetch nur für Neues).
Byte-Änderungen bereits bekannter Bilder auf stabilen Seiten deckt weiterhin
check-assets ab (Integritäts-Layer).

Tests: 258 grün (+1: Duplikat-URLs einmal geladen, einmal analysiert, danach
kein erneuter Fetch).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 04:17:31 +02:00
369a3b8774 fix: KI-Analyse deckt standardmäßig ALLE Inhalte ab (kein Limit-Risiko)
Das per-Scan-Limit (max_pages/images_per_scan) war ein Sicherheitsrisiko:
Inhalte jenseits des Fensters blieben über viele Scans ungeprüft. Eine
KI-Bildanalyse ist aber nur sinnvoll, wenn ALLE Bilder geprüft werden.

Neu, sauber getrennt:
- Geänderte/neue/geflaggte Inhalte werden IMMER sofort geprüft (Echtzeit-
  Schutz), ungeachtet jeder Drossel — auch wenn das Limit klein ist.
- Die Limits drosseln NUR den historischen Altbestand und sind per Default
  0 = unbegrenzt → der erste Scan deckt den kompletten Bestand ab. Dank
  Hash-Cache eine einmalige Ausgabe; Folge-Scans prüfen nur noch Neues.
- Positiver Wert bleibt als optionale Drossel nur für sehr große Sites.

Tests: 257 grün (+2: geänderte Seite trotz Drossel=1 geprüft; image-Bestand
backlog=0 deckt alle Bilder in einem Scan ab). Default-Werte 20/15 → 0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 04:12:11 +02:00
6c7e69e635 fix: volle Bildabdeckung — kein dauerhaft ungeprüftes Bild mehr
Sicherheitslücke: Die Bildanalyse baute keine Abdeckung auf. Nach dem ersten
Budget-Fenster (max_images_per_scan) wurden nur noch Bilder auf GEÄNDERTEN
Seiten oder bereits geflaggte nachgeholt. Bilder auf stabilen Seiten jenseits
des ersten Fensters blieben dauerhaft ungeprüft (real: 10 von 201 analysiert).
Ein Angreifer hätte so ein strafbares Logo (z. B. Hakenkreuz) auf einer
stabilen Firmenseite platzieren können, ohne dass die KI es je inhaltlich prüft.

Fix: Jeder Scan holt zusätzlich noch nicht analysierte Bilder (URL nicht im
Ledger) bis zum Budget nach → volle Abdeckung baut sich über mehrere Scans auf
(wie beim Text). Priorität: geänderte/geflaggte Bilder zuerst, dann Restbudget
für die Abdeckung. Byte-Änderungen bekannter Bilder deckt check-assets ab.

Bewusste Entscheidung GEGEN ai_analysis.image.exclude_paths: ein Pfad-Ausschluss
(z. B. /content/companies/) wäre genau der blinde Fleck — strafbare Logos würden
durchrutschen. Stattdessen: initiales "Gemecker" anhören, legitime Logos einmalig
per ai-dismiss akzeptieren (Quittung am Byte-Hash → ausgetauschtes Bild flaggt neu).

Tests: 255 grün (+1: Abdeckung baut sich über mehrere Scans bis 100% auf).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 04:02:35 +02:00
3ab313e733 fix: Report und Alarm-Mail konsistent (gemeinsamer Renderer)
Behebt vier vom Nutzer gemeldete Inkonsistenzen zwischen Report und E-Mail:

1. E-Mail ⊊ Report: Es gab zwei divergierende Renderer (alerter._format_body
   und __main__._render_markdown). Jetzt EIN gemeinsamer Renderer
   (scanner/report.py:render_markdown), den beide nutzen → identischer Inhalt,
   keine Divergenz mehr möglich.

2. KI im Report unsichtbar: Der Report rief klartext_befunde OHNE ai_result auf
   (KI-Satz fehlte im "Was bedeutet das?") und die Zusammenfassungs-Tabelle
   hatte keine KI-Zeile. Jetzt: KI-Satz im Klartext + Zeilen "KI-Funde" und
   "KI nicht geprüft" in der Tabelle.

3. Fehlende URL in Kommandos: approve/report/ai-dismiss standen ohne Target da
   (nicht ausführbar). Neuer Helfer plain.scanner_cmd(target, rest) injiziert
   das Target überall → "python -m scanner https://site approve --all".

4. "Level: YELLOW" aber "Score: 0": Der angezeigte Score war nur der Inhalts-
   Diff-Score; KI-/Cloak-/Asset-Scores flossen ins Level, aber nicht in den
   Score. cmd_scan/cmd_check aggregieren jetzt alle Teil-Scores → Score und
   Level passen zusammen.

Tests: 254 grün (+4: Body==Report, Kommandos-mit-Target, Score-Anzeige,
Level-Badge). alerter._format_body delegiert nur noch an render_markdown.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 03:35:05 +02:00
125d80e7f9 feat: adaptiver Modell-Router (Circuit-Breaker) + parallele KI-Analyse
Der reale Erst-Scan dauerte ~10 min, weil pro Kandidat sequenziell erst die
rate-limited Free-Modelle (429) probiert wurden. Zwei Hebel beheben das:

1. ModelRouter: programmatische Telemetrie statt LLM-Orchestrator. Pro Modell
   Erfolg/Latenz; ausgefallene/rate-limited Modelle bekommen per Circuit-Breaker
   einen Cooldown und werden übersprungen, das schnellste gesunde Modell zuerst.
   Failsafe-Pruning toter Slugs über OpenRouter /models (24h-Cache). State
   persistent in data/ai_router_state.json.

2. Parallelisierung: ThreadPoolExecutor mit konfigurierbarer max_concurrency
   (Default 8, I/O-gebunden → an API-Rate-Limits gebunden, nicht an CPU-Kerne).
   Klassifikationen laufen nebenläufig, Merge im Hauptthread (keine Locks),
   findings deterministisch sortiert. Fingerprint-Gruppierung: identischer
   Inhalt wird nur einmal klassifiziert, Funde aber für alle URLs emittiert.

Realtest bredelar.info: Frisch-Scan von ~10 min auf 1:01 min; Breaker öffnete
14× (Free-Modelle übersprungen), alle Verdikte von gemini-2.5-flash-lite.

Config: max_concurrency, breaker_failure_threshold, breaker_cooldown_seconds,
refresh_models. Tests: 251 grün (+12: Router-Reihung/Breaker/Pruning-failsafe,
parallel==sequenziell, Dedup, deterministische Reihenfolge).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:51:30 +02:00
31730403b3 fix: KI-Robustheit — harter Wanduhr-Deadline + persistenter Ledger
Ein echter Durchlauf auf bredelar.info deckte zwei Bugs auf, die mit
gemockten Tests unsichtbar waren:

1. Hang: attempt_timeout wurde nur als requests-Inaktivitäts-Timeout
   übergeben. Ein Free-Modell, das die Verbindung offen hält oder Tokens
   langsam tröpfeln lässt, umging ihn → der ganze Scan hing unbegrenzt
   (real auf einem Bild >5 min). Fix: _post_with_deadline führt den Call
   in einem Worker-Thread mit future.result(timeout=...) aus — hartes
   Wanduhr-Limit, bei Überschreitung Eskalation statt Hang.

2. Verlust bei Abbruch: Das Ledger wurde nur ganz am Ende gespeichert,
   ein Hang/Crash verwarf alle berechneten Verdikte. Fix: Zwischensicherung
   nach der Textphase + im finally — Verdikte überleben einen Abbruch.

Zusätzlich attempt_timeout 30 → 20 s (flottere Eskalation).

Im erneuten Lauf griff der Deadline 4× sauber, der Scan lief vollständig
durch (Gelb: ein off_topic_commercial-Fund auf einer Gewerbe-Seite).

Tests: 239 grün (+2: Deadline kappt Hang <2s, Textverdikte überleben
Bildphasen-Crash).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:11:14 +02:00
a8dcaabf7c feat: schwerwiegende KI-Funde dürfen ROT auslösen (konfigurierbar)
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>
2026-06-13 01:16:23 +02:00
d06e3d3dfd feat: KI-Absicherung gegen Falsch-Negative bei Dienstausfall
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>
2026-06-13 00:50:36 +02:00
cae3dbb985 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>
2026-06-13 00:04:33 +02:00
2c3aa60d97 feat: report --diff-only — colored terminal diff view
Shows only the changed parts of the latest report: text diffs with
ANSI colors (+green/-red/@cyan), new/missing URLs, link diffs,
hidden content, meta-refresh and inline script findings.
Colors are suppressed when stdout is not a terminal (pipes, log files).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 22:27:19 +02:00
beb7f274a3 docs: update README + Bedienungsanleitung; fix config.py SMTP defaults
- test-alert subcommand documented in both files
- Cron examples extended with www.bergbauspuren-bredelar.de (03:30)
- "Neue Website hinzufügen" updated to URL-first syntax
- Bedienungsanleitung: sitemap option, CF7 ignore_pattern example, generic header
- config.py: SMTP defaults corrected (smtp_host, smtp_user, from, to)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 22:17:34 +02:00
aa2b1ebf5c feat: test-alert subcommand + enable email for bredelar.info
- Add `test-alert [--level yellow|red]` to verify SMTP without a real scan
- Enable email alerting in bredelar.info/config.yaml (uses DEFAULT_CONFIG
  for host/user/from/to, only smtp_password_env kept explicit)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 19:50:24 +02:00
9801ce6fc4 feat: sitemap seeding — discover orphan pages not reachable via links
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>
2026-06-12 17:58:52 +02:00
792ca67c08 feat: single-line progress indicator during crawl and ext-link check
Uses \r to overwrite the same terminal line — no scrolling.
Crawler exposes on_progress callback; checker.check_external_links
gets an optional on_progress parameter.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 17:17:37 +02:00
fcbccce422 feat: URL-first CLI + auto-setup for new targets
- python -m scanner https://domain.de scan  (URL as first positional arg)
- On first use, the domain directory is auto-created from the
  meine-seite.de template with all paths and target URL substituted
- scanner.sh wrapper script for convenient ./scanner.sh usage
- --config syntax fully preserved (cron jobs unchanged)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 16:29:43 +02:00
Your Name
f964e41201 fix: retry crawler with browser UA on 403 (bot-blocking)
Some servers return 403 for the scanner's bot user-agent but serve
the page normally to browsers. On a 403 response, the crawler now
retries with a browser-like UA; if the server then returns 2xx, the
page is crawled normally (HTML extracted, status recorded as 2xx).

Mirrors the same fix already applied to check_external_links.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 14:46:06 +02:00
Your Name
3a44cfb515 feat: exclude_paths — skip dynamic areas (RSS feeds, news archives) from scan
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>
2026-06-12 13:14:06 +02:00
Your Name
b01344dcd8 fix: always report broken internal links + show source pages
Previously, broken links that were part of the baseline ("known broken")
disappeared from the terminal output (score=0, no Klartext mention).

- differ.py: build reverse link index to record which pages link to
  each broken URL; "pages" field added to new_broken/known_broken entries
- plain.py: klartext_befunde() now includes known_broken_urls alongside
  new_broken_urls — broken links are always reported until fixed
- __main__.py: report shows "gefunden auf: <page>" for every broken entry,
  known entries labelled "[bekannt, noch nicht behoben]"
- 3 new tests (169 total)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 12:18:38 +02:00
Your Name
30ac95a5cf fix: show source pages for broken/unreachable external links in report
The broken_links entries already contained a pages[] list, but
_render_markdown didn't display it. Now each broken link shows up to
3 pages where it was found ("gefunden auf: …").
Also adds error_links (connection errors) to the main scan report
with the same source-page annotation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 12:02:44 +02:00
Your Name
4bae633a41 perf: reuse browser-UA crawl result in cloak-check during scan
When cmd_scan triggers the weekly cloak-check, the browser-UA crawl
has already just been completed. Pass those pages directly to
_run_cloak_core() so only the Googlebot-UA crawl is needed,
reducing weekly scans from 3 full crawls to 2.

Standalone `cloak-check` command is unaffected (pages_normal=None).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 11:55:24 +02:00
Your Name
22ed1570a4 fix: retry external link check with browser UA on 403
Servers that block bot user-agents return 403 even for live URLs.
On a 403 response, the checker now retries with a browser-like UA;
if the server then responds with 2xx/3xx, the link is counted as OK.
This eliminates false positives from bot-blocking (e.g. bergbauspuren-bredelar.de).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 11:40:27 +02:00
Your Name
bed20db8bd feat: Laientauglichkeit — Auto-Modus, Klartext-Ausgabe, einfachere Doku
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>
2026-06-12 11:14:05 +02:00
Your Name
d803f79efe feat: Binärdateien auf Änderungen prüfen (check-assets / approve-assets)
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>
2026-06-12 09:26:59 +02:00
Your Name
ecd01abf98 feat: externe Links auf Erreichbarkeit prüfen (check-ext-links)
Neuer Befehl `python -m scanner check-ext-links`:
- Liest alle externen <a>-Links aus dem letzten Snapshot
- Prüft jeden per HEAD-Request (Fallback auf GET bei HTTP 405)
- Meldet 4xx/5xx und Verbindungsfehler mit Quelladressen
- Report unter reports/<ts>_ext-links/
- Exit-Code 1 bei kaputten Links, 0 wenn alle erreichbar

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 04:14:50 +02:00
Your Name
0302d469b1 feat: Cloaking-Erkennung per Doppel-Crawl (Browser-UA vs. Googlebot-UA)
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>
2026-06-12 04:02:24 +02:00
Your Name
68243a97b9 fix: Bugfixes, toten Code entfernt, Cache-Normalisierung & Session
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>
2026-06-12 03:20:32 +02:00
Your Name
b3ef3a90d4 feat: report broken links (4xx/5xx) in every scan
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>
2026-06-12 01:55:19 +02:00
Your Name
5d52db6eed fix: exclude internal comment-links; add initial external domain whitelist
- extractor.py: _find_comment_links() liefert nur noch externe Links.
  Interne Links in Kommentaren sind CMS-Artefakte (auskommentierte
  Navigations-Fragmente), keine SEO-Spam-Indikatoren.
- allowed_external.yaml: 36 legitime externe Domains nach initialem
  Crawl von bredelar.info manuell geprüft und eingetragen.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 01:15:58 +02:00
Your Name
de90dbc19c fix: handle YAML null values for list config fields
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>
2026-06-12 01:12:09 +02:00
Your Name
98e8d11eb5 feat: initial implementation of website integrity scanner
Python-Package zur unabhängigen Überwachung statischer Websites gegen
SEO-Spam und unbefugte Manipulationen. Läuft außerhalb des Hosters.

Kernfunktionen:
- Reiner Python-Crawler (bs4+lxml), kein wget
- Baseline-Management mit manuellem Freigabe-Workflow (niemals automatisch)
- Text-/Link-/Meta-Diff mit Risiko-Scoring (grün/gelb/rot)
- Hidden-Content-Erkennung (CSS inline, noscript, Kommentar-Links)
- Whitelist für externe Domains und interne Pfade
- E-Mail + Webhook Alarmierung
- CLI: init, crawl, check, scan, approve, report, status
- 79 Unit-Tests (pytest), alle grün

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