cuonztech-privacy-chaos-gate 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/action.yml CHANGED
@@ -188,7 +188,7 @@ runs:
188
188
  shell: bash
189
189
  env:
190
190
  ACTION_PATH: ${{ github.action_path }}
191
- CPCG_PINNED_VERSION: "0.3.0" # = package.json version; bei jedem Release mit anheben (tests/action-pin.test.ts prüft das)
191
+ CPCG_PINNED_VERSION: "0.3.1" # = package.json version; bei jedem Release mit anheben (tests/action-pin.test.ts prüft das)
192
192
  run: |
193
193
  set -euo pipefail
194
194
 
@@ -517,6 +517,7 @@ function hintViolations(violations, strict = false, jurisdictions = resolveJuris
517
517
  const blocking = new Set(blockingViolations(violations, strict, jurisdictions));
518
518
  return violations.filter((v) => !blocking.has(v));
519
519
  }
520
+ var UNLISTED_EMBED_PREFIX = "Fremd-Inhalt (";
520
521
  function hintReasonOf(v, jurisdictions) {
521
522
  if (isAdvisory(v) && v.trackerCategory === "cookieless-analytics") {
522
523
  return "Cookielose Reichweitenmessung (keine Cookies, kein Zugriff auf das Ger\xE4t): nach verbreiteter Aufsichtspraxis einwilligungsfrei, wenn sie nur der Reichweitenmessung dient; dokumentiert, mit --strict blockierend.";
@@ -524,6 +525,9 @@ function hintReasonOf(v, jurisdictions) {
524
525
  if (isAdvisory(v) && v.tcf === "denied") {
525
526
  return "Request nach dem Ablehnen mit IAB-TCF-String, der die Ablehnung tr\xE4gt (kein Zweck erteilt): der Anbieter muss selbst verzichten; der Request selbst \xFCbermittelt trotzdem Daten (IP, Seite). Dokumentiert, mit --strict blockierend.";
526
527
  }
528
+ if (isAdvisory(v) && v.trackerName.startsWith(UNLISTED_EMBED_PREFIX)) {
529
+ return "Eingebetteter Fremd-Inhalt, der vor der Einwilligung l\xE4dt und nicht in der Tracker-Liste steht: Inhalt und Datenschutzerkl\xE4rung pr\xFCfen; dokumentiert, mit --strict blockierend.";
530
+ }
527
531
  if (isAdvisory(v)) return "Rein funktionaler Drittanbieter (z. B. Schriften): kein Verstoss, dokumentiert.";
528
532
  return hintReasonFor(jurisdictions, v.ruleId, v.trackerCategory);
529
533
  }
@@ -1406,6 +1410,7 @@ var CURATED_VENDOR_DOMAINS = [
1406
1410
  ["gc.zgo.at", "GoatCounter", "cookieless-analytics"],
1407
1411
  ["withcabin.com", "Cabin Analytics", "cookieless-analytics"],
1408
1412
  ["counter.dev", "counter.dev", "cookieless-analytics"],
1413
+ ["cloudflareinsights.com", "Cloudflare Web Analytics", "cookieless-analytics"],
1409
1414
  // --- Werbung, Ad-Tech, Datenplattformen ---
1410
1415
  ["googletagservices.com", "Google Ad Manager", "marketing"],
1411
1416
  ["amazon-adsystem.com", "Amazon Ads", "marketing"],
@@ -1546,7 +1551,8 @@ var KNOWN_TRACKERS = [
1546
1551
  name: "Google Analytics / Google Tag (gtag.js)",
1547
1552
  category: "analytics",
1548
1553
  consentModeLoader: true,
1549
- patterns: [at(www("googletagmanager.com"), "/gtag/js$")]
1554
+ // `/a?id=G-…` ist der Beacon des Google-Tags (Konfiguration/Diagnose) derselben Messkennung.
1555
+ patterns: [at(www("googletagmanager.com"), "/gtag/js$"), at(www("googletagmanager.com"), "/a$")]
1550
1556
  },
1551
1557
  {
1552
1558
  name: "Meta (Facebook) Pixel",
@@ -3646,6 +3652,9 @@ function ruleIdFor(phase) {
3646
3652
  if (phase === "gpc") return "gpc-ignored";
3647
3653
  return phase === "post-wipe" ? "tracking-without-consent" : phase === "post-reject" || phase === "post-reject-restart" || phase === "post-tamper" ? "tracking-after-deny" : phase === "post-revoke" ? "tracking-after-revoke" : "pre-consent-tracking";
3648
3654
  }
3655
+ function isFirstPartyGoogleMeasurement(t) {
3656
+ return !!t.trackerName?.startsWith("Google Analytics (First-Party-Endpoint");
3657
+ }
3649
3658
  function asScope(s) {
3650
3659
  return typeof s === "string" ? new TargetScope(s) : s;
3651
3660
  }
@@ -5254,6 +5263,39 @@ var ChaosScanner = class {
5254
5263
  checkedAt: (/* @__PURE__ */ new Date()).toISOString()
5255
5264
  });
5256
5265
  }
5266
+ /**
5267
+ * Hosts eingebetteter Fremd-Frames, die vor der Freigabe Requests auslösen, ohne dass die Tracker-Liste sie kennt.
5268
+ * Eigene Hosts, Loopback/IP und bereits erkannte Tracker zählen nicht.
5269
+ */
5270
+ unlistedEmbedHosts(embeds, targetUrl, timeline) {
5271
+ let targetHost;
5272
+ try {
5273
+ targetHost = new URL(targetUrl).hostname;
5274
+ } catch {
5275
+ return [];
5276
+ }
5277
+ const hosts = /* @__PURE__ */ new Set();
5278
+ for (const e of embeds) {
5279
+ try {
5280
+ const u = new URL(e.src, targetUrl);
5281
+ if (/^https?:$/.test(u.protocol) && !/^[\d.]+$|^\[|^localhost$/i.test(u.hostname) && !isFirstPartyHost(u.hostname, targetHost)) hosts.add(u.hostname);
5282
+ } catch {
5283
+ }
5284
+ }
5285
+ const out = [];
5286
+ for (const host of hosts) {
5287
+ const own = timeline.filter((t) => {
5288
+ try {
5289
+ const h = new URL(t.url).hostname;
5290
+ return !t.trackerName && (h === host || h.endsWith(`.${host}`));
5291
+ } catch {
5292
+ return false;
5293
+ }
5294
+ });
5295
+ if (own.length) out.push({ host, count: own.length, sample: own[0].url });
5296
+ }
5297
+ return out;
5298
+ }
5257
5299
  /**
5258
5300
  * CNAME-Cloaking nachtragen: Hosts der eigenen Seite aus der Zeitlinie, die kein Tracker sind, per DNS prüfen. Zeigt die
5259
5301
  * CNAME-Kette auf einen Tracker-Host, wird der Request mit seiner ursprünglichen Phase als Tracker-Befund eingetragen.
@@ -6293,7 +6335,7 @@ var ChaosScanner = class {
6293
6335
  googleRequests.push(t.url);
6294
6336
  }
6295
6337
  }
6296
- const hits = timeline.filter((t) => isGoogleMeasurementRequest(t.url) || isGoogleMeasurementRequest(viaCname(t.url) ?? "")).map((t) => ({ ...t, signal: summarizeSignals(parseConsentSignals(t.url)) }));
6338
+ const hits = timeline.filter((t) => isGoogleMeasurementRequest(t.url) || isGoogleMeasurementRequest(viaCname(t.url) ?? "") || isFirstPartyGoogleMeasurement(t)).map((t) => ({ ...t, signal: summarizeSignals(parseConsentSignals(t.url)) }));
6297
6339
  const signalled = hits.some((h) => h.signal !== "none");
6298
6340
  const hasConsentMode = decided && (Object.keys(defaults).length > 0 || Object.keys(after).length > 0 || signalled);
6299
6341
  const gaCookies = storageAfter.cookies.filter((c) => /^_ga|_gid|_gat|_gcl/i.test(c));
@@ -6453,6 +6495,21 @@ var ChaosScanner = class {
6453
6495
  await page.waitForTimeout(1500);
6454
6496
  await this.snap(page, "embed-leaks", "01-embeds", screenshots, actionTimeline, actionSequence);
6455
6497
  const storageAfter = await this.snapshotStorage(page);
6498
+ const unlistedEmbeds = this.unlistedEmbedHosts(embeds, config.targetUrl, timeline);
6499
+ for (const u of unlistedEmbeds) {
6500
+ violations.push({
6501
+ id: `embed-leaks-unlisted-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
6502
+ ruleId: "pre-consent-embed",
6503
+ scenario: "embed-leaks",
6504
+ trackerName: `${UNLISTED_EMBED_PREFIX}${u.host})`,
6505
+ trackerCategory: "functional",
6506
+ requestUrl: u.sample,
6507
+ method: "GET",
6508
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
6509
+ description: `Eingebetteter Inhalt von ${u.host} l\xE4dt vor Consent-Freigabe ${u.count} Request(s); der Anbieter steht nicht in der Tracker-Liste.`,
6510
+ advisory: true
6511
+ });
6512
+ }
6456
6513
  if (embeds.length === 0 && embedLeakCount === 0 && embedAdvisoryCount === 0) {
6457
6514
  const status2 = embedsEvalOk ? "not-applicable" : "not-checkable";
6458
6515
  evidence.push({
@@ -6476,7 +6533,7 @@ var ChaosScanner = class {
6476
6533
  evidence.push({
6477
6534
  scenario: "embed-leaks",
6478
6535
  status,
6479
- note: (status === "passed" ? embedAdvisoryCount ? `${embeds.length} Embed(s) gefunden, keine marketing-/analytics-relevanten Drittanbieter-Requests vor Freigabe.` : `${embeds.length} Embed(s) gefunden, keine Drittanbieter-Requests vor Freigabe.` : `${embedLeakCount} Embed-/Widget-Request(s) vor Consent-Freigabe.`) + (embedAdvisoryCount ? ` ${embedAdvisoryCount} Hinweis(e) auf rein funktionale Einbettungen (z. B. Karten) vor Freigabe \u2014 im Report sichtbar, ohne Gate-Wirkung.` : ""),
6536
+ note: (status === "passed" ? embedAdvisoryCount ? `${embeds.length} Embed(s) gefunden, keine marketing-/analytics-relevanten Drittanbieter-Requests vor Freigabe.` : `${embeds.length} Embed(s) gefunden, keine Drittanbieter-Requests vor Freigabe.` : `${embedLeakCount} Embed-/Widget-Request(s) vor Consent-Freigabe.`) + (embedAdvisoryCount ? ` ${embedAdvisoryCount} Hinweis(e) auf rein funktionale Einbettungen (z. B. Karten) vor Freigabe \u2014 im Report sichtbar, ohne Gate-Wirkung.` : "") + (unlistedEmbeds.length ? ` Fremd-Inhalt ohne Eintrag in der Tracker-Liste l\xE4dt vor Freigabe: ${unlistedEmbeds.map((u) => `${u.host} (${u.count} Request(s))`).join(", ")} \u2014 Hinweis, ohne Gate-Wirkung.` : ""),
6480
6537
  actionSequence,
6481
6538
  actionTimeline,
6482
6539
  requestTimeline: timeline,
@@ -10931,7 +10988,7 @@ import pc from "picocolors";
10931
10988
  import { createHash } from "crypto";
10932
10989
  import fs10 from "fs/promises";
10933
10990
  import path10 from "path";
10934
- var CPCG_VERSION = true ? "0.3.0" : "dev";
10991
+ var CPCG_VERSION = true ? "0.3.1" : "dev";
10935
10992
  function scanConfigOf(config) {
10936
10993
  return {
10937
10994
  targetUrl: config.targetUrl,
package/dist/cli.js CHANGED
@@ -11,12 +11,12 @@ import {
11
11
  resolveEntitlement,
12
12
  resolveGateConfig,
13
13
  resolveJurisdictions
14
- } from "./chunk-YOWEZW3W.js";
14
+ } from "./chunk-BZFL6QDO.js";
15
15
 
16
16
  // src/cli.ts
17
17
  import { Command, CommanderError } from "commander";
18
18
  import pc from "picocolors";
19
- var VERSION = true ? "0.3.0" : "dev";
19
+ var VERSION = true ? "0.3.1" : "dev";
20
20
  loadProjectEnv();
21
21
  function finish(code) {
22
22
  process.exitCode = code;
package/dist/index.d.ts CHANGED
@@ -2439,6 +2439,11 @@ declare class ChaosScanner implements ChaosScannerInterface {
2439
2439
  * Ergebnis: passed nur bei ausgeführter Aktion und null Illegal-Funden.
2440
2440
  */
2441
2441
  private runScenario;
2442
+ /**
2443
+ * Hosts eingebetteter Fremd-Frames, die vor der Freigabe Requests auslösen, ohne dass die Tracker-Liste sie kennt.
2444
+ * Eigene Hosts, Loopback/IP und bereits erkannte Tracker zählen nicht.
2445
+ */
2446
+ private unlistedEmbedHosts;
2442
2447
  /**
2443
2448
  * CNAME-Cloaking nachtragen: Hosts der eigenen Seite aus der Zeitlinie, die kein Tracker sind, per DNS prüfen. Zeigt die
2444
2449
  * CNAME-Kette auf einen Tracker-Host, wird der Request mit seiner ursprünglichen Phase als Tracker-Befund eingetragen.
package/dist/index.js CHANGED
@@ -56,7 +56,7 @@ import {
56
56
  uncheckableReasons,
57
57
  uncheckableReasonsForReport,
58
58
  waitForVerifyUrl
59
- } from "./chunk-YOWEZW3W.js";
59
+ } from "./chunk-BZFL6QDO.js";
60
60
 
61
61
  // src/index.ts
62
62
  async function runGate(optionsOrConfig) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cuonztech-privacy-chaos-gate",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "CuonzTech Privacy Chaos Gate — CI/CD Consent-Scanner, Chaos-Szenarien und Auto-Fix-PR",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",