Sicherheit und Datenverarbeitung
Beaconry verarbeitet zwei sensible Dinge: die API-Credentials, die deine Site mit zehn Ad-Plattformen verbinden, und die Besucher-PII, die Conversions matchen lässt. Diese Seite dokumentiert ganz genau, wie beides geschützt wird, von Anfang bis Ende. Jedes Credential ist at-rest verschlüsselt, zwei der zehn Kanäle lassen deine Site nie ein OAuth-Token halten, jedes Event ist consent-gated, und Besucher-PII wird mit SHA-256 gehasht, bevor sie deinen Server verlässt.
Die Kurzfassung
- Credentials at-rest: jedes API-Secret, jedes Access-Token, jeder OAuth-Bearer und der Lizenzschlüssel werden AES-256-GCM-verschlüsselt in der Datenbank gespeichert, abgeleitet von deinem WordPress-Auth-Salt.
- Google Ads und Microsoft Ads: deine Site hält die OAuth-Tokens nie. Sie liegen in Beaconrys Broker; das Plugin hält nur einen signierten Bearer, der Uploads in deinem Namen autorisiert.
- Consent: keine Analytics-Einwilligung im
nl_pref-Cookie des Besuchers heißt, kein Event verlässt den Browser und kein server-seitiger Dispatch findet statt. Das Gate wird zentral durchgesetzt, einmal. - PII: E-Mail, Telefon, Name, Adresse und der Rest werden normalisiert und gemäß der Matching-Spec jedes Kanals mit SHA-256 gehasht. Die Rohwerte gehen nie über die Leitung.
- Löschung: ein eingebautes Recht-auf-Vergessenwerden-Tool berechnet die exakten Hashes, die jeder Vendor braucht, und protokolliert die Anfrage im Audit-Log, ohne je den Klartext zu speichern.
Credential-Verschlüsselung at-rest (AES-256-GCM)
Jeder Wert, der sich gegenüber einem Drittanbieter authentifizieren könnte, wird verschlüsselt, bevor er die Datenbank berührt. Das deckt das GA4-API-Secret, das Meta-CAPI-Token, das TikTok-Access-Token, das LinkedIn-Access-Token, die Pinterest- / Snapchat- / Reddit-Access-Tokens, die vier X-Ads-OAuth-1.0a-Secrets, beide Ad-Broker-Bearer und den Polar-Lizenzschlüssel ab.
Das Chiffrierverfahren ist AES-256-GCM, authentifizierte Verschlüsselung. GCM gibt dir Vertraulichkeit und Integrität in einem Durchgang: ein Ciphertext, der manipuliert wurde, scheitert beim Entschlüsseln an der Prüfung des Authentication-Tags und wird abgewiesen, statt still zu Müll zu entschlüsseln. Jeder Wert bekommt einen frischen, 12 Byte langen Zufalls-IV, und der 16 Byte lange GCM-Tag wird neben dem Ciphertext gespeichert.
Der Verschlüsselungs-Key wird aus wp_salt('auth') abgeleitet, dem per-Install-Authentication-Salt, den jede WordPress-Site ohnehin in wp-config.php generiert. Das heißt, der Key wird nie mit dem Plugin ausgeliefert und ist nie über zwei Sites hinweg gleich: ein Datenbank-Dump aus einem Install lässt sich ohne die Salts dieses Installs nicht entschlüsseln. Gespeicherte Werte tragen ein encv2:-Präfix, damit das Format selbstbeschreibend ist.
| Eigenschaft | Wert |
|---|---|
| Algorithmus | AES-256-GCM (authentifiziert) |
| Key-Quelle | wp_salt('auth'), pro Install |
| IV | 12 Byte, frisch zufällig pro Wert |
| Auth-Tag | 16 Byte, mit dem Ciphertext gespeichert |
| Gespeicherte Form | encv2: + base64(IV + Tag + Ciphertext) |
Alte CBC-Blobs migrieren beim Lesen
Installs aus der Zeit vor dem GCM-Wechsel speicherten Secrets als AES-256-CBC (ein 16 Byte langer IV, Präfix enc:, kein Authentication-Tag). CBC ohne MAC ist anfällig für Manipulation im Stil eines Padding-Oracle, weshalb Beaconry davon weggewechselt ist. Du musst nichts an alten Werten tun: der Decrypt-Pfad erkennt das Präfix und liest beide Formate, und beim nächsten Speichern dieses Felds wird es als GCM neu verschlüsselt. So verschwinden CBC-Blobs nach und nach aus der Datenbank, während du jede Einstellung anfasst, ohne manuelle Migration und ohne erneutes Verbinden.
Was gar nicht gespeichert wird
Der stärkste Schutz ist nicht, ein Secret zu verschlüsseln, sondern es nie zu halten. Zwei Kanäle sind so gebaut (siehe nächster Abschnitt), und Besucher-PII wird von der Dispatch-Pipeline ohnehin nie persistiert: sie wird im Flug gehasht und weitergeleitet, nicht in eine Beaconry-Tabelle geschrieben. Das Betriebs-Log hält Event-Ergebnisse fest (Health, Dispatch-Fehler, abgewiesene Requests), nie Roh-Event-PII.
Das OAuth-Broker-Vertrauensmodell (Google Ads + Microsoft Ads)
Google Ads und Microsoft Ads brauchen einen vertraulichen OAuth-Client: ein Client-Secret, ein Developer-Token und ein langlebiges Refresh-Token. Die an tausende Kunden-Installs auszuliefern, käme nicht infrage, jede Kopie wäre ein Ort, von dem das Secret leaken könnte. Also liefert Beaconry sie nicht aus. Sie liegen in einem einzigen Confidential-Client-Broker, den Beaconry unter oauth.beaconry.app betreibt.
Deine Site verbindet sich einmal über einen Standard-OAuth-Redirect. Am Ende davon übergibt der Broker deiner Site nicht die Google- oder Microsoft-Tokens. Er erzeugt eine zufällige, opake Site-ID, speichert das Refresh-Token gegen diese ID in seinem eigenen Key-Value-Store und gibt stattdessen einen kurzen signierten Bearer an dein Plugin zurück. Jeder spätere Conversion-Upload von deiner Site sendet diesen Bearer; der Broker verifiziert ihn, schlägt das Refresh-Token nach, erzeugt ein frisches Access-Token, hängt das Developer-Token an und postet in deinem Namen an die Ad-Plattform. Deine wp-config.php und deine Datenbank halten null Google- oder Microsoft-Secrets, nur den Bearer (selbst at-rest verschlüsselt wie jedes andere Credential).
Was der Bearer ist und was er nicht ist
Der Bearer ist ein JWT, signiert mit HMAC-SHA256 (HS256) unter einem Signing-Key, der nur innerhalb des Brokers existiert. Sein Payload ist bewusst winzig: eine opake Site-ID (sid), der Site-Host, ein Issued-at-Zeitstempel und ein Ablauf (Bearer tragen einen Expiry-Claim von rund fünf Jahren; ein abgelaufener Bearer wird abgewiesen). Er ist kein Google-Token, er trägt keinen Google- oder Microsoft-Scope, und er ist gegen jeden Endpoint außer der eigenen Upload-Route des Brokers nutzlos. Die Signatur bedeutet, dass ein Kunde keinen fälschen oder verändern kann; der Broker weist alles ab, dessen HMAC nicht verifiziert.
Hardening, das den Schaden begrenzt, falls ein Bearer leakt
- Tokens reisen nie in einer URL. Für den Ad-Broker-Flow wird der Bearer per Server-zu-Server-POST an den
/redeem-Endpoint des Brokers geholt (der einmalige OAuth-State wird in einem JSON-Body gegen den Bearer getauscht, dann wird der State-Key gelöscht), sodass der Bearer nie in einer Redirect-URL landet, wo er in einer Browser-History, einem Proxy-Log oder einem Referrer-Header auftauchen könnte. - Lazy Account-Pin bei Erstnutzung. Der erste Conversion-Upload, den ein Bearer macht, pinnt ihn an diese Ads-Konto-ID. Jeder spätere Upload muss den Pin treffen, sonst gibt der Broker 403 zurück. Sollte ein Bearer je leaken, könnte ein Angreifer ihn nicht auf ein anderes Ads-Konto richten, das er zufällig kontrolliert, um Conversions einzuschleusen oder abzugreifen. Der Pin wird beim Trennen geleert, sodass ein sauberes erneutes Verbinden ein frisches Pin-Fenster startet.
- Einbahn-Revoke. Das Trennen im Tab Tracking weist den Broker an, das gespeicherte Refresh-Token zu widerrufen und zu löschen und den Pin zu leeren, dann wird der Bearer lokal geleert. Es gibt von der Plugin-Seite keinen Weg zurück zu den Tokens.
Die acht anderen Kanäle (GA4, Meta, TikTok, LinkedIn, Pinterest, Snapchat, Reddit, X Ads) authentifizieren sich mit einem Token, das du direkt einfügst und das dann wie oben beschrieben at-rest verschlüsselt wird. Sie nutzen den Broker nicht, weil ihre APIs ein langlebiges Token oder eine Per-Call-Signatur akzeptieren, die sich sicher verschlüsselt auf deinem eigenen Server halten lässt. LinkedIns 60-Tage-Token zeigt zusätzlich einen Tage-verbleibend-Zähler, damit du dich neu authentifizierst, bevor es abläuft.
Das Consent-Gate
Beaconry dispatcht ohne Einwilligung gar nichts. Der mitgelieferte Consent-Banner (nl-data-gate) schreibt ein nl_pref-Cookie, das die Wahl des Besuchers über die Kategorien funktional, Analytics und Marketing festhält. Zwei Prüfungen lesen es:
- Analytics-Gate am REST-Endpoint. Jedes Browser-Event postet an deinen eigenen Same-Origin-Endpoint. Bevor irgendetwas anderes passiert, liest der Endpoint
nl_prefund verwirft das Event still, sofern dasanalytics-Flag nicht gesetzt ist. Keine Einwilligung, kein Event, Punkt. Das ist kein kosmetischer Banner über einem Tracker, der trotzdem feuert, die Daten verlassen den Browser physisch nicht, bis der Besucher zustimmt. - Marketing-Gate am First-Party-Match-Cookie. Das optionale Cross-Event-Match-Feature (das sich die gehashten Match-Keys eines Besuchers zwischen, sagen wir, einem Formular-Submit und einem späteren Checkout merkt, um die Match-Qualität zu heben) schreibt sein First-Party-Cookie nur, wenn das separate
marketing-Flag gesetzt ist. Analytics-Einwilligung allein triggert es nicht.
Server-seitige Aufrufer (eine Formular-Submission, ein WooCommerce-Kauf) fragen auf ihrer eigenen Ebene nicht erneut nach, weil der Besucher bereits in einem Kontext gehandelt hat, in dem Consent gilt. Consent wird einmal durchgesetzt, zentral, im Dispatcher, dasselbe Gate, das Commerce- und Custom-Events abdeckt. Der Nettoeffekt: ein Ort zum Nachvollziehen, statt einer Streuung von Per-Kanal-Prüfungen, die auseinanderdriften können.
Wenn du deine eigene Consent-Management-Plattform betreibst, richte sie auf dasselbe nl_pref-Cookie, und Beaconry respektiert die Entscheidung deiner CMP ohne einen zweiten Banner.
Wie Besucher-PII gehasht wird
Wenn ein Event Userdaten trägt (ein Formular-Lead, ein Kauf mit Rechnungsadresse), hasht Beaconry sie vor der Übertragung pro Kanal. Die Advertiser-Match-Felder, die es erkennt, sind E-Mail, Telefon, Vorname, Nachname, Stadt, Bundesland, PLZ, Land, Geschlecht, Geburtsdatum und eine externe Kunden-ID.
Gehasht wird mit SHA-256 über einen normalisierten Wert. Die Normalisierung zählt genauso viel wie der Hash: derselbe logische Wert muss denselben Digest erzeugen, egal aus welcher Quelle er kam, sonst matchen Audiences nie. Also vor dem Hashen:
- setzt Beaconry den Wert auf Kleinschreibung und trimmt ihn (die Baseline-Regel, die die Plattformen vorgeben),
- reduziert eine Telefonnummer auf E.164-Ziffern (keine Leerzeichen, kein führendes Plus), damit eine
+49 170 1234567und eine0170 1234567zusammenfinden, - kanonisiert Land, Bundesland und PLZ, damit ein deutsches Bundesland, angegeben als
Niedersachsen,NIoderni, über Meta, TikTok, Pinterest und Snap zu einem Audience-Eintrag auflöst, - reduziert Geschlecht auf ein einzelnes
f/mund das Geburtsdatum aufYYYYMMDDvor dem Hash.
Jeder Kanal erhält dann die gehashten Felder in genau den Key-Namen und der Form, die seine CAPI erwartet (Metas em / ph / fn / ..., GA4s sha256_email_address, LinkedIns gehashte User-ID-Matcher und so weiter). Die rohen, ungehashten Werte werden nie an irgendeine Plattform gesendet und nie von Beaconry gespeichert. Ein Feld, das du im Tab Forms auf Don't map setzt, wird vollständig vom Hashing ausgeschlossen, selbst wenn sein Name noch nach PII aussieht, siehe die Forms-Dokumentation für den Per-Field-Skip-Marker.
Endpoint-Hardening
Der Same-Origin-Endpoint, der Browser-Events empfängt, ist die einzige extern erreichbare Fläche, also ist er entsprechend abgeriegelt. Er antwortet unabhängig vom Ergebnis immer mit 204 No Content, damit ein Probe ein verworfenes Event nicht von einem akzeptierten unterscheiden kann. Zusätzlich zum Consent-Gate erzwingt er:
- Eine rotierende Nonce. Jedes Event muss eine gültige WordPress-Nonce (12-Stunden-Rotation) tragen, die die Seite ausgegeben hat. Ein nackter POST aus dem offenen Internet ohne die Nonce wird verworfen. Das ist es, was einen Fremden davon abhält, gefälschte Conversions zu skripten, um dein Ad-Plattform-Kontingent zu verbrennen oder deine GA4-Reports zu vergiften. Lange offene oder gecachte Seiten holen automatisch eine frische Nonce, damit legitimes Tracking nie ausfällt.
- Ein Bot-Filter. Offensichtlicher Crawler-, Preview- und Scanner-Traffic wird verworfen, bevor er auch nur eine Ad-Plattform berühren kann, was Kontingent und Daten sauber hält. Für ungewöhnliche Setups ist er filterbar.
- Ein Per-IP-Rate-Limit. Eine weiche Obergrenze (standardmäßig 600 Events pro Minute, filterbar) fängt missbräuchliche Bursts weit über dem Tempo jedes echten Besuchers ab. Der Bucket-Key ist ein Hash der IP, sodass keine Roh-IP persistiert wird.
- Spoofing-resistente Client-IP. Proxy-Header wie
X-Forwarded-ForundCF-Connecting-IPwerden nur dann vertraut, wenn die direkte Verbindung tatsächlich aus einer Cloudflare-Edge-Range kommt. Ein Direct-Connect-Angreifer kann keinen Header fälschen, um dem Rate-Limit auszuweichen, weil sein gespoofter Wert zugunsten der echten verbindenden Adresse verworfen wird. - Kein Caching. Der Endpoint markiert seine Antworten als no-store, sodass ein CDN oder Full-Page-Cache nie eine veraltete Nonce ausliefern oder ein Event verschlucken kann.
DSGVO-Recht-auf-Vergessenwerden-Tool
Wenn ein Besucher eine "lösch meine Daten"-Anfrage schickt, ist der heikle Teil nicht deine eigene Datenbank (Beaconry speichert dort keine Besucher-PII), es sind die zehn Ad-Plattformen, an die du gehashte Daten weitergeleitet hast, jede mit ihrer eigenen Lösch-UI, die gehashten Input nimmt. Beaconrys Tool im Tab Advanced schließt diese Lücke.
Du gibst die E-Mail und / oder das Telefon des Besuchers ein. Das Tool berechnet die SHA-256-Hashes mit genau derselben Normalisierung, die die Dispatcher zur Upload-Zeit nutzen, sodass der Hash, den du in die Privacy-UI eines Vendors einfügst, mit dem Hash übereinstimmt, den dieser Vendor gespeichert hat. Es rendert dann eine Workflow-Karte pro Ziel (nur für die Kanäle, die du tatsächlich konfiguriert hast), sodass du nicht durch zehn Vendor-Docs suchen musst, um jedes Lösch-Formular zu finden.
Entscheidend: die Anfrage wird für deinen eigenen Compliance-Nachweis im Audit-Log protokolliert, aber das Log speichert nur die Hashes und die WordPress-User-ID des Admins, der es ausgeführt hat, nie die Klartext-E-Mail oder das Klartext-Telefon. Das Klartext-Ergebnis wird einmal gezeigt, in einem Einmal-Transient, das beim nächsten Seitenaufruf konsumiert und gelöscht wird. So schafft das Ausführen einer Lösch-Anfrage nicht selbst einen neuen Ort, an dem die Roh-PII des Besuchers herumliegt.
Power-User-Kontrollen
Zwei Schalter lohnt es sich für gehärtete oder Compliance-getriebene Setups zu kennen, beide in wp-config.php gesetzt:
- Credentials komplett aus der Datenbank heraus. Jedes Kanal-Credential kann als
wp-config.php-Konstante geliefert werden, statt über die UI. Wenn die Konstante definiert und nicht leer ist, gewinnt sie beim Lesen über den gespeicherten Wert, und das Feld rendert im Admin gesperrt. Das hält Secrets in deinem Infrastructure-as-Code oder deiner Server-Config statt in der WordPress-Options-Tabelle, nützlich, wenn die Datenbank breiter gesichert oder repliziert wird als die Codebasis. - PII-Persistenz deaktivieren. Das Cross-Event-Match-Cookie kann abgeschaltet werden, wenn deine Datenschutzerklärung das Persistieren von Besucher-Match-Keys nicht erlaubt, selbst verschlüsselt und selbst consent-gated. Server-seitiges Conversion-Tracking funktioniert auch ohne es weiter, du tauschst ein Stück Match-Qualität gegen null PII-Persistenz.
Eine Anmerkung zu Beaconrys eigener Marketing-Site: sie liefert kein Tracking-Pixel, kein Analytics und keinen Cookie-Banner. Server-seitiges Privacy-Tooling zu verkaufen und dann ein Drittanbieter-Pixel auf deine Besucher zu droppen wäre ein Widerspruch, also tun wir es nicht.