Ein Leser hat mir auf Mastodon geschrieben. Er fand den Blog interessant, aber ein Artikel ließ sich bei ihm nicht öffnen. Alle anderen schon. Safari meldete nur, die Seite werde „von einer Inhaltssperre blockiert“. Er schrieb selbst, das sei wohl eher ein Fun-Fact. Etwas später kam der Nachtrag, dass er unter macOS mit Safari und dem Werbeblocker wBlock unterwegs ist.

Der betroffene Artikel war ausgerechnet der über Besucherstatistik ohne Cookie-Banner. Also genau der Artikel, in dem ich erkläre, warum hier kein Tracking-Skript läuft.

Mein erster Gedanke: Das darf doch nicht wahr sein. Ausgerechnet ein Artikel über die Erfassung von Besucherdaten, bei dem ich extrem darauf achte, datensparsam zu sein, wird geblockt.

Mein erster Verdacht

Der Artikel war der einzige mit „matomo“ in der Adresse. Viele Blocklisten haben Regeln gegen Matomo, weil Matomo meistens als Tracking-Skript im Browser läuft. Bei mir läuft es zwar nur auf dem Server und wertet Logdateien aus, aber das weiß eine Filterliste nicht. Das klang plausibel, und so habe ich es auch geantwortet.

Es war falsch. Aber dazu brauchte ich erst die Liste der aktiven Filter. Der Leser hatte diese sieben aktiv:

  • AdGuard Base Filter
  • AdGuard Tracking Protection Filter
  • AdGuard URL Tracking Protection Filter
  • Actually Legitimate URL Shortener Tool
  • Peter Lowe’s Blocklist
  • AdGuard Cookie Notices
  • AdGuard German Filter

Suchen in 300.000 Zeilen

Die AdGuard-Listen liegen offen auf GitHub im Repo AdguardTeam/FiltersRegistry, auch in der Fassung für Safari. Zusammen sind das gut 300.000 Zeilen. Ich habe sie heruntergeladen und nach den Wörtern aus der Adresse durchsucht: matomo, log-import, besucherstatistik, cookie-banner.

Bei matomo kam nichts, was eine Seite sperrt. Die Treffer waren nur Regeln, die auf bestimmten fremden Seiten Cookie-Dialoge wegklicken. Bei cookie-banner dagegen stand in der Liste AdGuard Cookie Notices diese Zeile:

-cookie-banner-$domain=~killthecookiebanner.eu

Kurz übersetzt:

  • -cookie-banner- ist das Muster. Es hat keinen Anker am Anfang oder Ende, passt also auf jede Adresse, in der diese Zeichenfolge irgendwo vorkommt.
  • $domain=~killthecookiebanner.eu heißt: überall, außer auf dieser einen Domain. Die Tilde ist ein „nicht“.

Gedacht ist die Regel für Dateien wie /js/site-cookie-banner-v2.js. Mein Slug, also der Teil der Adresse, der den Artikel benennt, sah aber so aus:

/posts/besucherstatistik-ohne-cookie-banner-matomo-log-import/

Da steht es: -ohne-cookie-banner-matomo. Bindestrich, cookie, Bindestrich, banner, Bindestrich. Die Regel hat keine Einschränkung auf Skripte oder Bilder, und wBlock hat damit in Safari gleich die ganze Seite gesperrt. Kein anderer Artikel hat diese Zeichenfolge in der Adresse, deshalb ging alles andere.

In Safari nachgestellt habe ich das nicht. Ob andere Werbeblocker mit derselben Liste auch das ganze Dokument sperren oder nur Dateien, habe ich ebenfalls nicht geprüft.

Die Ironie gefällt mir trotzdem: Ein Filter gegen Cookie-Banner sperrt einen Artikel darüber, wie man ohne Cookie-Banner auskommt.

Die Lösung: neue Adresse

Der Inhalt war nie das Problem, nur die Adresse. Filterlisten wie diese sehen den Text eines Artikels gar nicht, sie prüfen URLs. Also bekam der Artikel einen neuen Slug ohne die verdächtige Zeichenfolge. Im Front Matter sieht das so aus:

slug: "besucherstatistik-matomo-log-import"
aliases: ["/posts/besucherstatistik-ohne-cookie-banner-matomo-log-import/"]

Mit aliases legt Hugo unter der alten Adresse eine kleine Seite an, die per Meta-Refresh auf die neue weiterleitet. Die Seite enthält kein JavaScript, an der Content-Security-Policy ändert sich also nichts. Links, die schon im Umlauf sind, funktionieren damit weiter.

Mit einer Einschränkung: Wer die Filterliste aktiv hat, bleibt auf der alten Adresse hängen. Die Weiterleitungsseite liegt ja genau dort, wo die Regel greift. Diesen Lesern hilft nur der neue Link.

Drei andere Artikel haben auf den Matomo-Artikel verlinkt. Die zeigen jetzt direkt auf die neue Adresse statt über die Weiterleitung.

Eine Sorge hatte ich noch: Meine Pipeline kündigt neue Artikel selbst auf Mastodon an, und sie merkt sich das über die Adresse. Für sie war der Matomo-Artikel unter der neuen Adresse also ein „neuer“ Artikel. Getootet hat sie trotzdem nicht, weil sie nur Artikel ankündigt, die höchstens drei Tage alt sind. Da hat mich eine Schutzregel vor einem doppelten Toot bewahrt, an die ich bei diesem Problem gar nicht gedacht hatte.

Kurz nach der Umstellung kam die Rückmeldung vom Leser: Die Seite lässt sich jetzt „ohne Murren“ öffnen.

Ein grober Check für neue Slugs

Damit mir das nicht noch einmal passiert, habe ich ein kleines Skript, das eine Adresse gegen die Listen prüft. Es ist kein Nachbau eines Werbeblockers. Es kennt nur einfache URL-Regeln, ignoriert Ausnahmen und die meisten Optionen. Für die Frage „Steht in meinem Slug etwas, worauf eine Liste anspringt?“ reicht es.

#!/usr/bin/env python3
"""Grober Check: Welche einfache Filterregel passt auf eine URL?

Kein vollständiger Adblocker-Nachbau. Er kennt nur einfache URL-Regeln,
ignoriert Ausnahmen (@@) und die meisten Optionen.
"""
import re
import sys
import urllib.request

BASE = "https://raw.githubusercontent.com/AdguardTeam/FiltersRegistry/master/platforms/extension/safari/filters/"
LISTS = {
    "AdGuard Base": "2.txt",
    "AdGuard Tracking Protection": "3.txt",
    "AdGuard Cookie Notices": "18.txt",
    "AdGuard German": "6.txt",
    "Peter Lowe's Blocklist": "204.txt",
}
SKIP_OPTIONS = ("third-party", "script", "image", "stylesheet", "xmlhttprequest",
                "font", "media", "subdocument", "permissions", "removeparam")


def to_regex(pattern):
    if pattern.startswith("||"):
        rx, pattern = r"^[a-z]+://([^/]*\.)?", pattern[2:]
    elif pattern.startswith("|"):
        rx, pattern = "^", pattern[1:]
    else:
        rx = ""
    for c in pattern:
        if c == "*":
            rx += ".*"
        elif c == "^":
            rx += r"([^\w.%-]|$)"
        else:
            rx += re.escape(c)
    return rx


def check(url, name, text):
    for line in text.splitlines():
        line = line.strip()
        # Kommentare, Ausnahmen, Regex-Regeln und kosmetische Regeln überspringen
        if not line or line[0] in "![@/" or "#" in line:
            continue
        pattern, _, options = line.partition("$")
        if any(o.lstrip("~").startswith(SKIP_OPTIONS) for o in options.split(",") if o):
            continue
        if "domain=" in options and not re.search(r"domain=~", options):
            continue  # Regel gilt nur für bestimmte fremde Domains
        if len(pattern) >= 4 and re.search(to_regex(pattern), url, re.I):
            print(f"{name}: {line}")


for url in sys.argv[1:]:
    print(f"== {url}")
    for name, file in LISTS.items():
        with urllib.request.urlopen(BASE + file) as r:
            check(url, name, r.read().decode("utf-8", "ignore"))

Aufgerufen mit der alten und der neuen Adresse:

$ python3 slug-check.py \
    https://example.com/posts/besucherstatistik-ohne-cookie-banner-matomo-log-import/ \
    https://example.com/posts/besucherstatistik-matomo-log-import/
== https://example.com/posts/besucherstatistik-ohne-cookie-banner-matomo-log-import/
AdGuard Cookie Notices: -cookie-banner-$domain=~killthecookiebanner.eu
== https://example.com/posts/besucherstatistik-matomo-log-import/

Die alte Adresse trifft genau eine Regel, die neue keine. Die Listen ändern sich ständig, das Ergebnis gilt also nur für den Tag, an dem man prüft. Bei mir war das der 4. November 2026.

Was ich daraus mitnehme

Der erste Verdacht war plausibel und trotzdem falsch. „matomo in der Adresse“ klang so logisch, dass ich es fast nicht geprüft hätte. Erst die Liste der aktiven Filter und ein stumpfes Durchsuchen haben die echte Ursache gezeigt.

Auch Adressen müssen durch Filter. Ich achte bei Adressen auf Lesbarkeit und Suchbegriffe. Dass sie auch durch Filterlisten müssen, die nur Zeichenketten vergleichen, hatte ich nicht auf dem Schirm. Wörter wie cookie-banner, tracking oder ads mit Bindestrichen drumherum sind in einer Adresse riskant.

Meldungen von Lesern sind Gold wert. Ohne die Nachricht hätte ich das nie bemerkt. In meinen Logs taucht ein gesperrter Abruf gar nicht auf, weil der Browser die Anfrage nie abschickt. Danke an den Leser, der sich die Mühe gemacht hat.

Wie es weitergeht

Bei AdGuard melde ich das nicht. Die Regel ist breit, aber ich habe meine Seite umgebaut und achte in Zukunft selbst darauf, was in meinen Adressen steht.

Dafür überlege ich, den Slug-Check als Schritt in die Pipeline zu hängen, so wie die Prüfung der CSP-Hashes. Dann würde mir ein neuer Artikel mit verdächtiger Adresse schon vor der Veröffentlichung auffallen und nicht erst, wenn jemand auf Mastodon nachfragt.