@slashgear/gdpr-cookie-scanner 3.6.0 → 3.7.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 (113) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/CLAUDE.md +12 -1
  3. package/NEXT_STEPS.md +37 -3
  4. package/README.md +23 -0
  5. package/dist/analyzers/colour.d.ts +36 -0
  6. package/dist/analyzers/colour.d.ts.map +1 -0
  7. package/dist/analyzers/colour.js +75 -0
  8. package/dist/analyzers/colour.js.map +1 -0
  9. package/dist/analyzers/compliance.d.ts.map +1 -1
  10. package/dist/analyzers/compliance.js +24 -6
  11. package/dist/analyzers/compliance.js.map +1 -1
  12. package/dist/analyzers/tcf-decoder.d.ts +9 -0
  13. package/dist/analyzers/tcf-decoder.d.ts.map +1 -0
  14. package/dist/analyzers/tcf-decoder.js +123 -0
  15. package/dist/analyzers/tcf-decoder.js.map +1 -0
  16. package/dist/analyzers/wording.d.ts +1 -0
  17. package/dist/analyzers/wording.d.ts.map +1 -1
  18. package/dist/analyzers/wording.js +39 -0
  19. package/dist/analyzers/wording.js.map +1 -1
  20. package/dist/report/generator.d.ts +1 -0
  21. package/dist/report/generator.d.ts.map +1 -1
  22. package/dist/report/generator.js +71 -1
  23. package/dist/report/generator.js.map +1 -1
  24. package/dist/report/html.d.ts.map +1 -1
  25. package/dist/report/html.js +123 -0
  26. package/dist/report/html.js.map +1 -1
  27. package/dist/scanner/consent-modal.d.ts.map +1 -1
  28. package/dist/scanner/consent-modal.js +4 -2
  29. package/dist/scanner/consent-modal.js.map +1 -1
  30. package/dist/scanner/index.d.ts.map +1 -1
  31. package/dist/scanner/index.js +4 -0
  32. package/dist/scanner/index.js.map +1 -1
  33. package/dist/scanner/tcf.d.ts +9 -0
  34. package/dist/scanner/tcf.d.ts.map +1 -0
  35. package/dist/scanner/tcf.js +72 -0
  36. package/dist/scanner/tcf.js.map +1 -0
  37. package/dist/types.d.ts +26 -0
  38. package/dist/types.d.ts.map +1 -1
  39. package/docs/index.html +37 -49
  40. package/docs/reports/www.arte.tv/after-accept.png +0 -0
  41. package/docs/reports/www.arte.tv/after-reject.png +0 -0
  42. package/docs/reports/www.arte.tv/gdpr-report-arte.tv-2026-02-24.html +997 -0
  43. package/docs/reports/www.deezer.com/after-accept.png +0 -0
  44. package/docs/reports/www.deezer.com/after-reject.png +0 -0
  45. package/docs/reports/www.deezer.com/gdpr-report-deezer.com-2026-02-22.html +1667 -0
  46. package/docs/reports/www.impots.gouv.fr/after-accept.png +0 -0
  47. package/docs/reports/www.impots.gouv.fr/after-reject.png +0 -0
  48. package/docs/reports/www.impots.gouv.fr/gdpr-report-impots.gouv.fr-2026-02-22.html +751 -0
  49. package/docs/reports/www.leboncoin.fr/after-accept.png +0 -0
  50. package/docs/reports/www.leboncoin.fr/after-reject.png +0 -0
  51. package/docs/reports/www.leboncoin.fr/gdpr-report-leboncoin.fr-2026-02-22.html +764 -0
  52. package/docs/reports/www.netflix.com/after-accept.png +0 -0
  53. package/docs/reports/www.netflix.com/after-reject.png +0 -0
  54. package/docs/reports/www.netflix.com/gdpr-report-netflix.com-2026-02-23.html +1050 -0
  55. package/docs/reports/www.radiofrance.fr/after-accept.png +0 -0
  56. package/docs/reports/www.radiofrance.fr/after-reject.png +0 -0
  57. package/docs/reports/www.radiofrance.fr/gdpr-report-radiofrance.fr-2026-02-24.html +1145 -0
  58. package/package.json +1 -1
  59. package/src/analyzers/colour.ts +89 -0
  60. package/src/analyzers/compliance.ts +35 -10
  61. package/src/analyzers/tcf-decoder.ts +130 -0
  62. package/src/analyzers/wording.ts +44 -0
  63. package/src/report/generator.ts +83 -1
  64. package/src/report/html.ts +146 -0
  65. package/src/scanner/consent-modal.ts +3 -1
  66. package/src/scanner/index.ts +5 -0
  67. package/src/scanner/tcf.ts +80 -0
  68. package/src/types.ts +29 -0
  69. package/tests/analyzers/colour.test.ts +187 -0
  70. package/tests/analyzers/compliance.test.ts +102 -0
  71. package/tests/analyzers/tcf-decoder.test.ts +292 -0
  72. package/tests/analyzers/wording.test.ts +38 -0
  73. package/tests/scanner/button-classification.test.ts +32 -0
  74. package/docs/reports/github.com/after-accept.png +0 -0
  75. package/docs/reports/github.com/after-reject.png +0 -0
  76. package/docs/reports/github.com/gdpr-checklist-github.com-2026-02-22.md +0 -44
  77. package/docs/reports/github.com/gdpr-cookies-github.com-2026-02-22.md +0 -29
  78. package/docs/reports/github.com/gdpr-report-github.com-2026-02-22.md +0 -102
  79. package/docs/reports/github.com/gdpr-report-github.com-2026-02-22.pdf +0 -0
  80. package/docs/reports/gitlab.com/after-accept.png +0 -0
  81. package/docs/reports/gitlab.com/after-reject.png +0 -0
  82. package/docs/reports/gitlab.com/gdpr-checklist-gitlab.com-2026-02-22.md +0 -44
  83. package/docs/reports/gitlab.com/gdpr-cookies-gitlab.com-2026-02-22.md +0 -55
  84. package/docs/reports/gitlab.com/gdpr-report-gitlab.com-2026-02-22.md +0 -200
  85. package/docs/reports/gitlab.com/gdpr-report-gitlab.com-2026-02-22.pdf +0 -0
  86. package/docs/reports/gitlab.com/modal-initial.png +0 -0
  87. package/docs/reports/npmjs.com/after-accept.png +0 -0
  88. package/docs/reports/npmjs.com/after-reject.png +0 -0
  89. package/docs/reports/npmjs.com/gdpr-checklist-npmjs.com-2026-02-22.md +0 -44
  90. package/docs/reports/npmjs.com/gdpr-cookies-npmjs.com-2026-02-22.md +0 -25
  91. package/docs/reports/npmjs.com/gdpr-report-npmjs.com-2026-02-22.md +0 -88
  92. package/docs/reports/npmjs.com/gdpr-report-npmjs.com-2026-02-22.pdf +0 -0
  93. package/docs/reports/reddit.com/after-accept.png +0 -0
  94. package/docs/reports/reddit.com/after-reject.png +0 -0
  95. package/docs/reports/reddit.com/gdpr-checklist-reddit.com-2026-02-22.md +0 -44
  96. package/docs/reports/reddit.com/gdpr-cookies-reddit.com-2026-02-22.md +0 -33
  97. package/docs/reports/reddit.com/gdpr-report-reddit.com-2026-02-22.md +0 -148
  98. package/docs/reports/reddit.com/gdpr-report-reddit.com-2026-02-22.pdf +0 -0
  99. package/docs/reports/reddit.com/modal-initial.png +0 -0
  100. package/docs/reports/stackoverflow.com/after-accept.png +0 -0
  101. package/docs/reports/stackoverflow.com/after-reject.png +0 -0
  102. package/docs/reports/stackoverflow.com/gdpr-checklist-stackoverflow.com-2026-02-22.md +0 -44
  103. package/docs/reports/stackoverflow.com/gdpr-cookies-stackoverflow.com-2026-02-22.md +0 -67
  104. package/docs/reports/stackoverflow.com/gdpr-report-stackoverflow.com-2026-02-22.md +0 -206
  105. package/docs/reports/stackoverflow.com/gdpr-report-stackoverflow.com-2026-02-22.pdf +0 -0
  106. package/docs/reports/stackoverflow.com/modal-initial.png +0 -0
  107. package/docs/reports/www.afp.com/after-accept.png +0 -0
  108. package/docs/reports/www.afp.com/after-reject.png +0 -0
  109. package/docs/reports/www.afp.com/gdpr-checklist-afp.com-2026-02-22.md +0 -44
  110. package/docs/reports/www.afp.com/gdpr-cookies-afp.com-2026-02-22.md +0 -42
  111. package/docs/reports/www.afp.com/gdpr-report-afp.com-2026-02-22.md +0 -202
  112. package/docs/reports/www.afp.com/gdpr-report-afp.com-2026-02-22.pdf +0 -0
  113. package/docs/reports/www.afp.com/modal-initial.png +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,49 @@
1
1
  # @slashgear/gdpr-cookie-scanner
2
2
 
3
+ ## 3.7.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 439a24d: feat: detect colour nudging dark pattern (green accept + grey/red reject)
8
+
9
+ Using a "positive" colour (green) on the accept button while the reject button
10
+ is visually de-emphasised (grey or red) is a documented dark pattern per
11
+ EDPB Guidelines 03/2022 § 3.3.3. The easyRefusal score now deducts 5 points
12
+ and surfaces a `nudging` warning when this pattern is detected.
13
+
14
+ A new `src/analyzers/colour.ts` module implements RGB→HSL conversion, perceptual
15
+ hue classification (green / red / grey / blue / neutral), and the nudging check.
16
+ 32 unit tests cover the colour math and the compliance integration.
17
+
18
+ ### Patch Changes
19
+
20
+ - ee162de: fix: classify reject before accept to handle "continuer sans accepter" dark pattern
21
+
22
+ Buttons like "Continuer sans accepter" or "Continue without accepting" contain the
23
+ word "accept/accepter" and were being incorrectly classified as accept buttons because
24
+ accept patterns were tested first. Swapping the check order (reject → accept) ensures
25
+ these rejection-phrased labels are correctly identified, preventing false negatives on
26
+ a very common dark pattern used by French and other European sites.
27
+
28
+ - 00c926b: fix: deduct points for indirect reject button labels in easyRefusal score
29
+
30
+ Buttons like "Continuer sans accepter" or "Continue without accepting" are technically
31
+ reject buttons but use indirect wording that makes the refusal non-obvious to users.
32
+ This is a dark pattern covered by EDPB Guidelines 03/2022 (§ 3.3.3 — hiding choices).
33
+
34
+ The easyRefusal score now deducts 5 points when such indirect wording is detected,
35
+ and a warning issue is surfaced in the report alongside the existing detection logic.
36
+
37
+ - 9f69e31: feat: add IAB TCF detection and consent string decoding
38
+
39
+ The scanner now detects TCF (Transparency & Consent Framework) implementations on scanned pages.
40
+ It checks for the `__tcfapi` JavaScript API, the `__tcfapiLocator` iframe, and `euconsent-v2`/`euconsent` cookies.
41
+ When a consent string is found, it is decoded (TCF v1 and v2 core segments) to extract CMP identity,
42
+ declared purposes, legitimate interests, and special features opt-ins.
43
+
44
+ This data is purely informational and does not affect the compliance score.
45
+ It appears as a new section in both the HTML and Markdown reports.
46
+
3
47
  ## 3.6.0
4
48
 
5
49
  ### Minor Changes
package/CLAUDE.md CHANGED
@@ -19,7 +19,18 @@ node dist/cli.js scan <url> -o ./reports --locale fr-FR --verbose
19
19
  node dist/cli.js list-trackers
20
20
  ```
21
21
 
22
- There are no tests currently.
22
+ ## Tests
23
+
24
+ Use [Vitest](https://vitest.dev/) for unit tests. Test files live in `tests/` and mirror the source structure (`tests/analyzers/`, `tests/classifiers/`, `tests/scanner/`, `tests/unit/`).
25
+
26
+ ```bash
27
+ pnpm test # run all tests
28
+ pnpm test:watch # watch mode
29
+ ```
30
+
31
+ **When to write tests:** every new feature or bug fix must include tests. Pure functions (decoders, classifiers, analyzers) are the primary targets. Browser-dependent code (scanner phases) is not unit-tested.
32
+
33
+ **When to update the README:** every new user-facing feature must be documented in `README.md` — CLI flags, output formats, new report sections, etc.
23
34
 
24
35
  ## Commit checklist
25
36
 
package/NEXT_STEPS.md CHANGED
@@ -20,13 +20,15 @@ Patterns that are explicitly listed in CNIL/EDPB guidelines but not yet detected
20
20
 
21
21
  - **Cookie wall** — detect when the page content is blurred, hidden, or inaccessible before consent is given. Requires comparing DOM structure / visible content area before and after interaction.
22
22
 
23
- - **Colour nudging** — accept button in green / reject button in grey or red is a documented dark pattern. Detect hue of each button's background colour and flag when accept is visually "positive" and reject is "negative".
23
+ - **Colour nudging** — accept button in green / reject button in grey or red is a documented dark pattern. Detect hue of each button's background colour and flag when accept is visually "positive" (green hue) and reject is "negative" (grey or red hue). Deduct from `easyRefusal`.
24
24
 
25
25
  - **Scroll- or navigation-as-consent** — some sites display a banner and treat scrolling as acceptance. Would require simulating a scroll event and checking whether the banner disappears without an explicit click.
26
26
 
27
27
  - **Bundled opt-outs** — some CMPs only provide "reject analytics + marketing" as a single checkbox rather than granular controls. Could be inferred from `hasGranularControls` combined with a single reject-all path.
28
28
 
29
- - **Consent fatigue / re-prompting** — detecting a banner reappearing on the next page load after rejection is hard without multi-page scanning, but worth exploring.
29
+ - **Legitimate interest abuse** — sites often declare legitimate interest for purposes 2–10 (advertising, profiling), bypassing consent entirely. When TCF is detected, flag when `purposesLegitimateInterest` includes ad-related IAB purposes (2, 3, 4, 7, 8, 9, 10).
30
+
31
+ - **Consent fatigue / re-prompting** — detect if the consent banner reappears on page reload after rejection. Requires a 5th scan phase: reload after reject and check for modal re-appearance.
30
32
 
31
33
  ---
32
34
 
@@ -40,6 +42,8 @@ Patterns that are explicitly listed in CNIL/EDPB guidelines but not yet detected
40
42
 
41
43
  - **Firefox / WebKit** — Playwright supports both. Firefox in particular is relevant because some CMPs behave differently across engines. A `--browser chromium|firefox|webkit` flag would be low-effort to wire up.
42
44
 
45
+ - **Screenshot capture** — capture a screenshot of the consent modal at detection time and embed it in the HTML report as visual evidence. Useful for audit trails and regulatory submissions.
46
+
43
47
  ---
44
48
 
45
49
  ## Tracker & cookie classification
@@ -50,12 +54,42 @@ Patterns that are explicitly listed in CNIL/EDPB guidelines but not yet detected
50
54
 
51
55
  ## Report improvements
52
56
 
53
- - **Single-file Markdown** — the current 3-file split (report + checklist + inventory) is logical but awkward to share or attach in a ticket. An optional `--merge-md` flag that concatenates the three files would help.
57
+ - **JSON output format** — export the full `ScanResult` as a `gdpr-report-*.json` file. Enables programmatic consumption in CI pipelines, dashboards, and historical comparisons without parsing Markdown or HTML.
58
+
59
+ - **CMP fingerprinting** — identify which CMP is in use (OneTrust, Didomi, Axeptio, Cookiebot, TrustArc, Usercentrics…) from script URLs, global objects (`window.Didomi`, `window.OneTrust`…), or CSS class names. Enrich the report and correlate compliance scores with specific vendors.
54
60
 
55
61
  - **Report localisation** — recommendations and section headings are hardcoded in French even when `--locale en-US` is used. The report language should follow `--locale`.
56
62
 
57
63
  - **Historical comparison** — if a previous JSON report for the same hostname exists in the output directory, surface a diff (score delta, issues resolved/introduced). Useful for tracking progress over time.
58
64
 
65
+ - **TCF consent string cross-check** — after rejection, re-read the TCF consent string and verify `purposesConsent` is empty. Currently we capture the string before interaction only; a mismatch between the declared rejection and a non-empty consent string would be a critical finding.
66
+
67
+ ---
68
+
69
+ ## Website (`docs/`)
70
+
71
+ The site is served as GitHub Pages from the `docs/` directory. Reports and cards are maintained manually.
72
+
73
+ - **HTML reports on the site** — generate `.html` reports into `docs/reports/<host>/` and point the "Live Reports" cards directly at the GitHub Pages URL instead of GitHub's Markdown renderer. Visitors stay on the site and get the full styled report experience.
74
+
75
+ - **Per-report `<title>` and `<meta description>`** — each HTML report should carry a specific page title (`"github.com — Grade F (15/100) | gdpr-cookie-scanner"`) and a meta description summarising the scan result. Currently the generator uses a generic title.
76
+
77
+ - **Open Graph / Twitter Card tags** — add `og:title`, `og:description`, `og:url` (and ideally `og:image` with a grade badge) to each report page so social shares display a meaningful preview card.
78
+
79
+ - **`sitemap.xml`** — auto-generated file listing the home page + every report page. Critical for search engine discovery of individual report pages. Can be a simple script that reads `docs/reports/` and writes the XML.
80
+
81
+ - **JSON-LD structured data** — `SoftwareApplication` schema on the home page; a `Dataset` or `Article` schema on each report page with `name`, `url`, `datePublished`, and a custom `score` property. Enables Google rich snippets.
82
+
83
+ - **`robots.txt`** — confirm (or add) a `robots.txt` that allows full crawling of `docs/` including report sub-directories.
84
+
85
+ - **Screenshots in the HTML report** — `after-reject.png` and `after-accept.png` are already captured alongside each report. Embedding them in the HTML report as visual evidence would make reports significantly more compelling.
86
+
87
+ - **Fix outdated copy** — the "What it checks" feature card still says "3 Markdown reports". Update to reflect the current single-file output and the HTML format.
88
+
89
+ - **"Submit a site" workflow** — a GitHub Issues template that lets users request a scan. A workflow picks it up, runs the scanner, commits the HTML report to `docs/reports/`, and posts a link back in the issue.
90
+
91
+ - **Dark mode** — `style.css` has no `@media (prefers-color-scheme: dark)` support. The design is simple enough that adding a dark palette would be straightforward.
92
+
59
93
  ---
60
94
 
61
95
  ## Testing
package/README.md CHANGED
@@ -317,9 +317,32 @@ All formats contain:
317
317
  - Detected dark patterns (missing reject button, visual asymmetry, pre-ticked boxes, misleading wording…)
318
318
  - Cookie table before interaction, after reject, after accept
319
319
  - Network tracker requests by phase
320
+ - **IAB TCF detection** — see below
320
321
  - Targeted recommendations
321
322
  - Legal references (RGPD, ePrivacy directive, CEPD guidelines, CNIL 2022)
322
323
 
324
+ ### IAB TCF detection
325
+
326
+ When a site uses the [IAB Transparency & Consent Framework](https://iabeurope.eu/tcf-2-0/), the scanner automatically detects and decodes it. This is **informational only** — it does not affect the compliance score.
327
+
328
+ Detected signals:
329
+
330
+ - `window.__tcfapi` (TCF v2) or `window.__cmp` (TCF v1) JavaScript API
331
+ - `__tcfapiLocator` iframe
332
+ - `euconsent-v2` / `euconsent` cookie
333
+
334
+ When a consent string is found (via API or cookie), it is decoded to expose:
335
+
336
+ | Field | Description |
337
+ | --------------------------------- | ----------------------------------------------- |
338
+ | CMP ID & version | Which Consent Management Platform is in use |
339
+ | Consent language | Language the consent was collected in |
340
+ | Vendor list version | IAB GVL version at time of consent |
341
+ | Purposes with consent | IAB purposes explicitly consented to (IDs 1–11) |
342
+ | Purposes with legitimate interest | IAB purposes claimed under legitimate interest |
343
+ | Special features opted in | Geolocation, device scanning (IDs 1–2) |
344
+ | Publisher country | Country code of the publisher |
345
+
323
346
  ## Scoring
324
347
 
325
348
  The score is made up of 4 criteria (25 points each):
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Colour nudging detection — EDPB Guidelines 03/2022 § 3.3.3.
3
+ *
4
+ * A "positive" colour (green = go, approve) on the accept button combined with
5
+ * a "negative" or neutral colour (grey, red) on the reject button steers users
6
+ * toward consent without technically hiding the refusal option.
7
+ */
8
+ /** Returns [hue 0–360, saturation 0–100, lightness 0–100]. */
9
+ export declare function rgbToHsl(r: number, g: number, b: number): [number, number, number];
10
+ export type ButtonHue = "green" | "red" | "grey" | "blue" | "neutral";
11
+ /**
12
+ * Classify the perceptual "valence" of a colour:
13
+ * - green → positive, approval (h 80–165, s ≥ 25)
14
+ * - red → negative, danger (h ≤ 20 or ≥ 340, s ≥ 25)
15
+ * - grey → neutral / muted (s < 20)
16
+ * - blue → informational (h 195–265, s ≥ 25)
17
+ * - neutral → anything else
18
+ *
19
+ * Very dark (<10 L) or very light (>93 L) colours are treated as neutral
20
+ * because their hue carries little visual weight in a button context.
21
+ */
22
+ export declare function classifyHue(r: number, g: number, b: number): ButtonHue;
23
+ export interface ColourNudgingResult {
24
+ acceptHue: ButtonHue | null;
25
+ rejectHue: ButtonHue | null;
26
+ /** True when accept is green and reject is grey or red. */
27
+ isNudging: boolean;
28
+ }
29
+ /**
30
+ * Detect colour nudging between the accept and reject buttons.
31
+ *
32
+ * Returns `isNudging: true` when the accept button has a "positive" hue (green)
33
+ * while the reject button has a "negative" or neutral hue (grey or red).
34
+ */
35
+ export declare function detectColourNudging(acceptBg: string | null | undefined, rejectBg: string | null | undefined): ColourNudgingResult;
36
+ //# sourceMappingURL=colour.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"colour.d.ts","sourceRoot":"","sources":["../../src/analyzers/colour.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAQH,8DAA8D;AAC9D,wBAAgB,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAsBlF;AAED,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;AAEtE;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,SAAS,CAQtE;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,SAAS,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,SAAS,GAAG,IAAI,CAAC;IAC5B,2DAA2D;IAC3D,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACnC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAClC,mBAAmB,CAUrB"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Colour nudging detection — EDPB Guidelines 03/2022 § 3.3.3.
3
+ *
4
+ * A "positive" colour (green = go, approve) on the accept button combined with
5
+ * a "negative" or neutral colour (grey, red) on the reject button steers users
6
+ * toward consent without technically hiding the refusal option.
7
+ */
8
+ function parseRgb(css) {
9
+ const m = css.match(/rgba?\(\s*(\d+),\s*(\d+),\s*(\d+)/);
10
+ if (!m)
11
+ return null;
12
+ return [parseInt(m[1], 10), parseInt(m[2], 10), parseInt(m[3], 10)];
13
+ }
14
+ /** Returns [hue 0–360, saturation 0–100, lightness 0–100]. */
15
+ export function rgbToHsl(r, g, b) {
16
+ const rr = r / 255, gg = g / 255, bb = b / 255;
17
+ const max = Math.max(rr, gg, bb), min = Math.min(rr, gg, bb);
18
+ const l = (max + min) / 2;
19
+ if (max === min)
20
+ return [0, 0, Math.round(l * 100)];
21
+ const d = max - min;
22
+ const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
23
+ let h;
24
+ switch (max) {
25
+ case rr:
26
+ h = (gg - bb) / d + (gg < bb ? 6 : 0);
27
+ break;
28
+ case gg:
29
+ h = (bb - rr) / d + 2;
30
+ break;
31
+ default:
32
+ h = (rr - gg) / d + 4;
33
+ }
34
+ return [Math.round(h * 60), Math.round(s * 100), Math.round(l * 100)];
35
+ }
36
+ /**
37
+ * Classify the perceptual "valence" of a colour:
38
+ * - green → positive, approval (h 80–165, s ≥ 25)
39
+ * - red → negative, danger (h ≤ 20 or ≥ 340, s ≥ 25)
40
+ * - grey → neutral / muted (s < 20)
41
+ * - blue → informational (h 195–265, s ≥ 25)
42
+ * - neutral → anything else
43
+ *
44
+ * Very dark (<10 L) or very light (>93 L) colours are treated as neutral
45
+ * because their hue carries little visual weight in a button context.
46
+ */
47
+ export function classifyHue(r, g, b) {
48
+ const [h, s, l] = rgbToHsl(r, g, b);
49
+ if (l < 10 || l > 93)
50
+ return "neutral";
51
+ if (s < 20)
52
+ return "grey";
53
+ if (h >= 80 && h <= 165)
54
+ return "green";
55
+ if (h <= 20 || h >= 340)
56
+ return "red";
57
+ if (h >= 195 && h <= 265)
58
+ return "blue";
59
+ return "neutral";
60
+ }
61
+ /**
62
+ * Detect colour nudging between the accept and reject buttons.
63
+ *
64
+ * Returns `isNudging: true` when the accept button has a "positive" hue (green)
65
+ * while the reject button has a "negative" or neutral hue (grey or red).
66
+ */
67
+ export function detectColourNudging(acceptBg, rejectBg) {
68
+ const acceptRgb = acceptBg ? parseRgb(acceptBg) : null;
69
+ const rejectRgb = rejectBg ? parseRgb(rejectBg) : null;
70
+ const acceptHue = acceptRgb ? classifyHue(...acceptRgb) : null;
71
+ const rejectHue = rejectRgb ? classifyHue(...rejectRgb) : null;
72
+ const isNudging = acceptHue === "green" && (rejectHue === "grey" || rejectHue === "red");
73
+ return { acceptHue, rejectHue, isNudging };
74
+ }
75
+ //# sourceMappingURL=colour.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"colour.js","sourceRoot":"","sources":["../../src/analyzers/colour.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"compliance.d.ts","sourceRoot":"","sources":["../../src/analyzers/compliance.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,YAAY,EAEZ,aAAa,EACb,cAAc,EACf,MAAM,aAAa,CAAC;AAGrB,UAAU,eAAe;IACvB,KAAK,EAAE,YAAY,CAAC;IACpB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,wBAAwB,EAAE,aAAa,EAAE,CAAC;IAC1C,kBAAkB,EAAE,aAAa,EAAE,CAAC;IACpC,kBAAkB,EAAE,aAAa,EAAE,CAAC;IACpC,wBAAwB,EAAE,cAAc,EAAE,CAAC;IAC3C,kBAAkB,EAAE,cAAc,EAAE,CAAC;IACrC,kBAAkB,EAAE,cAAc,EAAE,CAAC;CACtC;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,eAAe,GAAG,eAAe,CAgPzE"}
1
+ {"version":3,"file":"compliance.d.ts","sourceRoot":"","sources":["../../src/analyzers/compliance.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,YAAY,EAEZ,aAAa,EACb,cAAc,EACf,MAAM,aAAa,CAAC;AAIrB,UAAU,eAAe;IACvB,KAAK,EAAE,YAAY,CAAC;IACpB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,wBAAwB,EAAE,aAAa,EAAE,CAAC;IAC1C,kBAAkB,EAAE,aAAa,EAAE,CAAC;IACpC,kBAAkB,EAAE,aAAa,EAAE,CAAC;IACpC,wBAAwB,EAAE,cAAc,EAAE,CAAC;IAC3C,kBAAkB,EAAE,cAAc,EAAE,CAAC;IACrC,kBAAkB,EAAE,cAAc,EAAE,CAAC;CACtC;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,eAAe,GAAG,eAAe,CAwQzE"}
@@ -1,4 +1,5 @@
1
1
  import { analyzeButtonWording, analyzeModalText } from "./wording.js";
2
+ import { detectColourNudging } from "./colour.js";
2
3
  export function analyzeCompliance(input) {
3
4
  const issues = [];
4
5
  // Determine whether a consent mechanism is actually required
@@ -11,6 +12,9 @@ export function analyzeCompliance(input) {
11
12
  ...input.networkAfterAccept,
12
13
  ].some((r) => r.requiresConsent);
13
14
  const consentRequired = hasNonEssentialCookies || hasNonEssentialTrackers;
15
+ // Run wording analysis once — modal may not be detected, so these can be null
16
+ const wordingResult = input.modal.detected ? analyzeButtonWording(input.modal.buttons) : null;
17
+ const textResult = input.modal.detected ? analyzeModalText(input.modal.text) : null;
14
18
  // ── A. Consent validity (0-25) ────────────────────────────────
15
19
  let consentValidity = 25;
16
20
  if (!input.modal.detected && consentRequired) {
@@ -23,9 +27,7 @@ export function analyzeCompliance(input) {
23
27
  consentValidity = 0;
24
28
  }
25
29
  else if (input.modal.detected) {
26
- // Wording analysis
27
- const wordingResult = analyzeButtonWording(input.modal.buttons);
28
- const textResult = analyzeModalText(input.modal.text);
30
+ // Wording analysis (wordingResult / textResult hoisted above)
29
31
  issues.push(...wordingResult.issues, ...textResult.issues);
30
32
  // Pre-ticked checkboxes
31
33
  const preTicked = input.modal.checkboxes.filter((c) => c.isCheckedByDefault);
@@ -86,6 +88,23 @@ export function analyzeCompliance(input) {
86
88
  easyRefusal -= 5;
87
89
  }
88
90
  }
91
+ // Indirect reject label ("continuer sans accepter", "continue without accepting"…)
92
+ if (wordingResult?.hasIndirectRejectLabel) {
93
+ easyRefusal -= 5;
94
+ }
95
+ // Colour nudging: green accept + grey/red reject
96
+ if (acceptButton && rejectButton) {
97
+ const { isNudging, acceptHue, rejectHue } = detectColourNudging(acceptButton.backgroundColor, rejectButton.backgroundColor);
98
+ if (isNudging) {
99
+ issues.push({
100
+ type: "nudging",
101
+ severity: "warning",
102
+ description: 'Accept button uses a "positive" colour (green) while reject is visually de-emphasised',
103
+ evidence: `Accept: ${acceptButton.backgroundColor} (${acceptHue}), Reject: ${rejectButton.backgroundColor} (${rejectHue}) — EDPB Guidelines 03/2022 § 3.3.3`,
104
+ });
105
+ easyRefusal -= 5;
106
+ }
107
+ }
89
108
  // Font size asymmetry
90
109
  if (acceptButton?.fontSize && rejectButton?.fontSize) {
91
110
  if (acceptButton.fontSize > rejectButton.fontSize * 1.3) {
@@ -142,9 +161,8 @@ export function analyzeCompliance(input) {
142
161
  transparency -= 10;
143
162
  }
144
163
  // Already deducted in consentValidity for missing info
145
- const wordingResult = analyzeModalText(input.modal.text);
146
- if (wordingResult.missingInfo.length > 0) {
147
- transparency -= wordingResult.missingInfo.length * 3;
164
+ if (textResult.missingInfo.length > 0) {
165
+ transparency -= textResult.missingInfo.length * 3;
148
166
  }
149
167
  // No privacy policy link in the modal
150
168
  if (!input.modal.privacyPolicyUrl) {
@@ -1 +1 @@
1
- {"version":3,"file":"compliance.js","sourceRoot":"","sources":["../../src/analyzers/compliance.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"compliance.js","sourceRoot":"","sources":["../../src/analyzers/compliance.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { TcfConsentString } from "../types.js";
2
+ export declare const IAB_PURPOSES: Record<number, string>;
3
+ export declare const IAB_SPECIAL_FEATURES: Record<number, string>;
4
+ /**
5
+ * Decode a TCF v1 or v2 consent string (core segment only).
6
+ * Returns null if decoding fails or if the version is not 1 or 2.
7
+ */
8
+ export declare function decodeTcfConsentString(raw: string): TcfConsentString | null;
9
+ //# sourceMappingURL=tcf-decoder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tcf-decoder.d.ts","sourceRoot":"","sources":["../../src/analyzers/tcf-decoder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAY/C,CAAC;AAEF,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAGvD,CAAC;AAwCF;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAkE3E"}
@@ -0,0 +1,123 @@
1
+ export const IAB_PURPOSES = {
2
+ 1: "Store and/or access information on a device",
3
+ 2: "Select basic ads",
4
+ 3: "Create a personalised ads profile",
5
+ 4: "Select personalised ads",
6
+ 5: "Create a personalised content profile",
7
+ 6: "Select personalised content",
8
+ 7: "Measure ad performance",
9
+ 8: "Measure content performance",
10
+ 9: "Apply market research to generate audience insights",
11
+ 10: "Develop and improve products",
12
+ 11: "Use limited data to select content",
13
+ };
14
+ export const IAB_SPECIAL_FEATURES = {
15
+ 1: "Use precise geolocation data",
16
+ 2: "Actively scan device characteristics for identification",
17
+ };
18
+ class BitReader {
19
+ buf;
20
+ pos = 0;
21
+ constructor(buf) {
22
+ this.buf = buf;
23
+ }
24
+ readBits(n) {
25
+ let value = 0;
26
+ for (let i = 0; i < n; i++) {
27
+ const byteIndex = Math.floor(this.pos / 8);
28
+ if (byteIndex >= this.buf.length)
29
+ throw new Error("BitReader: out of bounds");
30
+ const bitIndex = 7 - (this.pos % 8);
31
+ const bit = (this.buf[byteIndex] >> bitIndex) & 1;
32
+ // Use multiplication instead of bit shift to avoid 32-bit overflow on 36-bit timestamps
33
+ value = value * 2 + bit;
34
+ this.pos++;
35
+ }
36
+ return value;
37
+ }
38
+ }
39
+ function deciSecondsToDate(ds) {
40
+ return new Date(ds * 100);
41
+ }
42
+ function readLanguage(reader) {
43
+ const c1 = reader.readBits(6) + 65; // 'A' = 0
44
+ const c2 = reader.readBits(6) + 65;
45
+ return String.fromCharCode(c1, c2);
46
+ }
47
+ function readBitField(reader, count) {
48
+ const active = [];
49
+ for (let i = 1; i <= count; i++) {
50
+ if (reader.readBits(1) === 1)
51
+ active.push(i);
52
+ }
53
+ return active;
54
+ }
55
+ /**
56
+ * Decode a TCF v1 or v2 consent string (core segment only).
57
+ * Returns null if decoding fails or if the version is not 1 or 2.
58
+ */
59
+ export function decodeTcfConsentString(raw) {
60
+ try {
61
+ // Take only the core segment (before '~')
62
+ const coreSegment = raw.split("~")[0];
63
+ // Convert base64url to standard base64 and decode
64
+ const base64 = coreSegment.replace(/-/g, "+").replace(/_/g, "/");
65
+ const buf = Buffer.from(base64, "base64");
66
+ const reader = new BitReader(buf);
67
+ const version = reader.readBits(6);
68
+ if (version !== 1 && version !== 2)
69
+ return null;
70
+ const created = deciSecondsToDate(reader.readBits(36));
71
+ const lastUpdated = deciSecondsToDate(reader.readBits(36));
72
+ const cmpId = reader.readBits(12);
73
+ const cmpVersion = reader.readBits(12);
74
+ reader.readBits(6); // consentScreen (unused)
75
+ const consentLanguage = readLanguage(reader);
76
+ const vendorListVersion = reader.readBits(12);
77
+ if (version === 1) {
78
+ const purposesAllowed = readBitField(reader, 24);
79
+ return {
80
+ raw,
81
+ version: 1,
82
+ created,
83
+ lastUpdated,
84
+ cmpId,
85
+ cmpVersion,
86
+ consentLanguage,
87
+ vendorListVersion,
88
+ specialFeatureOptins: [],
89
+ purposesConsent: purposesAllowed,
90
+ purposesLegitimateInterest: [],
91
+ };
92
+ }
93
+ // TCF v2
94
+ const tcfPolicyVersion = reader.readBits(6);
95
+ const isServiceSpecific = reader.readBits(1) === 1;
96
+ reader.readBits(1); // useNonStandardStacks
97
+ const specialFeatureOptins = readBitField(reader, 12);
98
+ const purposesConsent = readBitField(reader, 24);
99
+ const purposesLegitimateInterest = readBitField(reader, 24);
100
+ reader.readBits(1); // purposeOneTreatment
101
+ const publisherCC = readLanguage(reader);
102
+ return {
103
+ raw,
104
+ version: 2,
105
+ created,
106
+ lastUpdated,
107
+ cmpId,
108
+ cmpVersion,
109
+ consentLanguage,
110
+ vendorListVersion,
111
+ tcfPolicyVersion,
112
+ isServiceSpecific,
113
+ specialFeatureOptins,
114
+ purposesConsent,
115
+ purposesLegitimateInterest,
116
+ publisherCC,
117
+ };
118
+ }
119
+ catch {
120
+ return null;
121
+ }
122
+ }
123
+ //# sourceMappingURL=tcf-decoder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tcf-decoder.js","sourceRoot":"","sources":["../../src/analyzers/tcf-decoder.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,YAAY,GAA2B;IAClD,CAAC,EAAE,6CAA6C;IAChD,CAAC,EAAE,kBAAkB;IACrB,CAAC,EAAE,mCAAmC;IACtC,CAAC,EAAE,yBAAyB;IAC5B,CAAC,EAAE,uCAAuC;IAC1C,CAAC,EAAE,6BAA6B;IAChC,CAAC,EAAE,wBAAwB;IAC3B,CAAC,EAAE,6BAA6B;IAChC,CAAC,EAAE,qDAAqD;IACxD,EAAE,EAAE,8BAA8B;IAClC,EAAE,EAAE,oCAAoC;CACzC,CAAC;AAEF,MAAM,CAAC,MAAM,oBAAoB,GAA2B;IAC1D,CAAC,EAAE,8BAA8B;IACjC,CAAC,EAAE,yDAAyD;CAC7D,CAAC;AAEF,MAAM,SAAS;IAGgB;IAFrB,GAAG,GAAG,CAAC,CAAC;IAEhB,YAA6B,GAAW;QAAX,QAAG,GAAH,GAAG,CAAQ;IAAG,CAAC;IAE5C,QAAQ,CAAC,CAAS;QAChB,IAAI,KAAK,GAAG,CAAC,CAAC;QACd,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAC3B,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;YAC3C,IAAI,SAAS,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM;gBAAE,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;YAC9E,MAAM,QAAQ,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;YACpC,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,QAAQ,CAAC,GAAG,CAAC,CAAC;YAClD,wFAAwF;YACxF,KAAK,GAAG,KAAK,GAAG,CAAC,GAAG,GAAG,CAAC;YACxB,IAAI,CAAC,GAAG,EAAE,CAAC;QACb,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;CACF;AAED,SAAS,iBAAiB,CAAC,EAAU;IACnC,OAAO,IAAI,IAAI,CAAC,EAAE,GAAG,GAAG,CAAC,CAAC;AAC5B,CAAC;AAED,SAAS,YAAY,CAAC,MAAiB;IACrC,MAAM,EAAE,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,UAAU;IAC9C,MAAM,EAAE,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;IACnC,OAAO,MAAM,CAAC,YAAY,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;AACrC,CAAC;AAED,SAAS,YAAY,CAAC,MAAiB,EAAE,KAAa;IACpD,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;QAChC,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC;YAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC/C,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CAAC,GAAW;IAChD,IAAI,CAAC;QACH,0CAA0C;QAC1C,MAAM,WAAW,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QACtC,kDAAkD;QAClD,MAAM,MAAM,GAAG,WAAW,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QACjE,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QAC1C,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,GAAG,CAAC,CAAC;QAElC,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;QACnC,IAAI,OAAO,KAAK,CAAC,IAAI,OAAO,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAEhD,MAAM,OAAO,GAAG,iBAAiB,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC;QACvD,MAAM,WAAW,GAAG,iBAAiB,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC;QAC3D,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QAClC,MAAM,UAAU,GAAG,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QACvC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,yBAAyB;QAC7C,MAAM,eAAe,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;QAC7C,MAAM,iBAAiB,GAAG,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QAE9C,IAAI,OAAO,KAAK,CAAC,EAAE,CAAC;YAClB,MAAM,eAAe,GAAG,YAAY,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;YACjD,OAAO;gBACL,GAAG;gBACH,OAAO,EAAE,CAAC;gBACV,OAAO;gBACP,WAAW;gBACX,KAAK;gBACL,UAAU;gBACV,eAAe;gBACf,iBAAiB;gBACjB,oBAAoB,EAAE,EAAE;gBACxB,eAAe,EAAE,eAAe;gBAChC,0BAA0B,EAAE,EAAE;aAC/B,CAAC;QACJ,CAAC;QAED,SAAS;QACT,MAAM,gBAAgB,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;QAC5C,MAAM,iBAAiB,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QACnD,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,uBAAuB;QAC3C,MAAM,oBAAoB,GAAG,YAAY,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACtD,MAAM,eAAe,GAAG,YAAY,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACjD,MAAM,0BAA0B,GAAG,YAAY,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QAC5D,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,sBAAsB;QAC1C,MAAM,WAAW,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;QAEzC,OAAO;YACL,GAAG;YACH,OAAO,EAAE,CAAC;YACV,OAAO;YACP,WAAW;YACX,KAAK;YACL,UAAU;YACV,eAAe;YACf,iBAAiB;YACjB,gBAAgB;YAChB,iBAAiB;YACjB,oBAAoB;YACpB,eAAe;YACf,0BAA0B;YAC1B,WAAW;SACZ,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
@@ -4,6 +4,7 @@ export interface WordingAnalysis {
4
4
  missingInfo: string[];
5
5
  hasPositiveActionForAccept: boolean;
6
6
  hasExplicitRejectOption: boolean;
7
+ hasIndirectRejectLabel: boolean;
7
8
  }
8
9
  export declare function analyzeButtonWording(buttons: ConsentButton[]): WordingAnalysis;
9
10
  export declare function analyzeModalText(text: string): {
@@ -1 +1 @@
1
- {"version":3,"file":"wording.d.ts","sourceRoot":"","sources":["../../src/analyzers/wording.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AA6BnE,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,gBAAgB,EAAE,CAAC;IAC3B,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,0BAA0B,EAAE,OAAO,CAAC;IACpC,uBAAuB,EAAE,OAAO,CAAC;CAClC;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,aAAa,EAAE,GAAG,eAAe,CAoD9E;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG;IAC9C,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,MAAM,EAAE,gBAAgB,EAAE,CAAC;CAC5B,CAkBA"}
1
+ {"version":3,"file":"wording.d.ts","sourceRoot":"","sources":["../../src/analyzers/wording.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AA0DnE,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,gBAAgB,EAAE,CAAC;IAC3B,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,0BAA0B,EAAE,OAAO,CAAC;IACpC,uBAAuB,EAAE,OAAO,CAAC;IACjC,sBAAsB,EAAE,OAAO,CAAC;CACjC;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,aAAa,EAAE,GAAG,eAAe,CAkE9E;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG;IAC9C,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,MAAM,EAAE,gBAAgB,EAAE,CAAC;CAC5B,CAkBA"}
@@ -10,6 +10,34 @@ const MISLEADING_ACCEPT_LABELS = [
10
10
  * Labels that suggest rejection but are actually just "close" or navigate away.
11
11
  */
12
12
  const FAKE_REJECT_LABELS = [/^(×|✕|✖|close|fermer|dismiss|ignorer|skip|passer)$/i];
13
+ /**
14
+ * Indirect reject labels: the button does refuse, but without using a clear negative
15
+ * word like "refuse/reject". The user has to infer the refusal from context.
16
+ * Flagged as a dark pattern per EDPB Guidelines 03/2022 (§ 3.3.3 — hiding choices).
17
+ *
18
+ * Pattern logic: "continue/proceed + without/sans/sin/senza/ohne + consent-related word".
19
+ */
20
+ const INDIRECT_REJECT_LABELS = [
21
+ // English
22
+ /\bcontinue\s+without\b/i,
23
+ /\bproceed\s+without\b/i,
24
+ /\bwithout\s+(accepting|accept|consent|consenting)\b/i,
25
+ // French
26
+ /\bcontinuer\s+sans\b/i,
27
+ /\bsans\s+(accepter|consentir|cookies)\b/i,
28
+ // Spanish
29
+ /\bcontinuar\s+sin\b/i,
30
+ /\bsin\s+(aceptar|consentir)\b/i,
31
+ // Italian
32
+ /\bcontinua\s+senza\b/i,
33
+ /\bsenza\s+(accettare|consenso)\b/i,
34
+ // German
35
+ /\bweiter\s+ohne\b/i,
36
+ /\bohne\s+(akzeptieren|zustimmen)\b/i,
37
+ // Dutch
38
+ /\bvervolgenen?\s+zonder\b/i,
39
+ /\bzonder\s+(accepteren|toestemming)\b/i,
40
+ ];
13
41
  /**
14
42
  * Required informational elements in consent text (RGPD Art. 13-14).
15
43
  */
@@ -64,11 +92,22 @@ export function analyzeButtonWording(buttons) {
64
92
  }
65
93
  }
66
94
  }
95
+ // ── Indirect reject (refusal implied, not stated) ─────────────
96
+ const hasIndirectRejectLabel = !!rejectButton && INDIRECT_REJECT_LABELS.some((p) => p.test(rejectButton.text));
97
+ if (hasIndirectRejectLabel && rejectButton) {
98
+ issues.push({
99
+ type: "misleading-wording",
100
+ severity: "warning",
101
+ description: `Reject button uses indirect wording: "${rejectButton.text}"`,
102
+ evidence: 'EDPB Guidelines 03/2022: the refusal option must be as clear as acceptance — indirect phrases like "continue without accepting" obscure the user\'s choice',
103
+ });
104
+ }
67
105
  return {
68
106
  issues,
69
107
  missingInfo: [], // filled in by analyzeModalText
70
108
  hasPositiveActionForAccept: !!acceptButton,
71
109
  hasExplicitRejectOption: !!rejectButton,
110
+ hasIndirectRejectLabel,
72
111
  };
73
112
  }
74
113
  export function analyzeModalText(text) {