npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

cuonztech-privacy-chaos-gate

v0.3.12

Published

CuonzTech Privacy Chaos Gate — CI/CD Consent-Scanner, Chaos-Szenarien und Auto-Fix-PR

Readme

CuonzTech Privacy Chaos Gate (CPCG)

CI-Gate für Consent-Tracker: stresst den Consent-Banner mit 16 Chaos-Szenarien, findet Requests vor der Einwilligung und nach dem Ablehnen, bewertet sie je Rechtsraum und schlägt den Fix vor: als Pull Request für OneTrust und Cookiebot, sonst als Anleitung. Der Report zeigt auch die Gegenrichtung (welche Tracker nach dem Akzeptieren laden); beim Re-Scan der Vorschau (--verify-url) ist sie die Gegenprobe für «Fix verifiziert».

Hinweis: Technisches Hilfsmittel, keine Rechtsberatung, keine Garantie für revDSG-/DSGVO-/TTDSG-Konformität. / Technical aid, not legal advice.

Installieren

Voraussetzung: Node.js 20 oder neuer.

# 1. CLI installieren (Befehl: cpcg)
npm install -g cuonztech-privacy-chaos-gate     # oder im Projekt: npm install -D cuonztech-privacy-chaos-gate

# 2. Prüf-Browser installieren (einmalig pro Rechner)
npx playwright install chromium

# 3. Ersten Scan starten (ohne Lizenz zeigt das Ergebnis nur Zahlen, Ziele auf localhost sind vollständig frei)
cpcg --url https://staging.example.com

Ohne Installation: npx cuonztech-privacy-chaos-gate --url https://staging.example.com (der Browser aus Schritt 2 muss trotzdem vorhanden sein; auf Linux-CI zusätzlich mit Systembibliotheken: npx playwright install --with-deps chromium). Fehlt der Browser, meldet das CLI das und beendet sich mit Exit 3. Gebraucht wird das vollwertige Chromium von Playwright, nicht nur die Headless-Shell: playwright install chromium installiert beides; eine Installation mit --only-shell reicht nicht (Meldung «Browser fehlt»).

Das CLI hat keine Subcommands: alle Angaben sind Optionen von cpcg.

Lizenz und Preise

Das Paket ist proprietär (Datei LICENSE, Revision 4 ab 0.3.9: Gebrauchsüberlassung; das Paket enthält lesbaren JavaScript-Code, der TypeScript-Quelltext ist nicht öffentlich). Jede Version steht unter der Lizenz, die ihrem Paket beiliegt; 0.1.0 und 0.2.0 stehen unter MIT. Online-Kauf → Code per Mail.

export CPCG_LICENSE="CPCG-…"      # Kunden-Key CPCG-K-… (Prüfpaket und Abo; ältere Offline-Lizenzcodes CPCG-… gelten weiter)
cpcg --url https://staging.example.com --jurisdiction CH

In CI die Lizenz als Secret setzen und per Umgebungsvariable CPCG_LICENSE übergeben, nicht als Argument.

Ohne gültige Lizenz (keine hinterlegt, ungültig, abgelaufen, gesperrt, oder Lizenzdienst nicht erreichbar ohne gültige Offline-Freigabe) läuft der Scan vollständig, und Gate-Zustand und Exit-Code sind echt. Ausgegeben wird nur das Ergebnis in Zahlen, zum Beispiel «BLOCKED (3 Befundgruppen, 47 Requests in 6 Szenarien)», dazu der Grund und der Weg zur Lizenz. Es gibt keine Tracker-Namen, URLs und Belege, keine Fix-Muster, keinen Auto-Fix, keine Reports (Markdown, HTML, PDF), kein HAR; report.json enthält nur Zustand und Zahlen. Mit --report-pdf entsteht zusätzlich ein einseitiges Kurzergebnis-PDF (<name>-kurzergebnis.pdf) mit denselben Zahlen und dem Weg zur Lizenz, ohne Anbieter, Adressen und Belege. Die Action liefert ihre Outputs (gate_state, leak_requests, exit_code) unverändert. Ziele auf localhost, 127.0.0.1 und ::1 sind immer vollständig frei. Ist der Lizenzdienst nicht erreichbar, gilt eine zuvor erhaltene, noch gültige Freigabe (Cache nach der ersten Online-Prüfung); ohne sie wird gekürzt. Den Lizenzstatus bestimmt der Gate-Lauf selbst, auch bei Nutzung als Bibliothek (new PrivacyChaosGate().run(config, { licenseKey })); ein mitgegebenes license in der Konfiguration wird nicht übernommen, und ohne Lizenz enthält der Rückgabewert keinen Bericht, nur Zustand, Exit-Code und Zahlen (limited). Ohne Lizenz wirken zudem --ignore, --deep-state und eine externe --verify-url nicht. Lokale Ziele ohne Lizenz bleiben lokal: Navigationen und Weiterleitungen auf externe Adressen bricht der Scanner ab.

Mit gültiger Lizenz: vollständige Konsole, report.json mit allen Befunden, Evidence-Report (Markdown, HTML), PDF-Report, Kundenreport, HAR, Auto-Fix lokal, als Commit oder Pull Request.

| Funktion | Option | |----------|--------| | PDF-Report sowie Kundenreport (HTML/Markdown mit --client) | --report-pdf, --client | | Auto-Fix lokal, als Commit oder Pull Request | --auto-fix, --git-commit, --create-pr | | Evidence-Report, HAR | (Standard), --har |

| Plan | Preis (netto, CHF) | Umfang | |------|--------------------|--------| | Prüfpaket | 890 einmalig | Kundenreport, Auto-Fix-PR, 1 Domain, 90 Tage | | Pro | 69 je Domain und Monat | Alle Details und Reports, Kundenreport, Auto-Fix-PR (bei OneTrust/Cookiebot; sonst Anleitung) | | Agency | 349 je Monat | Pro für bis zu 10 Domains |

Die Domain-Anzahl beruht auf Vertrauen: das CLI zählt Domains nicht. Scans werden nicht gezählt. Monatlich kündbar. Kauf und Fragen: [email protected].

Was das Tool prüft

16 Chaos-Szenarien in einem Lauf — jedes mit ehrlichem Status (bestanden / auffällig / nicht prüfbar). Ein Button, der nicht gefunden wird, wird nie zu einem grünen Häkchen.

| # | Szenario | Frage | |---|----------|-------| | 1 | happy-path-denied | Bleibt Tracking nach «Ablehnen» aus? | | 2 | happy-path-accepted | Ist vor dem Klick Ruhe — und läuft erlaubtes Tracking danach an? | | 3 | fast-click | Rennen: Link vor dem Banner geklickt, und Ablehnen sofort beim Erscheinen (ohne auf ein fertiges CMP zu warten, bei Bedarf doppelt): Geht die Ablehnung verloren, feuern danach Tracker, auch nach Neuladen? | | 4 | state-manipulation | Tracking nach Storage-Wipe? Fragt die Seite danach erneut? Wird eine gezielt beschädigte Ablehnung als Zustimmung gelesen? | | 5 | network-drop | Tracker trotz blockiertem CMP? Ein Banner, das schon ohne JavaScript im HTML steht (kein CMP-Skript, das ausfallen könnte), gilt als «nicht anwendbar»; ein per Skript gebautes Banner eines unbekannten Anbieters bleibt «nicht prüfbar». | | 6 | consent-revoked | Stoppt Tracking nach Widerruf? Welche IDs bleiben liegen? | | 7 | category-selective | Nur Statistik erlaubt — bleiben Marketing-Tracker aus? | | 8 | consent-persistence | Überlebt die Ablehnung Reload, bis zu 3 Unterseiten und Neustart? | | 9 | banner-ignored | Starten Tracker per Timer/Scroll ohne Klick? | | 10 | subpage-spa | Greift der Schutz auf Unterseiten und bei SPA-Navigation? | | 11 | cmp-failure | Tracking-Zeitfenster bei verzögerter/defekter CMP? Bei einem Banner ohne CMP-Skript im HTML «nicht anwendbar» (wie network-drop). | | 12 | embed-leaks | Kontaktieren YouTube/Maps/Social-Embeds vor Freigabe? | | 13 | consent-mode-v2 | Decken sich Consent-Mode-Zustände mit Cookies/Requests? Welche Signale (gcs/gcd) senden die Google-Tags vor und nach der Entscheidung: «erteilt» trotz Ablehnen, Standard «erteilt», cookielose Pings? | | 14 | duplicate-events | Doppelte page_view-Events (Plugin + GTM parallel)? | | 15 | mobile-usability | Sind Ablehnen/Einstellungen auf dem Handy bedienbar? | | 16 | gpc-signal | Beachtet die Seite das Opt-out-Signal Global Privacy Control (Sec-GPC: 1) vor jeder Entscheidung? Vergleich mit einem Lauf ohne GPC. Werbe-Tracker trotz GPC blockieren nur nach US-CA, sonst Hinweis. |

Tracker: rund 245 Anbieter über 252 Tracker-Hosts, 27 präzise Host-/Pfad-Muster und 1209 Cookie-Muster (u. a. Google Analytics und Tag Manager, Meta, TikTok, LinkedIn, Adobe, Piano, Hotjar, Criteo, The Trade Desk, Taboola, Awin, HubSpot, Matomo), dazu CNAME-Cloaking (eigene Subdomain, die per DNS auf einen Tracker zeigt). Fremde Hosts ohne Zuordnung listet der Report ohne Bewertung. Umfang, Quellen, Regeln gegen Fehlalarme und Messung: docs/TRACKER-ABDECKUNG.md.

CMPs: OneTrust, Cookiebot, Usercentrics, Didomi, iubenda, Axeptio, Sourcepoint, Tealium, etracker, JustRelate, Consentmanager, Borlabs, Klaro, CookieYes, Complianz, Cookie Script, CookieFirst, Termly, TrustArc, Osano, die WordPress-Banner Cookie Notice, CookieLawInfo und Moove GDPR sowie cookieconsent (Open Source); erkannt zusätzlich Quantcast Choice, CCM19 und Real Cookie Banner. Andere Banner bedient die Textklassifikation (de/en/fr/it). Details und Messung: docs/CMP-ABDECKUNG.md.

Output: der Prüfbericht

Jeder Scan schreibt ein Evidence-Paket:

reports/privacy-chaos-report.json           LeakReport (maschinenlesbar), Pfad per --report-output
reports/privacy-chaos-report-evidence.md    Evidence-Report (Markdown); mit Lizenz und --client als Kundenreport
reports/privacy-chaos-report-evidence.html  Evidence-Report (HTML, CuonzTech-Brand); mit Lizenz und --client als Kundenreport
reports/privacy-chaos-report-kundenbericht.pdf     Kundenbericht als PDF (mit --report-pdf, nur mit Lizenz): Zusammenfassung, Anleitung für die Webagentur, technischer Nachweis
reports/evidence/                           Screenshots (portabel, relative Pfade)

Die Dateinamen richten sich nach dem JSON-Pfad: <name>.json ergibt <name>-evidence.md, <name>-evidence.html und <name>-kundenbericht.pdf im selben Ordner, die Screenshots liegen in evidence/ daneben. Standard ist reports/privacy-chaos-report.json; mit --report-output reports/scan.json heissen die Dateien reports/scan-evidence.md usw.

Der Kundenbericht (-kundenbericht.pdf, etwa 7 bis 8 Seiten) hat drei Teile: (1) Zusammenfassung für die Geschäftsleitung in Alltagssprache, höchstens drei Punkte mit «Gemessen:»-Angaben, (2) eine Anleitung für die Webagentur, zugeschnitten auf den im Quelltext erkannten Seitenaufbau (WordPress mit Pixelmate oder anderem Cookie-Tool, WordPress ohne Tool; sonst eine als allgemein gekennzeichnete Anleitung) samt dem echten Code der Seite, (3) der technische Nachweis mit Zeitabfolge, Prüfprotokoll und Messumgebung. Das ausführliche Evidence-HTML bleibt als Beilage. Der Bericht behauptet nur Gemessenes und ist keine Rechtsberatung.

Der Report führt Befunde (ausgelöste Regeln), Hinweise und beobachtete Requests (Timelines) getrennt — und ordnet jedes Ergebnis mehreren Rechtsräumen zu (CH, EU, UK, US-CA, BR, ROW). Keine globale Compliance-Aussage.

Hinweise sind kein Blocker. Rein funktionale Drittanbieter (etwa Google Fonts) und cookielose Reichweitenmessung (etwa Plausible, Fathom) erscheinen als Hinweis und blockieren das Gate nicht. Ebenso Befunde, die nach dem gewählten Rechtsraum nur Hinweis sind (zum Beispiel Tracker vor der Einwilligung mit --jurisdiction US-CA, Opt-out-Modell): Sie stehen mit ihrer Einordnung in der Liste «Hinweise», nicht im Befundregister. Ein Szenario, das nur wegen solcher Hinweise auffiel, gilt als «bestanden nach US-CA (opt-out)». Mit --strict blockieren auch Hinweise.

Nicht prüfbar heisst: mit Grund. Der Report führt je nicht prüfbarem Szenario den Grund in einer eigenen Liste «Warum nicht prüfbar» (auch in der Konsole, in der Step-Summary und in report.json unter gate.uncheckableReasons). Das Deckblatt nennt nur Prüfthemen, deren Szenarien alle tatsächlich geprüft wurden, und den Rechtsraum.

Bot-Schutz. Zeigt die Zielseite dem Browser eine Challenge- oder Sperrseite (etwa Cloudflare, DataDome, Akamai, AWS WAF, Imperva, PerimeterX) oder ein Rate-Limit (HTTP 429), sind die betroffenen Szenarien nicht prüfbar. Report-Kopf, Konsole und Step-Summary nennen das gesondert, report.json unter botProtection. Das ist kein Befund über Consent. Skripte dieser Anbieter auf einer normal geladenen Seite (etwa Cloudflare Turnstile im Formular) lösen nichts aus. Ein Lauf, in dem der Bot-Schutz die Kernszenarien verhindert, endet immer mit Exit 4, auch mit --fail-on-uncheckable false. Abhilfe: Scan-Rechner bzw. CI-Runner beim Bot-Schutz freigeben oder eine Staging-URL ohne Bot-Schutz scannen.

Browser-Kennung. CPCG scannt mit dem vollwertigen Chromium und meldet sich wie ein gewöhnliches Chrome derselben Version (User-Agent und Client-Hints ohne «Headless»), damit die Seite ihren Banner so ausliefert wie an echte Besucher. report.json nennt die verwendete Kennung unter browserUserAgent. Bot-Schutz wird nicht umgangen (navigator.webdriver bleibt gesetzt); er führt wie oben beschrieben zu «nicht prüfbar».

Besucherprofil und Land. Seiten zeigen je nach Herkunft des Besuchers andere Banner. Gemessen an Schweizer Seiten (November 2026) entscheidet dabei die IP-Adresse, nicht Sprache oder Zeitzone des Browsers. CPCG trennt deshalb: --visitor ch|eu setzt Sprache und Zeitzone fest (Standard ch; vorher galten die des Rechners, auf einem GitHub-Runner meist Englisch/USA), damit Läufe vergleichbar sind. Einen Besucher aus einem anderen Land simuliert erst --proxy mit einem Ausgang dort. Mit --visitor eu oder --proxy misst CPCG das Land der eigenen IP über https://www.cloudflare.com/cdn-cgi/trace (ein Abruf, nur in diesen Fällen) und schreibt es in den Report (visitor); passt es nicht zum Profil, steht eine Warnung dabei. Der Proxy kommt nie aus einer .env: ein Proxy könnte Tracker-Requests verschlucken und so das Gate beeinflussen. Die Erreichbarkeitsprüfung vor dem Scan läuft ohne Proxy.

Fortschritt und Zeitlimit. Während des Scans zeigt die Konsole je Szenario eine Zeile («[3/17] fast-click (nach 52 s)»). Jedes Szenario und jede Vor- und Schlussprüfung hat eine Obergrenze: je Szenario seine Navigationen mal --timeout-ms (mindestens 120 s, Unterseiten zuzüglich ihres Zeitbudgets), je Vorprüfung drei Navigationsfristen (mindestens 60 s). Reagiert die Seite nicht mehr, wird das Szenario abgebrochen und «nicht prüfbar (Zeitlimit)»; bis dahin erfasste Befunde bleiben im Bericht, der Scan läuft mit dem nächsten Szenario weiter. Ein abgebrochenes Szenario gilt nie als bestanden. Trifft es ein Kernszenario, endet das Gate mit Exit 4 (unvollständig); trifft es ein Zusatzszenario, bleibt das Ergebnis als unvollständig markiert, mit Grund. Auf einer Seite ohne Banner verhindert jeder Abbruch das «nichts zu prüfen» (Exit 4).

Code-Lösungen sind Muster. Werte in {{…}} (etwa {{GA4_MESS_ID}}) sind Platzhalter, die Legende darunter sagt, was hingehört. Bekannte, bewusst akzeptierte Befunde lassen sich mit --ignore ausnehmen; sie stehen im Report weiterhin als «ignoriert».

Flags

cpcg --url https://staging.example.com \
  --report-pdf \
  --client "Firma AG" \
  --jurisdiction CH \
  --report-output reports/scan.json

| Option | Wirkung | |--------|---------| | --url <url> | Ziel (Pflicht) | | --license <code> | Lizenzcode oder Kunden-Key; besser per Env CPCG_LICENSE | | --jurisdiction <code> | CH, CH-SENSIBEL, EU, UK, US-CA, BR oder ROW | | --report-output <pfad> | JSON-Report (weitere Dateien daneben) | | --report-pdf [boolean] | Kundenbericht als PDF (Zusammenfassung, Anleitung, technischer Nachweis) — Lizenz erforderlich | | --client <name> | Kundenname für Kundenreport und Deckblatt — Lizenz erforderlich | | --demo [boolean] | Report als synthetische Testdaten kennzeichnen | | --fail-on-leak [boolean] | Exit 1 bei Leaks (CI-Gate, Standard true) | | --fail-on-uncheckable [boolean] | Exit 4, wenn Kernszenarien nicht prüfbar sind (Standard true); bei false Exit 0, das Ergebnis bleibt als unvollständig markiert | | --strict [boolean] | Auch Hinweise (z. B. Google Fonts) blockieren das Gate (Standard false) | | --ignore <liste> | Befunde ausnehmen: Tracker-Name oder Host-Muster, kommagetrennt oder mehrfach (fonts.googleapis.com,*.hotjar.com); im Report als «ignoriert». */** und nackte TLDs (com, ch) werden immer abgelehnt, auch mit --allow-broad-ignore (zum Abschalten der Blockierung dient --fail-on-leak false); ganze Zonen (*.com, .ch) nur mit --allow-broad-ignore. Die Konsole nennt, wie viele Befunde ausgenommen wurden; ein Szenario, dessen Befunde alle ausgenommen sind, steht als «bestanden (Befunde ignoriert)» | | --allow-broad-ignore [boolean] | Ganze Zonen als --ignore-Muster (*.com, *.co.uk, .ch) bewusst zulassen; sie erscheinen in Konsole und Report als Warnung. * und nackte TLDs bleiben abgelehnt (Standard false) | | --accept-selector <css> | Eigener Selektor des Akzeptieren-Buttons, wenn die Erkennung ihn nicht findet (mehrfach oder kommagetrennt) | | --reject-selector <css> | Eigener Selektor des Ablehnen-Buttons | | --settings-selector <css> | Eigener Selektor des Einstellungen-Buttons | | --banner-frame <teilstring> | Teil der URL des iframes, in dem der Banner liegt (CMP im Frame); findet der Scan keinen solchen Frame, warnt er | | --banner-wait <sekunden> | Wartefenster auf einen spät erscheinenden Banner, 1 bis 60 (Standard 8; Umgebung CPCG_BANNER_WAIT). Mit Angabe wird das Fenster ausgeschöpft, ebenso bei --accept-selector/--reject-selector; die Schlussprüfung wartet zusätzlich eine Nachfrist | | --visitor <profil> | Besucherprofil: ch (Sprache de-CH, Zeitzone Zürich, Standard) oder eu (de-DE, Berlin); feste Sprache und Zeitzone statt der des Rechners (Umgebung CPCG_VISITOR). Das Land der IP ändert nur --proxy (siehe «Besucherprofil und Land») | | --proxy <url> | Proxy für alle Browser-Zugriffe, z. B. ein Ausgang in der EU: http(s)://[user:pass@]host:port oder socks5://host:port (Umgebung CPCG_PROXY, nie aus .env). Report und Konsole nennen den Proxy ohne Zugangsdaten und das gemessene IP-Land | | --max-subpages <n> | Obergrenze der Unterseiten-Stichprobe (Szenario subpage-spa), 1 bis 40 (Standard 12; Umgebung CPCG_MAX_SUBPAGES); das Zeitbudget wächst moderat mit (45 s bis 12, 90 s bis 24, sonst 120 s) | | --sitemap | Sitemap-Tiefenlauf: subpage-spa prüft Unterseiten aus robots.txt/sitemap.xml (auch Index und gzip) zusammen mit den Links der Startseite, reihum nach Pfadbereich, bis --deep-budget verbraucht ist; --max-subpages gilt dann nicht (Umgebung CPCG_SITEMAP) | | --deep-budget <s> | Zeitbudget des Tiefenlaufs für die Unterseiten, 30 bis 3600 s (Standard 300; Umgebung CPCG_DEEP_BUDGET) | | --deep-state <pfad> | Zustandsdatei des Tiefenlaufs: der nächste Lauf besucht nur die noch offenen Unterseiten; sind alle besucht, beginnt eine neue Runde (Umgebung CPCG_DEEP_STATE) | | --baseline <pfad> | Report (JSON) eines früheren Laufs: der Bericht nennt neue, weggefallene und unveränderte Befunde (je Regel, Tracker und Host) und geänderte Szenario-Status; warnt bei anderer Ziel-URL oder Scan-Konfiguration (Umgebung CPCG_BASELINE) | | --har | HAR-Mitschnitt je Browser-Kontext ohne Inhalte nach evidence/har/<Lauf>/ (Adressen, Header, Zeiten; Umgebung CPCG_HAR). Achtung: Die Dateien enthalten Cookies und Set-Cookie-Header der Zielseite und der Drittanbieter; in öffentlichen Repos nicht als Artefakt hochladen | | --verify-url <url> | Geschlossener Kreis: hat die Ziel-URL Befunde, prüft CPCG zusätzlich diese URL (z. B. die Vorschau des Fix-PRs). «Fix verifiziert» (Zustand remediated, Exit 0) nur, wenn dort nichts leakt, die Kernszenarien und alle Szenarien mit Befund geprüft wurden und kein Bot-Schutz greift; sonst bleibt das Gate BLOCKED mit Grund (Umgebung CPCG_VERIFY_URL) | | --verify-wait <s> | So lange auf die Erreichbarkeit der Verify-URL warten, 0 bis 1800 s (Standard 0; Umgebung CPCG_VERIFY_WAIT) | | --target-header <"Name: Wert"> | Freigabe-Header nur für die geprüfte Website (mehrfach; Umgebung CPCG_TARGET_HEADERS, eine Zeile je Header): z. B. eine WAF-Ausnahme gegen Bot-Schutz auf der eigenen Seite oder x-vercel-protection-bypass: … für eine geschützte Vorschau. Geht nur an die Origins des Ziels bzw. der Verify-URL, nie an Drittanbieter und nie hinter eine Weiterleitung auf eine fremde Origin (auch nicht an eine Domain, auf die die Seite später weiterleitet); der Wert erscheint in keinem Report und keiner HAR-Datei. Nebenwirkung: Der Scanner holt Dokumente der eigenen Origin dann selbst; der HTTP-Cache des Browsers ist aus, und die Chrome-Sperre für Anfragen ins lokale Netz ist abgeschaltet, damit Staging-Seiten im internen Netz normal laden | | --auto-fix [boolean] | Patches erzeugen und im Quellverzeichnis anwenden (Standard true nur mit ausdrücklichem --source-dir) | | --source-dir <dir> | Quellcode für Auto-Fix; ein nicht vorhandenes Verzeichnis ist ein Fehler (Exit 3). Für --git-commit/--create-pr gilt das Git-Repository, in dem es liegt (nicht das Arbeitsverzeichnis) | | --git-commit [boolean] | Fix in den Git-Branch committen — Lizenz erforderlich | | --create-pr [boolean] | Fix als Pull Request öffnen (Token in Env GITHUB_TOKEN, ersatzweise --github-token) — Lizenz erforderlich; hat Vorrang vor --git-commit | | --base-branch <branch>, --branch-prefix <prefix> | Basisbranch und Branch-Präfix des Fix-PRs | | --llm-remediation [boolean] | Opt-in: Quellcode darf an den LLM-Anbieter gesendet werden (Standard false) | | --allow-custom-banner-fix [boolean] | Opt-in: Auto-Fix darf auch bei eigenem Banner Skripte ändern. Standard false: dann nur Anleitung (cpcgGrantConsent()), kein Patch | | --llm-provider, --llm-model, --api-key | Nur openai ist umgesetzt; andere Anbieter fallen auf die Regel-Engine zurück | | --headless [boolean], --timeout-ms <ms> | Browser-Modus und Timeout je Szenario-Navigation; daraus abgeleitet die Obergrenze je Szenario (siehe «Fortschritt und Zeitlimit») |

Alle Optionen und Standardwerte: cpcg --help.

Exit-Codes (für CI): 0 ok · 1 Leaks gefunden (auch wenn ein Fix nur vorgeschlagen, aber nicht verifiziert ist) · 2 nicht vergeben (eine fehlende oder ungültige Lizenz beendet den Lauf nicht, sie kürzt die Ausgabe auf Zahlen) · 3 Fehler (Ziel unerreichbar oder HTTP 4xx/5xx ausser 403/429, Konfiguration, Browser fehlt, falsche Argumente; bei 403/429 versucht der Browser-Scan es trotzdem, siehe Bot-Schutz) · 4 unvollständig, Kernszenarien nicht prüfbar. Ein Lauf, der nichts abschliessend prüfen konnte, ist nie grün: --fail-on-uncheckable (Standard) macht daraus Exit 4. Ausnahme: Hat der Scan positiv festgestellt, dass die Seite keinen Consent-Banner hat, und traten weder Tracker noch Befunde noch Bot-Schutz auf, gilt das Gate als bestanden (Exit 0, «nichts zu prüfen»); die übrigen Szenarien bleiben als «nicht prüfbar» aufgeführt. Banner gesehen, aber nicht bedienbar, Banner zu spät, instabile Seite oder unbekannt bleiben Exit 4.

Unterseiten-Stichprobe

Das Szenario subpage-spa prüft höchstens --max-subpages verlinkte Unterseiten (verschiedene erste Pfadsegmente zuerst, Impressum und Datenschutz zuletzt). Sind mehr Unterseiten verlinkt als geprüft wurden, steht das nicht nur im Report: Die Konsole meldet «Unterseiten: X von Y geprüft — nicht geprüfte Routen können weitere Befunde enthalten; --max-subpages erhöhen», report.json führt es unter gate.sampling, die Step-Summary und der Report-Kopf zeigen «PASS, aber Stichprobe», und gate.incomplete ist true. Der Exit-Code bleibt bei PASS 0 (nicht 4).

Mit --sitemap meldet das Gate den Fortschritt der Runde: «Unterseiten (Sitemap-Tiefenlauf): 120 von 300 besucht, über 3 Läufe». Gezählt werden besuchte Kandidaten (auch Fehlerseiten); die Befunde eines Laufs stehen in dessen eigenem Report. Gelesen werden höchstens 25 Sitemap-Dateien, 5000 Adressen und 10 MB je Datei, nur im Zielbereich (Weiterleitungen nach aussen werden nicht verfolgt). Im CI die Zustandsdatei mit actions/cache zwischen den Läufen aufbewahren.

Prüfbarkeit des Berichts: Jeder Report enthält unter provenance die CPCG-Version, den Browser und die Scan-Konfiguration ohne Geheimnisse (Lizenz, Tokens, API-Keys, lokale Pfade) samt SHA-256 (configHash): gleicher Hash heisst gleiche Prüfung. Neben dem Report liegt <name>-manifest.sha256 mit den Hashes aller Artefakte (Report, Markdown, HTML, PDF, Screenshots, HAR); im Report-Ordner prüft sha256sum -c <name>-manifest.sha256, dass nichts verändert wurde. <name>-manifest.json enthält dieselben Angaben mit Konfigurations-Hash und Version.

Banner nicht gefunden: Selektoren und Frames

Findet die Erkennung den Banner oder einen Button nicht, wird das Szenario «nicht prüfbar» (nie grün). Dann helfen die Overrides:

cpcg --url https://staging.example.com \
  --accept-selector "#meine-zustimmung" \
  --reject-selector "button[data-consent=reject]" \
  --settings-selector ".cookie-einstellungen" \
  --banner-frame "consent.mein-cmp.example"

Erscheint der Banner erst spät, verlängert --banner-wait <sekunden> das Wartefenster (Standard 8 s). Der Scan behauptet «kein Banner» nur, wenn die Seite stabil lud und die Schlussprüfung das Fenster samt Nachfrist ohne Banner-Hinweis durchlief. Sonst steht eine eigene Meldung: «Banner erschien nach dem Wartefenster — mit --banner-wait erhöhen», «Banner vorhanden, aber nicht über Ablehnen/Akzeptieren bedienbar» (zum Beispiel nur ein Schliessen-Kreuz) oder «Seite instabil (Reload-Schleife) — Banner nicht auswertbar».

Auto-Fix: Grenzen und Consent-Anbindung

--auto-fix verschiebt Tracker-Skripte hinter die Einwilligung. Das Ergebnis ist ein Vorschlag, den die Review-Stufe prüft; ein nicht verifizierter Fix macht das Gate nicht grün.

  • OneTrust und Cookiebot werden automatisch gelesen (Kategorien Statistik und Marketing); ein Hook ist nicht nötig. OneTrust wird auch ohne CDN-Marker erkannt, etwa bei selbst gehostetem SDK (Optanon…-Symbole, OptanonConsent-Cookie).
  • Eigene Banner ändert der Auto-Fix nicht. Er gibt nur eine Anleitung aus: Der Banner ruft nach jeder Zustimmung window.cpcgGrantConsent("analytics") (Statistik) bzw. window.cpcgGrantConsent("marketing") auf, ohne Argument alle Kategorien. Erst mit --allow-custom-banner-fix fügt der Fix die Runtime ein. Ohne dieses Signal startet kein Tracker.
  • Andere Consent-Plattformen (zum Beispiel Usercentrics, Sourcepoint, Didomi, iubenda) sind nicht automatisch behebbar: der Auto-Fix kann ihre Kategorie-Einwilligung nicht sicher lesen und erzeugt keinen Patch, sondern eine Anleitung zur manuellen Behebung.
  • Tracker, die nicht im Quelltext stehen (über Tag Manager, Server, CMS oder Drittanbieter geladen) bleiben «manuell zu beheben».
  • LLM-gestützte Patches sind ein Opt-in (--llm-remediation), nur mit OpenAI umgesetzt, und senden den Quellcode an den Anbieter. Ein API-Key allein genügt nicht und sendet nichts.
  • Commit und Pull Request (--git-commit, --create-pr) brauchen eine Lizenz; das lokale Anwenden der Patches nicht. Git läuft im Repository, in dem --source-dir liegt; liegt es in keinem, bleibt der Fix lokal und der Lauf meldet das ausdrücklich.
  • Teilfix: Bleiben gemeldete Tracker offen, zählt das Gate nur diese als «ungepatcht»; Score und «Passed» des Review-Agenten gelten dann ausdrücklich nur für den gepatchten Teil.

Demo-Portal

Reproduzierbare Testseite mit fünf absichtlichen Consent-Leaks (Pre-Consent-Bootstrap, Timer, Widerruf, Embeds, doppelte Events):

node demo-portal/server.mjs     # http://127.0.0.1:8787
npx tsx scripts/scan-demo-portal.mts    # voller Scan + Report
npx tsx scripts/make-customer-report.mts  # Kundenbericht (PDF) daraus

Die Skripte in scripts/ sind TypeScript: node scripts/x.mts scheitert an den Importen, deshalb npx tsx.

Erwartet: Gate BLOCKED, Szenarien «auffällig» — exakt dieselben Zahlen bei jedem Lauf.

GitHub Action

- uses: cuonztech/privacy-chaos-gate-action@v1
  id: cpcg
  with:
    staging-url: ${{ vars.STAGING_URL }}
    license: ${{ secrets.CPCG_LICENSE }}
    report-pdf: true
    client-name: "Firma AG"

Laufzeit und Actions-Minuten: Die Action läuft in Ihrem GitHub-Konto und verbraucht dessen Minuten. Gemessen auf einem GitHub-Runner (ubuntu-24.04): Installation samt Browser etwa 35 Sekunden, der Scan einer einfachen Seite etwa 110 Sekunden, zusammen rund 2,5 Minuten. Seiten mit viel Werbetechnik und vielen Unterseiten brauchen länger; mit deep-budget (Standard 300 s) kann ein Tiefenlauf bis zu fünf weitere Minuten dauern. Im kostenlosen Tarif sind private Repositories auf 2.000 Minuten im Monat begrenzt, öffentliche Repositories kosten nichts. Deshalb lieber nicht bei jedem Push scannen, sondern:

  • nach dem Deployment (deployment_status oder repository_dispatch, siehe das Verify-Beispiel unten),
  • zeitgesteuert, zum Beispiel einmal pro Woche, und
  • von Hand (workflow_dispatch) vor einem Release.
on:
  schedule: [{ cron: "17 5 * * 1" }]   # montags 05:17 UTC
  workflow_dispatch:
concurrency:
  group: cpcg-${{ github.ref }}
  cancel-in-progress: true             # ein neuer Lauf ersetzt einen noch laufenden

Die Action installiert das veröffentlichte Paket cuonztech-privacy-chaos-gate in der Version, die in action.yml fest eingetragen ist (npm install --ignore-scripts, ohne Secrets in der Umgebung dieses Schritts), samt dem passenden Chromium (playwright install --with-deps chromium). Kennt die installierte CLI eine angeforderte Option nicht, gibt die Action eine Warnung aus, statt sie still wegzulassen.

Scan und Konsolen-Gate laufen ohne Lizenz. report-pdf, client-name, git-commit und create-pr brauchen eine gültige Lizenz; ohne sie werden sie mit Hinweis übersprungen.

| Input | Standard | Bedeutung | |-------|----------|-----------| | staging-url | Pflicht | Zu prüfende URL | | license | leer | Lizenzcode oder Kunden-Key; wird nur per Umgebungsvariable CPCG_LICENSE übergeben | | fail-on-leak | true | Build blockieren (Exit 1), wenn Leaks bleiben | | fail-on-uncheckable | true | Build blockieren (Exit 4), wenn Kernszenarien nicht prüfbar sind | | strict | false | Auch Hinweise (z. B. Google Fonts) blockieren das Gate | | ignore | leer | Befunde ausnehmen (Tracker-Name oder Host-Muster, eine Angabe je Zeile oder kommagetrennt) | | accept-selector, reject-selector, settings-selector | leer | Eigene CSS-Selektoren der Banner-Buttons (eine Angabe je Zeile) | | banner-frame | leer | Teil der URL des iframes mit dem Banner | | visitor | leer (CLI: ch) | Besucherprofil ch oder eu (Sprache und Zeitzone) | | proxy | leer | Proxy für alle Browser-Zugriffe (aus Secrets, wenn mit Zugangsdaten); wird als CPCG_PROXY übergeben, nie in der Kommandozeile | | max-subpages | leer (CLI: 12) | Höchstzahl verlinkter Unterseiten, die auf Tracker geprüft werden (max. 40; mehr dauert länger) | | sitemap | false | Sitemap-Tiefenlauf (siehe --sitemap) | | deep-budget | leer (CLI: 300) | Zeitbudget des Tiefenlaufs in Sekunden (30 bis 3600) | | deep-state | leer | Zustandsdatei der Wiederaufnahme (mit actions/cache aufbewahren) | | baseline-report | leer | Report-JSON eines früheren Laufs für den Vergleich (z. B. per actions/cache oder download-artifact bereitstellen) | | har | false | HAR-Mitschnitt ohne Inhalte nach evidence/har/ | | verify-url | leer | Vorschau-URL des Fixes für den geschlossenen Kreis (siehe --verify-url) | | verify-wait | leer (CLI: 0) | Sekunden Wartezeit, bis die Verify-URL erreichbar ist (0 bis 1800) | | target-headers | leer | Freigabe-Header für die eigene Website, eine Zeile Name: Wert je Header, aus Secrets (z. B. ${{ secrets.CPCG_TARGET_HEADERS }}); nur per Umgebung übergeben, nie als Argument | | banner-wait | leer (CLI-Standard) | Sekunden, die auf ein spät erscheinendes Banner gewartet wird (1–60) | | auto-fix | false | Patches auf dem Runner erzeugen und anwenden; ein Gate ändert Dateien nicht stillschweigend | | create-pr | false | Fix als Pull Request öffnen (braucht auto-fix: true, github-token und eine Lizenz) | | github-token | leer | Token für create-pr (contents: write, pull-requests: write) | | git-commit | false | Fix in den ausgecheckten Branch committen (braucht auto-fix: true und eine Lizenz) | | target-source-dir | . | Quellcode-Verzeichnis für Auto-Fix | | report-output | reports/privacy-chaos-report.json | Pfad des JSON-Reports | | report-pdf, client-name | false, leer | Kundenbericht (PDF) und Kundenname (Lizenz erforderlich) | | jurisdiction | CH,EU | Rechtsräume, deren Regeln das Gate entscheiden (kommagetrennt, strengster gewinnt; siehe «Jurisdiktion») | | llm-remediation, api-key, llm-provider, llm-model | false, leer, openai, gpt-4o | Optional: LLM-gestützte Patches. Nur OpenAI ist umgesetzt; der Quellcode wird dann an den Anbieter gesendet; ohne llm-remediation: true wirkt der Key nicht |

Beispiel mit Pull Request als Ergebnis:

permissions:
  contents: write
  pull-requests: write
steps:
  - uses: actions/checkout@v4
  - uses: cuonztech/privacy-chaos-gate-action@v1
    with:
      staging-url: ${{ vars.STAGING_URL }}
      license: ${{ secrets.CPCG_LICENSE }}
      auto-fix: true
      create-pr: true
      github-token: ${{ secrets.GITHUB_TOKEN }}

Outputs: leaks-found, leaks-count, patch-applied, report-path sowie gate_state, incomplete, uncheckable_count, leak_requests, verified (true/false nach einem Re-Scan, sonst leer) und exit_code (geschrieben vom CLI nach GITHUB_OUTPUT; leer, wenn die installierte CLI sie nicht kennt). Beispiel: if: steps.cpcg.outputs.gate_state == 'blocked'.

Geschlossener Kreis: Nach dem Deployment der Vorschau des Fix-PRs prüft ein zweiter Workflow die Vorschau. Hat die Ziel-URL (Produktion) noch Befunde und ist die Vorschau sauber und vollständig geprüft, meldet das Gate «PASS (Fix auf der Verify-URL … verifiziert; die Ziel-URL leakt, bis der Fix deployed ist)» und verified=true. Leakt die Vorschau weiter, sind dort Szenarien mit Befund nicht prüfbar oder ist sie nicht erreichbar, bleibt das Gate BLOCKED mit Grund (verified=false).

# Vercel meldet fertige Builds als repository_dispatch (Workflow-Datei muss auf dem Standard-Branch liegen).
# Im Vercel-Projekt muss die Meldung eingeschaltet sein (gitProviderOptions.disableRepositoryDispatchEvents = false).
# Andere Hoster, die GitHub-Deployments anlegen: «on: deployment_status» und deployment_status.target_url.
on:
  repository_dispatch:
    types: [vercel.deployment.success]
jobs:
  verify:
    if: github.event.client_payload.environment != 'production'
    runs-on: ubuntu-24.04
    steps:
      - uses: cuonztech/privacy-chaos-gate-action@v1
        id: cpcg
        with:
          staging-url: ${{ vars.PRODUCTION_URL }}
          verify-url: ${{ github.event.client_payload.url }}
          verify-wait: 120

«Verifiziert» verlangt ausserdem die Gegenprobe: Die Tracker aus den Befunden müssen auf der Vorschau nach dem Akzeptieren laden (eine Vorschau ganz ohne Tracking, etwa ohne Mess-ID, belegt keinen Fix), und Unterseiten mit Befund werden auf der Vorschau gezielt erneut besucht. Ein Szenario, das auf der Vorschau nur an einer Grenze der Seite scheitert, die schon auf der Produktion galt (eingebettetes Banner ohne CMP-Skript, keine verlinkte Unterseite), zählt als nachgeprüft: die Befunde stammen dann aus den Teilen, die der Re-Scan wiederholt hat. Eine neue Grenze auf der Vorschau zählt nicht. Exit 0 bezieht sich dabei nur auf die Vorschau: Die Produktion leakt, bis der Fix deployed ist. verify-url deshalb nur im Workflow der Vorschau setzen, nicht dauerhaft im Produktions-Gate.

Ist die Vorschau per Zugangsschutz gesperrt (z. B. Vercel Deployment Protection), gibt target-headers den Freigabe-Header mit (bei Vercel «Protection Bypass for Automation»: x-vercel-protection-bypass: <Secret>). Ohne Freigabe sieht der Scanner nur die Anmeldeseite; die Szenarien sind dann nicht prüfbar und der Fix gilt nicht als verifiziert. Die Vorschau muss für den Runner erreichbar sein.

Jurisdiktion

--jurisdiction nimmt einen oder mehrere Rechtsräume, kommagetrennt (CH | CH-SENSIBEL | EU | UK | US-CA | BR | ROW, Standard CH,EU, in der Action ebenso). Der Rechtsraum entscheidet, was das Gate blockiert; gewählt mehrere, gewinnt der strengste. Was nach einem Rechtsraum nicht blockiert, steht als Hinweis mit Begründung im Report. --strict lässt auch Hinweise blockieren.

| Regel (Befund) | EU, UK, BR, CH-SENSIBEL (opt-in) | CH (Widerspruch vor Aktivierung) | US-CA (opt-out) | ROW | |---|---|---|---|---| | Tracker vor der Einwilligung (pre-consent-tracking), auch ohne gespeicherte Entscheidung nach gelöschtem Speicher (tracking-without-consent) | blockiert (funktionale Drittanbieter und cookielose Reichweitenmessung: Hinweis) | blockiert für Werbung, Marketing und Analyse (funktionale Drittanbieter und cookielose Reichweitenmessung: Hinweis) | Hinweis | Hinweis | | Tracker nach Ablehnen, auch nach beschädigter gespeicherter Ablehnung oder verlorenem Klick-Rennen mit registrierter Ablehnung (tracking-after-deny) | blockiert | blockiert | blockiert | Hinweis | | Keine Ablehnen-Möglichkeit (no-reject-option): weder auf der ersten Ebene noch in den (geöffneten) Einstellungen; ein Schliessen-Kreuz zählt nur dann nicht als Ablehnen, wenn nach Schliessen und Neuladen weiter Tracker laden (gemessen in einem frischen Browser) | blockiert | blockiert | blockiert | Hinweis | | Banner ohne Auswahl (no-consent-choice): nur Hinweistext, Infolink und Schliessen-Kreuz, weder Akzeptieren noch Ablehnen, und Tracking vor der Entscheidung | blockiert | blockiert | blockiert | Hinweis | | Werbe-Tracker trotz Global Privacy Control (gpc-ignored) | Hinweis | Hinweis | blockiert (nur Werbung) | Hinweis | | Nach Ablehnen meldet der IAB-TCF-String Zustimmung zu Zwecken (tcf-consent-mismatch) | blockiert | blockiert | blockiert | Hinweis | | Tracker-Request nach Ablehnen, dessen IAB-TCF-String die Ablehnung trägt (weder Zustimmung noch berechtigtes Interesse) | Hinweis (mit --strict blockierend) | Hinweis | Hinweis | Hinweis | | Tracker-Request nach Ablehnen, dessen IAB-TCF-String berechtigtes Interesse beansprucht (Anbieter darf ohne Einwilligung verarbeiten) | blockiert (tracking-after-deny, mit Angabe) | blockiert | blockiert | Hinweis | | Übrige Regeln (Widerruf, abgelehnte Kategorie, Consent-Mode-Widerspruch, doppelte Events, Embeds) | blockiert | blockiert | blockiert | Hinweis |

Wie die Tabelle zu lesen ist: Sie beschreibt die Regeln, die CPCG je Modell anwendet, und keine Rechtslage. Der Report nennt bei jeder Aussage das angewandte Modell, zum Beispiel «Nach US-CA (opt-out) Hinweis, nicht blockierend», und beschreibt nur, was gemessen wurde. Er sagt nicht, ob etwas rechtmässig oder rechtswidrig ist.

  • opt-in (EU, UK, BR): Tracker vor einer Einwilligung sind blockierend.
  • CH-SENSIBEL (opt-in, seit 0.3.9, nur auf Wahl): für Schweizer Seiten, deren Besuch besonders schützenswerte Daten berühren kann (Gesundheit, Religion, Politik, Kinder), etwa Praxen, Spitex, Beratungsstellen, Kitas. Bewertet wie opt-in; Quelle der Regel: DSG Art. 6 Abs. 7 (ausdrückliche Einwilligung) und EDÖB-Leitfaden Cookies V1.1, Ziff. 3.10. CPCG erkennt die Branche nicht selbst: wer solche Daten bearbeitet, wählt --jurisdiction CH-SENSIBEL (allein oder z. B. CH-SENSIBEL,EU).
  • Widerspruch vor Aktivierung (CH, seit 0.3.8): Werbe-, Marketing- und Analyse-Tracker vor einer Entscheidung sind blockierend; rein funktionale Anbieter und cookielose Reichweitenmessung sind ein Hinweis. Quelle der Regel: EDÖB-Leitfaden Cookies V1.1 (06.10.2025), Ziff. 3.9 (keine Rechtsnorm). Bis 0.3.7 galt für CH das Modell opt-out.
  • opt-out (US-CA): Tracker vor einer Entscheidung sind ein Hinweis; blockierend sind Tracker nach «Ablehnen», ein Banner ohne Ablehnen-Möglichkeit und ein Banner ohne Auswahl.
  • notice-only (ROW): kein Modell angewendet, nie blockierend.

Welches Modell für Ihre Seite zutrifft, ist keine Entscheidung von CPCG: Die Zuordnung der Rechtsräume zu den Modellen ist eine Festlegung des Tools (für CH nach Rechtsprüfung, für EU, UK, BR und US-CA offen: docs/LICENSE-PRUEFHINWEISE.md, Punkte 13 und 16). Hat die Seite Publikum aus der EU, gehört EU in die Liste; bei mehreren Rechtsräumen gilt der strengere.

Entwicklung

pnpm install
pnpm test                # Vitest-Suite (inkl. PHP-Lizenzdienst, falls Docker läuft; action.yml-Pin und Optionsweitergabe)
pnpm build
pnpm typecheck:all       # src + tests + scripts
npx tsx scripts/e2e-cli-report.mts   # E2E: echte CLI gegen Demo-Portal

Der Lizenzdienst liegt in server/license-api/ (eigene README mit Deploy-Checkliste und Betrieb) und gehört nicht zum veröffentlichten Paket.

Secrets (Stripe, Lizenz-Secret) nur über Env/KeePass — nie im Repo.


© CuonzTech · Carlo Cuonz · cuonztech.ch · Lizenz: siehe LICENSE