@slashgear/gdpr-cookie-scanner 2.0.4 → 3.1.0

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.
Files changed (53) hide show
  1. package/.github/workflows/update-trackers.yml +95 -0
  2. package/CHANGELOG.md +102 -0
  3. package/CLAUDE.md +1 -1
  4. package/README.md +78 -5
  5. package/dist/analyzers/compliance.d.ts.map +1 -1
  6. package/dist/analyzers/compliance.js +52 -9
  7. package/dist/analyzers/compliance.js.map +1 -1
  8. package/dist/classifiers/cookie-classifier.d.ts.map +1 -1
  9. package/dist/classifiers/cookie-classifier.js +2 -1
  10. package/dist/classifiers/cookie-classifier.js.map +1 -1
  11. package/dist/classifiers/network-classifier.d.ts +1 -0
  12. package/dist/classifiers/network-classifier.d.ts.map +1 -1
  13. package/dist/classifiers/network-classifier.js +10 -1
  14. package/dist/classifiers/network-classifier.js.map +1 -1
  15. package/dist/classifiers/tracker-list.d.ts +1 -0
  16. package/dist/classifiers/tracker-list.d.ts.map +1 -1
  17. package/dist/classifiers/tracker-list.js +7 -1
  18. package/dist/classifiers/tracker-list.js.map +1 -1
  19. package/dist/cli.js +1 -1
  20. package/dist/cli.js.map +1 -1
  21. package/dist/index.d.ts +51 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +46 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/report/generator.d.ts.map +1 -1
  26. package/dist/report/generator.js +82 -41
  27. package/dist/report/generator.js.map +1 -1
  28. package/dist/scanner/index.js +4 -4
  29. package/dist/scanner/index.js.map +1 -1
  30. package/dist/scanner/network.d.ts.map +1 -1
  31. package/dist/scanner/network.js +1 -0
  32. package/dist/scanner/network.js.map +1 -1
  33. package/dist/types.d.ts +2 -1
  34. package/dist/types.d.ts.map +1 -1
  35. package/package.json +11 -2
  36. package/scripts/update-trackers.ts +273 -0
  37. package/src/analyzers/compliance.ts +54 -11
  38. package/src/classifiers/cookie-classifier.ts +2 -1
  39. package/src/classifiers/network-classifier.ts +11 -1
  40. package/src/classifiers/tracker-list.ts +9 -1
  41. package/src/cli.ts +1 -5
  42. package/src/index.ts +87 -0
  43. package/src/report/generator.ts +83 -44
  44. package/src/scanner/index.ts +4 -4
  45. package/src/scanner/network.ts +1 -0
  46. package/src/types.ts +2 -1
  47. package/tests/analyzers/compliance.test.ts +489 -0
  48. package/tests/analyzers/wording.test.ts +160 -0
  49. package/tests/classifiers/cookie-classifier.test.ts +270 -0
  50. package/tests/classifiers/network-classifier.test.ts +140 -0
  51. package/tests/e2e/scanner.test.ts +4 -7
  52. package/tests/unit/compliance.test.ts +99 -13
  53. package/tests/unit/network-classifier.test.ts +27 -0
@@ -14,7 +14,7 @@ export class Scanner {
14
14
  const startTime = Date.now();
15
15
  const screenshotPaths = [];
16
16
  const errors = [];
17
- if (this.options.screenshots) {
17
+ if (this.options.screenshots && this.options.outputDir) {
18
18
  await mkdir(this.options.outputDir, { recursive: true });
19
19
  }
20
20
  // ────────────────────────────────────────────────────────────
@@ -44,7 +44,7 @@ export class Scanner {
44
44
  // ────────────────────────────────────────────────────────────
45
45
  onPhase("Phase 2/4 — Analyzing consent modal...");
46
46
  const modal = await detectConsentModal(session1.page, this.options);
47
- if (this.options.screenshots && modal.detected) {
47
+ if (this.options.screenshots && this.options.outputDir && modal.detected) {
48
48
  const screenshotPath = join(this.options.outputDir, "modal-initial.png");
49
49
  await session1.page.screenshot({ path: screenshotPath, fullPage: false });
50
50
  screenshotPaths.push(screenshotPath);
@@ -73,7 +73,7 @@ export class Scanner {
73
73
  errors.push("No reject button found — could not test rejection flow");
74
74
  }
75
75
  interceptor3.stop();
76
- if (this.options.screenshots) {
76
+ if (this.options.screenshots && this.options.outputDir) {
77
77
  const screenshotPath = join(this.options.outputDir, "after-reject.png");
78
78
  await session1.page.screenshot({ path: screenshotPath, fullPage: false });
79
79
  screenshotPaths.push(screenshotPath);
@@ -116,7 +116,7 @@ export class Scanner {
116
116
  errors.push(`Accept phase error: ${String(err)}`);
117
117
  }
118
118
  interceptor4.stop();
119
- if (this.options.screenshots) {
119
+ if (this.options.screenshots && this.options.outputDir) {
120
120
  const screenshotPath = join(this.options.outputDir, "after-accept.png");
121
121
  await session2.page.screenshot({ path: screenshotPath, fullPage: false });
122
122
  screenshotPaths.push(screenshotPath);
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/scanner/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/scanner/index.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"network.d.ts","sourceRoot":"","sources":["../../src/scanner/network.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAqB,MAAM,YAAY,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGlD,MAAM,MAAM,YAAY,GAAG,oBAAoB,GAAG,cAAc,GAAG,cAAc,CAAC;AAElF,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,YAAY;;;EA4CvE"}
1
+ {"version":3,"file":"network.d.ts","sourceRoot":"","sources":["../../src/scanner/network.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAqB,MAAM,YAAY,CAAC;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGlD,MAAM,MAAM,YAAY,GAAG,oBAAoB,GAAG,cAAc,GAAG,cAAc,CAAC;AAElF,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,YAAY;;;EA6CvE"}
@@ -23,6 +23,7 @@ export function createNetworkInterceptor(page, phase) {
23
23
  isThirdParty: classification.isThirdParty,
24
24
  trackerCategory: classification.trackerCategory,
25
25
  trackerName: classification.trackerName,
26
+ requiresConsent: classification.requiresConsent,
26
27
  capturedAt: phase,
27
28
  responseStatus: meta?.status ?? null,
28
29
  contentType: meta?.contentType ?? null,
@@ -1 +1 @@
1
- {"version":3,"file":"network.js","sourceRoot":"","sources":["../../src/scanner/network.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,sBAAsB,EAAE,MAAM,sCAAsC,CAAC;AAI9E,MAAM,UAAU,wBAAwB,CAAC,IAAU,EAAE,KAAmB;IACtE,MAAM,QAAQ,GAAqB,EAAE,CAAC;IACtC,MAAM,WAAW,GAAG,IAAI,GAAG,EAA0D,CAAC;IAEtF,MAAM,UAAU,GAAG,CAAC,QAAkB,EAAE,EAAE;QACxC,WAAW,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YACxC,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE;YACzB,WAAW,EAAE,QAAQ,CAAC,OAAO,EAAE,CAAC,cAAc,CAAC,IAAI,IAAI;SACxD,CAAC,CAAC;IACL,CAAC,CAAC;IAEF,MAAM,SAAS,GAAG,CAAC,OAAgB,EAAE,EAAE;QACrC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;QAE1B,8CAA8C;QAC9C,IAAI,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,mBAAmB,CAAC;YAAE,OAAO;QAE3E,MAAM,cAAc,GAAG,sBAAsB,CAAC,GAAG,EAAE,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;QAC3E,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC;QAE1C,QAAQ,CAAC,IAAI,CAAC;YACZ,GAAG;YACH,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE;YACxB,YAAY,EAAE,OAAO,CAAC,YAAY,EAAE;YACpC,SAAS,EAAE,IAAI;YACf,YAAY,EAAE,cAAc,CAAC,YAAY;YACzC,eAAe,EAAE,cAAc,CAAC,eAAe;YAC/C,WAAW,EAAE,cAAc,CAAC,WAAW;YACvC,UAAU,EAAE,KAAK;YACjB,cAAc,EAAE,IAAI,EAAE,MAAM,IAAI,IAAI;YACpC,WAAW,EAAE,IAAI,EAAE,WAAW,IAAI,IAAI;SACvC,CAAC,CAAC;IACL,CAAC,CAAC;IAEF,IAAI,CAAC,EAAE,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;IAChC,IAAI,CAAC,EAAE,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;IAE9B,OAAO;QACL,IAAI,EAAE,GAAG,EAAE;YACT,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;YACjC,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;QACjC,CAAC;QACD,WAAW,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,QAAQ,CAAC;KACjC,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"network.js","sourceRoot":"","sources":["../../src/scanner/network.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,sBAAsB,EAAE,MAAM,sCAAsC,CAAC;AAI9E,MAAM,UAAU,wBAAwB,CAAC,IAAU,EAAE,KAAmB;IACtE,MAAM,QAAQ,GAAqB,EAAE,CAAC;IACtC,MAAM,WAAW,GAAG,IAAI,GAAG,EAA0D,CAAC;IAEtF,MAAM,UAAU,GAAG,CAAC,QAAkB,EAAE,EAAE;QACxC,WAAW,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YACxC,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE;YACzB,WAAW,EAAE,QAAQ,CAAC,OAAO,EAAE,CAAC,cAAc,CAAC,IAAI,IAAI;SACxD,CAAC,CAAC;IACL,CAAC,CAAC;IAEF,MAAM,SAAS,GAAG,CAAC,OAAgB,EAAE,EAAE;QACrC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;QAE1B,8CAA8C;QAC9C,IAAI,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,mBAAmB,CAAC;YAAE,OAAO;QAE3E,MAAM,cAAc,GAAG,sBAAsB,CAAC,GAAG,EAAE,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;QAC3E,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC;QAE1C,QAAQ,CAAC,IAAI,CAAC;YACZ,GAAG;YACH,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE;YACxB,YAAY,EAAE,OAAO,CAAC,YAAY,EAAE;YACpC,SAAS,EAAE,IAAI;YACf,YAAY,EAAE,cAAc,CAAC,YAAY;YACzC,eAAe,EAAE,cAAc,CAAC,eAAe;YAC/C,WAAW,EAAE,cAAc,CAAC,WAAW;YACvC,eAAe,EAAE,cAAc,CAAC,eAAe;YAC/C,UAAU,EAAE,KAAK;YACjB,cAAc,EAAE,IAAI,EAAE,MAAM,IAAI,IAAI;YACpC,WAAW,EAAE,IAAI,EAAE,WAAW,IAAI,IAAI;SACvC,CAAC,CAAC;IACL,CAAC,CAAC;IAEF,IAAI,CAAC,EAAE,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;IAChC,IAAI,CAAC,EAAE,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;IAE9B,OAAO;QACL,IAAI,EAAE,GAAG,EAAE;YACT,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;YACjC,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;QACjC,CAAC;QACD,WAAW,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,QAAQ,CAAC;KACjC,CAAC;AACJ,CAAC"}
package/dist/types.d.ts CHANGED
@@ -21,6 +21,7 @@ export interface NetworkRequest {
21
21
  isThirdParty: boolean;
22
22
  trackerCategory: TrackerCategory | null;
23
23
  trackerName: string | null;
24
+ requiresConsent: boolean;
24
25
  capturedAt: "before-interaction" | "after-accept" | "after-reject";
25
26
  responseStatus: number | null;
26
27
  contentType: string | null;
@@ -82,7 +83,7 @@ export interface ComplianceScore {
82
83
  export type ReportFormat = "md" | "html" | "json" | "pdf";
83
84
  export interface ScanOptions {
84
85
  url: string;
85
- outputDir: string;
86
+ outputDir?: string;
86
87
  timeout: number;
87
88
  screenshots: boolean;
88
89
  locale: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slashgear/gdpr-cookie-scanner",
3
- "version": "2.0.4",
3
+ "version": "3.1.0",
4
4
  "description": "CLI tool to scan websites for GDPR cookie consent compliance",
5
5
  "keywords": [
6
6
  "compliance",
@@ -27,6 +27,13 @@
27
27
  },
28
28
  "type": "module",
29
29
  "main": "dist/index.js",
30
+ "types": "dist/index.d.ts",
31
+ "exports": {
32
+ ".": {
33
+ "types": "./dist/index.d.ts",
34
+ "import": "./dist/index.js"
35
+ }
36
+ },
30
37
  "publishConfig": {
31
38
  "access": "public",
32
39
  "registry": "https://registry.npmjs.org"
@@ -59,6 +66,8 @@
59
66
  "typecheck": "tsc --noEmit",
60
67
  "test": "vitest run",
61
68
  "test:watch": "vitest",
62
- "changeset": "changeset"
69
+ "changeset": "changeset",
70
+ "update-trackers": "node --experimental-strip-types scripts/update-trackers.ts",
71
+ "update-trackers:dry": "node --experimental-strip-types scripts/update-trackers.ts --dry-run"
63
72
  }
64
73
  }
@@ -0,0 +1,273 @@
1
+ /**
2
+ * scripts/update-trackers.ts
3
+ *
4
+ * Maintenance script: fetches Disconnect.me and DuckDuckGo Tracker Radar,
5
+ * maps their categories to TrackerCategory, and merges new entries into
6
+ * src/classifiers/tracker-list.ts — preserving all manually curated entries.
7
+ *
8
+ * Usage:
9
+ * node --experimental-strip-types scripts/update-trackers.ts
10
+ * node --experimental-strip-types scripts/update-trackers.ts --dry-run
11
+ *
12
+ * Or via npm:
13
+ * pnpm update-trackers
14
+ * pnpm update-trackers -- --dry-run
15
+ */
16
+
17
+ import { readFileSync, writeFileSync } from "node:fs";
18
+ import { dirname, resolve } from "node:path";
19
+ import { fileURLToPath } from "node:url";
20
+
21
+ // ── Types (inlined so the script runs without tsc) ────────────────────────────
22
+
23
+ type TrackerCategory =
24
+ | "analytics"
25
+ | "advertising"
26
+ | "social"
27
+ | "fingerprinting"
28
+ | "pixel"
29
+ | "cdn"
30
+ | "unknown";
31
+
32
+ interface TrackerEntry {
33
+ name: string;
34
+ category: TrackerCategory;
35
+ }
36
+
37
+ // ── Disconnect.me ─────────────────────────────────────────────────────────────
38
+
39
+ type DisconnectDomainMap = Record<string, string[]>;
40
+ type DisconnectService = Record<string, DisconnectDomainMap | string>;
41
+ type DisconnectData = { categories: Record<string, DisconnectService[]> };
42
+
43
+ const DISCONNECT_CATEGORY_MAP: Record<string, TrackerCategory> = {
44
+ Analytics: "analytics",
45
+ Advertising: "advertising",
46
+ Social: "social",
47
+ Content: "cdn",
48
+ Fingerprinting: "fingerprinting",
49
+ Email: "advertising",
50
+ Disconnect: "advertising",
51
+ };
52
+
53
+ async function fetchDisconnect(): Promise<Map<string, TrackerEntry>> {
54
+ const url =
55
+ "https://raw.githubusercontent.com/disconnectme/disconnect-tracking-protection/master/services.json";
56
+ const res = await fetch(url);
57
+ if (!res.ok) throw new Error(`Disconnect.me fetch failed: HTTP ${res.status}`);
58
+ const data = (await res.json()) as DisconnectData;
59
+ const result = new Map<string, TrackerEntry>();
60
+
61
+ for (const [catName, services] of Object.entries(data.categories)) {
62
+ const category = DISCONNECT_CATEGORY_MAP[catName];
63
+ if (!category) continue;
64
+ for (const serviceObj of services) {
65
+ for (const [serviceName, value] of Object.entries(serviceObj)) {
66
+ if (typeof value !== "object" || value === null) continue;
67
+ const domainMap = value as DisconnectDomainMap;
68
+ for (const [mainDomain, subDomains] of Object.entries(domainMap)) {
69
+ result.set(mainDomain, { name: serviceName, category });
70
+ for (const sub of subDomains) {
71
+ // Subdomain inherits parent name/category unless already set
72
+ if (!result.has(sub)) {
73
+ result.set(sub, { name: serviceName, category });
74
+ }
75
+ }
76
+ }
77
+ }
78
+ }
79
+ }
80
+
81
+ return result;
82
+ }
83
+
84
+ // ── DuckDuckGo Tracker Radar ──────────────────────────────────────────────────
85
+
86
+ interface DDGTracker {
87
+ domain: string;
88
+ owner?: { name: string; displayName: string };
89
+ prevalence: number;
90
+ categories?: string[];
91
+ }
92
+
93
+ type DDGData = { trackers: Record<string, DDGTracker> };
94
+
95
+ const DDG_CATEGORY_MAP: Record<string, TrackerCategory> = {
96
+ Analytics: "analytics",
97
+ Advertising: "advertising",
98
+ "Social Network": "social",
99
+ Fingerprinting: "fingerprinting",
100
+ CDN: "cdn",
101
+ "Action Pixels": "pixel",
102
+ "Embedded Content": "cdn",
103
+ "Session Replay": "analytics",
104
+ "Email Tracking": "advertising",
105
+ "Ad Motivated Tracking": "advertising",
106
+ "Audience Measurement": "analytics",
107
+ "Third-Party Analytics Marketing": "advertising",
108
+ };
109
+
110
+ /** Minimum prevalence (fraction of crawled sites) to include in the DB. */
111
+ const MIN_PREVALENCE = 0.001; // ≥ 0.1 %
112
+
113
+ async function fetchDDG(): Promise<Map<string, TrackerEntry>> {
114
+ const url =
115
+ "https://raw.githubusercontent.com/duckduckgo/tracker-radar/main/build-data/generated/tds.json";
116
+ const res = await fetch(url);
117
+ if (!res.ok) throw new Error(`DDG Tracker Radar fetch failed: HTTP ${res.status}`);
118
+ const data = (await res.json()) as DDGData;
119
+ const result = new Map<string, TrackerEntry>();
120
+
121
+ for (const tracker of Object.values(data.trackers)) {
122
+ if ((tracker.prevalence ?? 0) < MIN_PREVALENCE) continue;
123
+
124
+ let category: TrackerCategory | undefined;
125
+ for (const cat of tracker.categories ?? []) {
126
+ const mapped = DDG_CATEGORY_MAP[cat];
127
+ if (mapped) {
128
+ category = mapped;
129
+ break;
130
+ }
131
+ }
132
+ if (!category) continue;
133
+
134
+ const name = tracker.owner?.displayName ?? tracker.domain;
135
+ result.set(tracker.domain, { name, category });
136
+ }
137
+
138
+ return result;
139
+ }
140
+
141
+ // ── Tracker-list.ts patcher ───────────────────────────────────────────────────
142
+
143
+ const MARKER_START = "// ── AUTO-GENERATED: DO NOT EDIT BELOW THIS LINE";
144
+ const MARKER_END = "// ── END AUTO-GENERATED";
145
+
146
+ const CAT_ORDER: TrackerCategory[] = [
147
+ "analytics",
148
+ "advertising",
149
+ "social",
150
+ "fingerprinting",
151
+ "pixel",
152
+ "cdn",
153
+ ];
154
+
155
+ /** Extract domain keys that already exist in the manual section of the file. */
156
+ function extractExistingKeys(manualSection: string): Set<string> {
157
+ const keys = new Set<string>();
158
+ // Matches tracker entry lines: "domain.com": { name:
159
+ const re = /^\s+"([^"]+)":\s*\{\s*name:/gm;
160
+ let m: RegExpExecArray | null;
161
+ while ((m = re.exec(manualSection)) !== null) {
162
+ keys.add(m[1]);
163
+ }
164
+ return keys;
165
+ }
166
+
167
+ function renderAutoSection(entries: Map<string, TrackerEntry>, date: string): string {
168
+ const lines: string[] = [
169
+ ` ${MARKER_START} ──────────────────────────`,
170
+ ` // Last updated: ${date}`,
171
+ ` // Sources: Disconnect.me · DuckDuckGo Tracker Radar (prevalence ≥ ${MIN_PREVALENCE * 100}%)`,
172
+ ` // New entries: ${entries.size} domains`,
173
+ ];
174
+
175
+ if (entries.size === 0) {
176
+ lines.push(` ${MARKER_END}`);
177
+ return lines.join("\n");
178
+ }
179
+
180
+ lines.push("");
181
+
182
+ const byCategory = new Map<TrackerCategory, Array<[string, TrackerEntry]>>();
183
+ for (const [domain, entry] of entries) {
184
+ const bucket = byCategory.get(entry.category) ?? [];
185
+ bucket.push([domain, entry]);
186
+ byCategory.set(entry.category, bucket);
187
+ }
188
+
189
+ const sorted = [...byCategory.entries()].sort(
190
+ ([a], [b]) => CAT_ORDER.indexOf(a) - CAT_ORDER.indexOf(b),
191
+ );
192
+
193
+ for (const [cat, domainEntries] of sorted) {
194
+ domainEntries.sort(([a], [b]) => a.localeCompare(b));
195
+ const pad = "─".repeat(Math.max(0, 45 - cat.length));
196
+ lines.push(` // ── ${cat} (auto) ${pad}`);
197
+ for (const [domain, { name, category }] of domainEntries) {
198
+ const escaped = name.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
199
+ lines.push(` "${domain}": { name: "${escaped}", category: "${category}" },`);
200
+ }
201
+ lines.push("");
202
+ }
203
+
204
+ lines.push(` ${MARKER_END}`);
205
+ return lines.join("\n");
206
+ }
207
+
208
+ function patchFile(content: string, newEntries: Map<string, TrackerEntry>, date: string): string {
209
+ const autoSection = renderAutoSection(newEntries, date);
210
+ const startMarker = ` ${MARKER_START}`;
211
+ const endMarker = ` ${MARKER_END}`;
212
+
213
+ const startIdx = content.indexOf(startMarker);
214
+ const endIdx = content.indexOf(endMarker);
215
+
216
+ if (startIdx >= 0 && endIdx >= 0) {
217
+ const before = content.slice(0, startIdx);
218
+ const after = content.slice(endIdx + endMarker.length);
219
+ return before + autoSection + after;
220
+ }
221
+
222
+ // First run: insert before the closing `};` of TRACKER_DB
223
+ const closingIdx = content.lastIndexOf("\n};");
224
+ return content.slice(0, closingIdx) + "\n\n" + autoSection + "\n" + content.slice(closingIdx + 1);
225
+ }
226
+
227
+ // ── Main ──────────────────────────────────────────────────────────────────────
228
+
229
+ async function main(): Promise<void> {
230
+ const dryRun = process.argv.includes("--dry-run");
231
+ const __dirname = dirname(fileURLToPath(import.meta.url));
232
+ const trackerListPath = resolve(__dirname, "../src/classifiers/tracker-list.ts");
233
+
234
+ console.log("Fetching Disconnect.me…");
235
+ const disconnectEntries = await fetchDisconnect();
236
+ console.log(` → ${disconnectEntries.size} domains`);
237
+
238
+ console.log(`Fetching DuckDuckGo Tracker Radar (prevalence ≥ ${MIN_PREVALENCE * 100}%)…`);
239
+ const ddgEntries = await fetchDDG();
240
+ console.log(` → ${ddgEntries.size} domains`);
241
+
242
+ const content = readFileSync(trackerListPath, "utf-8");
243
+ const startIdx = content.indexOf(` ${MARKER_START}`);
244
+ const manualSection = startIdx >= 0 ? content.slice(0, startIdx) : content;
245
+ const existingKeys = extractExistingKeys(manualSection);
246
+ console.log(` → ${existingKeys.size} existing entries kept as-is`);
247
+
248
+ // Merge: Disconnect first, DDG overrides (more precise display names)
249
+ const merged = new Map<string, TrackerEntry>();
250
+ for (const [domain, entry] of disconnectEntries) {
251
+ if (!existingKeys.has(domain)) merged.set(domain, entry);
252
+ }
253
+ for (const [domain, entry] of ddgEntries) {
254
+ if (!existingKeys.has(domain)) merged.set(domain, entry);
255
+ }
256
+ console.log(` → ${merged.size} new entries to write`);
257
+
258
+ const date = new Date().toISOString().slice(0, 10);
259
+ const newContent = patchFile(content, merged, date);
260
+
261
+ if (dryRun) {
262
+ console.log("\n[dry-run] Result preview (last 60 lines):\n");
263
+ console.log(newContent.split("\n").slice(-60).join("\n"));
264
+ } else {
265
+ writeFileSync(trackerListPath, newContent, "utf-8");
266
+ console.log(`\nWrote ${trackerListPath}`);
267
+ }
268
+ }
269
+
270
+ main().catch((err: unknown) => {
271
+ console.error(err);
272
+ process.exit(1);
273
+ });
@@ -21,10 +21,21 @@ interface ComplianceInput {
21
21
  export function analyzeCompliance(input: ComplianceInput): ComplianceScore {
22
22
  const issues: DarkPatternIssue[] = [];
23
23
 
24
+ // Determine whether a consent mechanism is actually required
25
+ const hasNonEssentialCookies = [
26
+ ...input.cookiesBeforeInteraction,
27
+ ...input.cookiesAfterAccept,
28
+ ].some((c) => c.requiresConsent);
29
+ const hasNonEssentialTrackers = [
30
+ ...input.networkBeforeInteraction,
31
+ ...input.networkAfterAccept,
32
+ ].some((r) => r.requiresConsent);
33
+ const consentRequired = hasNonEssentialCookies || hasNonEssentialTrackers;
34
+
24
35
  // ── A. Consent validity (0-25) ────────────────────────────────
25
36
  let consentValidity = 25;
26
37
 
27
- if (!input.modal.detected) {
38
+ if (!input.modal.detected && consentRequired) {
28
39
  issues.push({
29
40
  type: "no-reject-button",
30
41
  severity: "critical",
@@ -32,7 +43,7 @@ export function analyzeCompliance(input: ComplianceInput): ComplianceScore {
32
43
  evidence: "A consent mechanism is required before depositing non-essential cookies",
33
44
  });
34
45
  consentValidity = 0;
35
- } else {
46
+ } else if (input.modal.detected) {
36
47
  // Wording analysis
37
48
  const wordingResult = analyzeButtonWording(input.modal.buttons);
38
49
  const textResult = analyzeModalText(input.modal.text);
@@ -59,9 +70,9 @@ export function analyzeCompliance(input: ComplianceInput): ComplianceScore {
59
70
  // ── B. Easy refusal (0-25) ────────────────────────────────────
60
71
  let easyRefusal = 25;
61
72
 
62
- if (!input.modal.detected) {
73
+ if (!input.modal.detected && consentRequired) {
63
74
  easyRefusal = 0;
64
- } else {
75
+ } else if (input.modal.detected) {
65
76
  const acceptButton = input.modal.buttons.find((b) => b.type === "accept");
66
77
  const rejectButton = input.modal.buttons.find((b) => b.type === "reject");
67
78
 
@@ -110,14 +121,48 @@ export function analyzeCompliance(input: ComplianceInput): ComplianceScore {
110
121
  easyRefusal -= 5;
111
122
  }
112
123
  }
124
+
125
+ // Contrast ratio: reject button must meet minimum legibility
126
+ if (rejectButton && rejectButton.contrastRatio !== null) {
127
+ const ratio = rejectButton.contrastRatio;
128
+ if (ratio < 3.0) {
129
+ issues.push({
130
+ type: "asymmetric-prominence",
131
+ severity: "critical",
132
+ description: "Reject button has critically low contrast ratio",
133
+ evidence: `Contrast ratio ${ratio}:1 — WCAG AA requires 4.5:1 for normal text (${rejectButton.backgroundColor ?? "?"} / ${rejectButton.textColor ?? "?"})`,
134
+ });
135
+ easyRefusal -= 10;
136
+ } else if (ratio < 4.5) {
137
+ issues.push({
138
+ type: "asymmetric-prominence",
139
+ severity: "warning",
140
+ description: "Reject button contrast ratio is below WCAG AA threshold",
141
+ evidence: `Contrast ratio ${ratio}:1 — WCAG AA requires 4.5:1 for normal text (${rejectButton.backgroundColor ?? "?"} / ${rejectButton.textColor ?? "?"})`,
142
+ });
143
+ easyRefusal -= 5;
144
+ }
145
+
146
+ // Relative contrast asymmetry: accept visually pops, reject is muted
147
+ const acceptContrast = acceptButton?.contrastRatio ?? null;
148
+ if (acceptContrast !== null && acceptContrast >= rejectButton.contrastRatio * 1.5) {
149
+ issues.push({
150
+ type: "asymmetric-prominence",
151
+ severity: "warning",
152
+ description: "Accept button has significantly higher contrast than reject button",
153
+ evidence: `Accept: ${acceptContrast}:1, Reject: ${rejectButton.contrastRatio}:1`,
154
+ });
155
+ easyRefusal -= 3;
156
+ }
157
+ }
113
158
  }
114
159
 
115
160
  // ── C. Transparency (0-25) ────────────────────────────────────
116
161
  let transparency = 25;
117
162
 
118
- if (!input.modal.detected) {
163
+ if (!input.modal.detected && consentRequired) {
119
164
  transparency = 0;
120
- } else {
165
+ } else if (input.modal.detected) {
121
166
  if (!input.modal.hasGranularControls) {
122
167
  transparency -= 10;
123
168
  }
@@ -139,8 +184,8 @@ export function analyzeCompliance(input: ComplianceInput): ComplianceScore {
139
184
  }
140
185
  }
141
186
 
142
- // No privacy policy link anywhere on the page
143
- if (!input.privacyPolicyUrl) {
187
+ // No privacy policy link anywhere on the page (only relevant when consent is required)
188
+ if (!input.privacyPolicyUrl && consentRequired) {
144
189
  issues.push({
145
190
  type: "missing-info",
146
191
  severity: "warning",
@@ -182,9 +227,7 @@ export function analyzeCompliance(input: ComplianceInput): ComplianceScore {
182
227
  }
183
228
 
184
229
  // Network trackers firing before interaction
185
- const preInteractionTrackers = input.networkBeforeInteraction.filter(
186
- (r) => r.trackerCategory !== null && r.trackerCategory !== "cdn",
187
- );
230
+ const preInteractionTrackers = input.networkBeforeInteraction.filter((r) => r.requiresConsent);
188
231
 
189
232
  if (preInteractionTrackers.length > 0) {
190
233
  issues.push({
@@ -94,7 +94,8 @@ const COOKIE_PATTERNS: Array<{
94
94
 
95
95
  // ── Social ─────────────────────────────────────────────────────
96
96
  { pattern: /^(fbsr_|fbm_)/, category: "social", requiresConsent: true }, // Facebook login
97
- { pattern: /^(yt-|VISITOR_INFO|YSC|GPS)$/i, category: "social", requiresConsent: true }, // YouTube
97
+ { pattern: /^(yt-|YSC|GPS)$/i, category: "social", requiresConsent: true }, // YouTube
98
+ { pattern: /^VISITOR_INFO/i, category: "social", requiresConsent: true }, // YouTube VISITOR_INFO1_LIVE etc.
98
99
 
99
100
  // ── Personalization ────────────────────────────────────────────
100
101
  {
@@ -5,6 +5,7 @@ interface NetworkClassification {
5
5
  isThirdParty: boolean;
6
6
  trackerCategory: TrackerCategory | null;
7
7
  trackerName: string | null;
8
+ requiresConsent: boolean;
8
9
  }
9
10
 
10
11
  export function classifyNetworkRequest(url: string, resourceType: string): NetworkClassification {
@@ -13,7 +14,12 @@ export function classifyNetworkRequest(url: string, resourceType: string): Netwo
13
14
  try {
14
15
  hostname = new URL(url).hostname.replace(/^www\./, "");
15
16
  } catch {
16
- return { isThirdParty: false, trackerCategory: null, trackerName: null };
17
+ return {
18
+ isThirdParty: false,
19
+ trackerCategory: null,
20
+ trackerName: null,
21
+ requiresConsent: false,
22
+ };
17
23
  }
18
24
 
19
25
  // Check tracker database (exact match or suffix match)
@@ -23,6 +29,7 @@ export function classifyNetworkRequest(url: string, resourceType: string): Netwo
23
29
  isThirdParty: true,
24
30
  trackerCategory: entry.category,
25
31
  trackerName: entry.name,
32
+ requiresConsent: entry.consentRequired !== false && entry.category !== "cdn",
26
33
  };
27
34
  }
28
35
  }
@@ -33,6 +40,7 @@ export function classifyNetworkRequest(url: string, resourceType: string): Netwo
33
40
  isThirdParty: true,
34
41
  trackerCategory: "pixel",
35
42
  trackerName: "Tracking Pixel",
43
+ requiresConsent: true,
36
44
  };
37
45
  }
38
46
 
@@ -42,6 +50,7 @@ export function classifyNetworkRequest(url: string, resourceType: string): Netwo
42
50
  isThirdParty: true,
43
51
  trackerCategory: "pixel",
44
52
  trackerName: "Tracking Pixel (image)",
53
+ requiresConsent: true,
45
54
  };
46
55
  }
47
56
 
@@ -49,6 +58,7 @@ export function classifyNetworkRequest(url: string, resourceType: string): Netwo
49
58
  isThirdParty: false,
50
59
  trackerCategory: null,
51
60
  trackerName: null,
61
+ requiresConsent: false,
52
62
  };
53
63
  }
54
64
 
@@ -3,6 +3,7 @@ import type { TrackerCategory } from "../types.js";
3
3
  interface TrackerEntry {
4
4
  name: string;
5
5
  category: TrackerCategory;
6
+ consentRequired?: boolean; // absent = true by default
6
7
  }
7
8
 
8
9
  /**
@@ -25,7 +26,7 @@ export const TRACKER_DB: Record<string, TrackerEntry> = {
25
26
  // ── Meta / Facebook ───────────────────────────────────────────
26
27
  "connect.facebook.net": { name: "Facebook SDK", category: "social" },
27
28
  "graph.facebook.com": { name: "Facebook Graph API", category: "social" },
28
- "facebook.com/tr": { name: "Meta Pixel", category: "advertising" },
29
+ "pixel.facebook.com": { name: "Meta Pixel", category: "advertising" },
29
30
  "fbcdn.net": { name: "Facebook CDN", category: "social" },
30
31
 
31
32
  // ── Microsoft ─────────────────────────────────────────────────
@@ -89,6 +90,13 @@ export const TRACKER_DB: Record<string, TrackerEntry> = {
89
90
  "optimizely.com": { name: "Optimizely", category: "analytics" },
90
91
  "vwo.com": { name: "VWO", category: "analytics" },
91
92
  "app.convert.com": { name: "Convert", category: "analytics" },
93
+
94
+ // ── Consent-exempt analytics (CNIL ePrivacy exemption) ───────
95
+ "plausible.io": {
96
+ name: "Plausible Analytics",
97
+ category: "analytics",
98
+ consentRequired: false,
99
+ },
92
100
  };
93
101
 
94
102
  /**
package/src/cli.ts CHANGED
@@ -23,11 +23,7 @@ program
23
23
  .option("--no-screenshots", "Disable screenshot capture")
24
24
  .option("-l, --locale <locale>", "Browser locale for language detection", "fr-FR")
25
25
  .option("-v, --verbose", "Show detailed output", false)
26
- .option(
27
- "-f, --format <formats>",
28
- "Output formats: md, html, json, pdf (comma-separated)",
29
- "md,pdf",
30
- )
26
+ .option("-f, --format <formats>", "Output formats: md, html, json, pdf (comma-separated)", "html")
31
27
  .action(async (url: string, opts) => {
32
28
  console.log();
33
29
  console.log(styleText(["bold", "blue"], " GDPR Cookie Scanner"));