privalint 0.0.0-stage → 0.2.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 (94) hide show
  1. package/LICENSE +21 -0
  2. package/README.de.md +89 -0
  3. package/README.md +222 -2
  4. package/dist/cli.d.ts +2 -0
  5. package/dist/cli.js +183 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/consent.d.ts +12 -0
  8. package/dist/consent.js +185 -0
  9. package/dist/consent.js.map +1 -0
  10. package/dist/data/third-parties.d.ts +13 -0
  11. package/dist/data/third-parties.js +42 -0
  12. package/dist/data/third-parties.js.map +1 -0
  13. package/dist/data/trackers.d.ts +21 -0
  14. package/dist/data/trackers.js +135 -0
  15. package/dist/data/trackers.js.map +1 -0
  16. package/dist/i18n.d.ts +5 -0
  17. package/dist/i18n.js +3 -0
  18. package/dist/i18n.js.map +1 -0
  19. package/dist/index.d.ts +6 -0
  20. package/dist/index.js +6 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/legal.d.ts +10 -0
  23. package/dist/legal.js +99 -0
  24. package/dist/legal.js.map +1 -0
  25. package/dist/meta.d.ts +3 -0
  26. package/dist/meta.js +6 -0
  27. package/dist/meta.js.map +1 -0
  28. package/dist/reporters/index.d.ts +11 -0
  29. package/dist/reporters/index.js +19 -0
  30. package/dist/reporters/index.js.map +1 -0
  31. package/dist/reporters/json.d.ts +2 -0
  32. package/dist/reporters/json.js +2 -0
  33. package/dist/reporters/json.js.map +1 -0
  34. package/dist/reporters/markdown.d.ts +9 -0
  35. package/dist/reporters/markdown.js +49 -0
  36. package/dist/reporters/markdown.js.map +1 -0
  37. package/dist/reporters/messages.d.ts +21 -0
  38. package/dist/reporters/messages.js +62 -0
  39. package/dist/reporters/messages.js.map +1 -0
  40. package/dist/reporters/sarif.d.ts +10 -0
  41. package/dist/reporters/sarif.js +84 -0
  42. package/dist/reporters/sarif.js.map +1 -0
  43. package/dist/reporters/shared.d.ts +3 -0
  44. package/dist/reporters/shared.js +17 -0
  45. package/dist/reporters/shared.js.map +1 -0
  46. package/dist/reporters/text.d.ts +2 -0
  47. package/dist/reporters/text.js +56 -0
  48. package/dist/reporters/text.js.map +1 -0
  49. package/dist/rules/google-fonts.d.ts +2 -0
  50. package/dist/rules/google-fonts.js +68 -0
  51. package/dist/rules/google-fonts.js.map +1 -0
  52. package/dist/rules/impressum-link.d.ts +2 -0
  53. package/dist/rules/impressum-link.js +59 -0
  54. package/dist/rules/impressum-link.js.map +1 -0
  55. package/dist/rules/index.d.ts +11 -0
  56. package/dist/rules/index.js +20 -0
  57. package/dist/rules/index.js.map +1 -0
  58. package/dist/rules/links.d.ts +8 -0
  59. package/dist/rules/links.js +16 -0
  60. package/dist/rules/links.js.map +1 -0
  61. package/dist/rules/pre-consent-trackers.d.ts +2 -0
  62. package/dist/rules/pre-consent-trackers.js +45 -0
  63. package/dist/rules/pre-consent-trackers.js.map +1 -0
  64. package/dist/rules/privacy-policy-link.d.ts +2 -0
  65. package/dist/rules/privacy-policy-link.js +33 -0
  66. package/dist/rules/privacy-policy-link.js.map +1 -0
  67. package/dist/rules/reject-option.d.ts +2 -0
  68. package/dist/rules/reject-option.js +66 -0
  69. package/dist/rules/reject-option.js.map +1 -0
  70. package/dist/rules/third-party-cdns.d.ts +2 -0
  71. package/dist/rules/third-party-cdns.js +30 -0
  72. package/dist/rules/third-party-cdns.js.map +1 -0
  73. package/dist/rules/third-party-embeds.d.ts +2 -0
  74. package/dist/rules/third-party-embeds.js +30 -0
  75. package/dist/rules/third-party-embeds.js.map +1 -0
  76. package/dist/rules/third-party-hits.d.ts +4 -0
  77. package/dist/rules/third-party-hits.js +13 -0
  78. package/dist/rules/third-party-hits.js.map +1 -0
  79. package/dist/rules/tracker-hits.d.ts +11 -0
  80. package/dist/rules/tracker-hits.js +40 -0
  81. package/dist/rules/tracker-hits.js.map +1 -0
  82. package/dist/rules/trackers-after-reject.d.ts +2 -0
  83. package/dist/rules/trackers-after-reject.js +39 -0
  84. package/dist/rules/trackers-after-reject.js.map +1 -0
  85. package/dist/scanner.d.ts +39 -0
  86. package/dist/scanner.js +175 -0
  87. package/dist/scanner.js.map +1 -0
  88. package/dist/types.d.ts +105 -0
  89. package/dist/types.js +2 -0
  90. package/dist/types.js.map +1 -0
  91. package/dist/url.d.ts +26 -0
  92. package/dist/url.js +71 -0
  93. package/dist/url.js.map +1 -0
  94. package/package.json +65 -4
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 JExcellence
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.de.md ADDED
@@ -0,0 +1,89 @@
1
+ # privalint
2
+
3
+ **English:** [README.md](README.md)
4
+
5
+ privalint prüft Websites auf die Datenschutzfehler, für die es Abmahnungen gibt: Tracker, die vor
6
+ der Einwilligung laden oder nach „Ablehnen“ weiterlaufen, Google Fonts vom Google-Server,
7
+ eingebundene Inhalte wie YouTube oder Google Maps und ein fehlendes Impressum.
8
+
9
+ privalint öffnet die Seite in einem echten Chromium-Browser ohne Cookies, wie beim ersten Besuch.
10
+ Es zeichnet auf, was geladen wird, bevor jemand das Cookie-Banner bedient, klickt dann auf
11
+ „Ablehnen“ und prüft, was trotzdem noch lädt. Es läuft im Terminal oder als GitHub Action bei
12
+ jedem Pull Request.
13
+
14
+ ```bash
15
+ npx privalint https://ihre-seite.de --lang de
16
+ ```
17
+
18
+ > [!IMPORTANT]
19
+ > privalint führt automatisierte Prüfungen durch. Ein sauberes Ergebnis bedeutet, dass keines der
20
+ > geprüften Probleme gefunden wurde. Es macht eine Website nicht DSGVO-konform und ist keine
21
+ > Rechtsberatung.
22
+
23
+ ## Deutschland, Österreich, Schweiz
24
+
25
+ Mit `--region` wählen Sie, nach welchem Recht privalint die Befunde einordnet. Jeder Befund nennt
26
+ die Rechtsgrundlage des gewählten Landes.
27
+
28
+ | Region | Cookies und Tracking | Impressum | Besonderheit |
29
+ | --------------- | ----------------------------------------- | ----------------------------------------------- | ----------------------------------------------------- |
30
+ | `de` (Standard) | Opt-in, § 25 TDDDG | § 5 DDG | – |
31
+ | `at` | Opt-in, § 165 Abs. 3 TKG 2021 | § 5 ECG, § 25 MedienG | – |
32
+ | `ch` | Opt-out, Art. 45c FMG | Art. 3 Abs. 1 lit. s UWG (bei Online-Angeboten) | Tracker, Einbindungen und Impressum eine Stufe milder |
33
+ | `eu` | Opt-in, Art. 5 Abs. 3 ePrivacy-Richtlinie | Art. 5 E-Commerce-Richtlinie | – |
34
+
35
+ Tracker, die nach einem ausdrücklichen „Ablehnen“ weiterlaufen, bleiben überall ein Fehler, auch in
36
+ der Schweiz (Art. 30 Abs. 2 lit. b DSG). Richtet sich eine Schweizer Website auch an Besucher aus
37
+ der EU, prüfen Sie sie zusätzlich mit `--region eu`.
38
+
39
+ ```bash
40
+ npx privalint https://ihre-seite.at --region at --lang de --max-pages 20
41
+ ```
42
+
43
+ ## Was geprüft wird
44
+
45
+ | Regel | Was sie findet |
46
+ | ----------------------- | ------------------------------------------------------------------------------------------------------- |
47
+ | `pre-consent-trackers` | Analyse-, Werbe- und Session-Replay-Tools, die vor der Einwilligung Anfragen senden oder Cookies setzen |
48
+ | `trackers-after-reject` | Tracker, die nach Klick auf „Ablehnen“ und Neuladen weiterlaufen |
49
+ | `reject-option` | Cookie-Banner mit „Zustimmen“, aber ohne „Ablehnen“ auf der ersten Ebene; Pur-Abo-Modelle als Hinweis |
50
+ | `google-fonts` | Schriften von `fonts.googleapis.com` / `fonts.gstatic.com` |
51
+ | `third-party-embeds` | YouTube, Vimeo, Google Maps, reCAPTCHA, Gravatar und Social-Media-Einbindungen vor der Einwilligung |
52
+ | `third-party-cdns` | Skripte und Styles von jsDelivr, unpkg, cdnjs und anderen öffentlichen CDNs (Hinweis) |
53
+ | `impressum-link` | Seiten ohne Link zum Impressum |
54
+ | `privacy-policy-link` | Seiten ohne Link zur Datenschutzerklärung |
55
+
56
+ Details, Hintergründe und Lösungen stehen auf Englisch in [`docs/rules`](docs/rules).
57
+
58
+ ## GitHub Action
59
+
60
+ ```yaml
61
+ - uses: jexcellence/privalint@v0
62
+ with:
63
+ url: https://ihre-seite.de
64
+ max-pages: 20
65
+ lang: de
66
+ region: de
67
+ ```
68
+
69
+ Der Bericht erscheint auf Deutsch in der Zusammenfassung des Workflow-Laufs. Alle Eingaben und
70
+ die Anbindung an GitHub Code Scanning beschreibt die [englische README](README.md#github-action).
71
+
72
+ ## Grenzen
73
+
74
+ - Manche Seiten zeigen ihr Cookie-Banner Headless-Browsern nicht. Dann meldet privalint „kein
75
+ Banner gefunden“. Lokal hilft `--headed --channel msedge` (oder `chrome`).
76
+ - Viele Consent-Tools entscheiden anhand der IP-Adresse, ob sie ein Banner zeigen. GitHub-Runner
77
+ haben US-Adressen und sehen deshalb unter Umständen etwas anderes als Besucher aus der EU.
78
+ - Serverseitiges Tracking ist im Browser nicht sichtbar.
79
+
80
+ ## Mitmachen
81
+
82
+ Fehlalarme, fehlende Tracker, Consent-Tools und neue Regeln sind sehr willkommen. Gerade
83
+ Anbieter aus dem deutschsprachigen Raum fehlen noch. Der Einstieg steht in
84
+ [CONTRIBUTING.md](CONTRIBUTING.md), passende Aufgaben tragen das Label
85
+ [`good first issue`](https://github.com/jexcellence/privalint/labels/good%20first%20issue).
86
+
87
+ ## Lizenz
88
+
89
+ [MIT](LICENSE)
package/README.md CHANGED
@@ -1,3 +1,223 @@
1
- # Temporary Holding Version
1
+ # privalint
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![CI](https://github.com/jexcellence/privalint/actions/workflows/ci.yml/badge.svg)](https://github.com/jexcellence/privalint/actions/workflows/ci.yml)
4
+ [![npm](https://img.shields.io/npm/v/privalint)](https://www.npmjs.com/package/privalint)
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
6
+
7
+ **Deutsch:** [README.de.md](README.de.md)
8
+
9
+ Lint your website for the GDPR mistakes that get people warning letters: trackers that fire before
10
+ consent or keep running after "reject", Google Fonts loaded from Google, embeds and a missing
11
+ Impressum.
12
+
13
+ privalint opens your site in a real Chromium browser with no cookies or storage, like a first
14
+ visit. It records what loads before anyone touches the consent banner, then clicks "reject" and
15
+ checks what still loads. Run it from your terminal or as a GitHub Action on every pull request.
16
+
17
+ ```bash
18
+ npx privalint https://your-site.de
19
+ ```
20
+
21
+ <p align="center">
22
+ <img src="docs/demo.svg" alt="privalint output: Google Analytics still runs after clicking reject, Google Fonts are loaded from Google, a YouTube embed loads before consent" width="820">
23
+ </p>
24
+
25
+ <sub>Output for the demo page in <a href="test/fixtures/demo/index.html"><code>test/fixtures/demo</code></a>, long lines
26
+ shortened. Regenerate it with <code>npm run demo</code>.</sub>
27
+
28
+ > [!IMPORTANT]
29
+ > privalint runs automated checks. A clean result means it found none of the problems it looks
30
+ > for. It does not make your site GDPR compliant and it is not legal advice.
31
+
32
+ ## What it checks
33
+
34
+ | Rule | Severity | What it finds |
35
+ | -------------------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------- |
36
+ | [`pre-consent-trackers`](docs/rules/pre-consent-trackers.md) | error | Analytics, ads and session-replay tools that send requests or set cookies before consent. A bare tag manager is a warning. |
37
+ | [`trackers-after-reject`](docs/rules/trackers-after-reject.md) | error | Trackers that still load after clicking "reject" on the consent banner and reloading. |
38
+ | [`reject-option`](docs/rules/reject-option.md) | warning | A banner with "accept" but no "reject" on its first layer. Pay-or-OK banners are noted as info. |
39
+ | [`google-fonts`](docs/rules/google-fonts.md) | error | Fonts fetched from `fonts.googleapis.com` / `fonts.gstatic.com`. Leftover preconnects are a warning. |
40
+ | [`third-party-embeds`](docs/rules/third-party-embeds.md) | warning | YouTube, Vimeo, Google Maps, reCAPTCHA, Gravatar and social embeds loading before consent. |
41
+ | [`third-party-cdns`](docs/rules/third-party-cdns.md) | info | Scripts and styles from jsDelivr, unpkg, cdnjs and other public CDNs. |
42
+ | [`impressum-link`](docs/rules/impressum-link.md) | warning | Pages without a link to an Impressum. A contact link alone is noted as info. |
43
+ | [`privacy-policy-link`](docs/rules/privacy-policy-link.md) | warning | Pages without a link to a privacy policy. |
44
+
45
+ More rules are on the [roadmap](#roadmap). Each one is a small file with tests, see
46
+ [CONTRIBUTING.md](CONTRIBUTING.md).
47
+
48
+ ## GitHub Action
49
+
50
+ ```yaml
51
+ name: Privacy
52
+
53
+ on:
54
+ pull_request:
55
+ schedule:
56
+ - cron: "0 6 * * 1"
57
+
58
+ jobs:
59
+ privalint:
60
+ runs-on: ubuntu-latest
61
+ permissions:
62
+ contents: read
63
+ security-events: write # only needed for the SARIF upload
64
+ steps:
65
+ - uses: jexcellence/privalint@v0
66
+ with:
67
+ url: https://your-site.de
68
+ max-pages: 20
69
+
70
+ - uses: github/codeql-action/upload-sarif@v4
71
+ if: always()
72
+ with:
73
+ sarif_file: privalint.sarif
74
+ category: privalint
75
+ ```
76
+
77
+ The report shows up in the job summary. With the SARIF upload, findings also appear under
78
+ **Security → Code scanning**.
79
+
80
+ Scanning a preview deployment instead of production catches problems before they ship. Pass the
81
+ preview URL from your hosting provider's deployment event or action output as `url`.
82
+
83
+ | Input | Default | |
84
+ | ------------- | ----------------- | ------------------------------------------- |
85
+ | `url` | | Page to scan. Required. |
86
+ | `max-pages` | `1` | How many pages of the same site to crawl. |
87
+ | `fail-on` | `error` | `error`, `warning` or `never`. |
88
+ | `disable` | | Comma-separated rule ids to skip. |
89
+ | `lang` | `en` | Language of the report: `en` or `de`. |
90
+ | `region` | `de` | Whose law to cite: `de`, `at`, `ch`, `eu`. |
91
+ | `sarif-file` | `privalint.sarif` | Where to write SARIF. Empty to skip. |
92
+ | `job-summary` | `true` | Add the Markdown report to the job summary. |
93
+
94
+ Outputs: `exit-code`, and `sarif-file` when a report was written.
95
+
96
+ The action uses the runner's Node.js (20.12 or newer) and leaves your job's Node version alone.
97
+ On self-hosted runners without it, add `actions/setup-node` before this step.
98
+
99
+ > [!NOTE]
100
+ > Code scanning expects each finding to point at a file in your repository, which a live page
101
+ > doesn't have. privalint maps every page to a stable path such as `privalint/your-site.de/kontakt`.
102
+ > Alerts show up with message, evidence and fix, but there is no source file to jump to. The
103
+ > [demo scan](.github/workflows/demo.yml) runs this setup every week.
104
+
105
+ ## CLI
106
+
107
+ ```
108
+ privalint <url> [options]
109
+
110
+ -p, --max-pages <n> Crawl up to n pages of the same site (default 1)
111
+ -f, --format <format> text | json | markdown | sarif (default text)
112
+ -o, --output <file> Write the report to a file and print a text summary.
113
+ Repeatable; .sarif, .json and .md pick their format,
114
+ other extensions use --format
115
+ --fail-on <level> error | warning | never (default error)
116
+ --disable <rules> Comma-separated rule ids to skip
117
+ --settle <ms> Wait this long after load for late tags (default 2000)
118
+ --timeout <ms> Navigation timeout per page (default 30000)
119
+ --locale <locale> Browser locale (default de-DE)
120
+ --lang <lang> Language of the report: en | de (default en)
121
+ --region <region> Whose law to cite: de | at | ch | eu (default de)
122
+ --skip-consent Don't click "reject" on the consent banner
123
+ --headed Open a visible browser window instead of running headless
124
+ --channel <name> Use an installed browser: chrome or msedge
125
+ --list-rules Print the available rules and exit
126
+ ```
127
+
128
+ Exit codes: `0` nothing at or above `--fail-on`, `1` findings, `2` bad arguments or a failed scan.
129
+
130
+ The first run needs a browser. If Playwright hasn't installed one yet:
131
+
132
+ ```bash
133
+ npx playwright install chromium
134
+ ```
135
+
136
+ ## Countries and languages
137
+
138
+ The same finding can be a clear violation in one country and a hint in another. `--region` picks
139
+ whose law privalint cites and how strictly it judges:
140
+
141
+ | Region | Cookies and tracking | Imprint | Changes |
142
+ | -------------- | ------------------------------------ | ------------------------------------- | -------------------------------------------------------------------------------- |
143
+ | `de` (default) | Opt-in, § 25 TDDDG | § 5 DDG | none |
144
+ | `at` | Opt-in, § 165 Abs. 3 TKG 2021 | § 5 ECG, § 25 MedienG | none |
145
+ | `ch` | Opt-out, Art. 45c FMG | Art. 3 Abs. 1 lit. s UWG (e-commerce) | `pre-consent-trackers`, `third-party-embeds` and `impressum-link` drop one level |
146
+ | `eu` | Opt-in, Art. 5(3) ePrivacy Directive | Art. 5 Directive 2000/31/EC | none |
147
+
148
+ Every finding lists its legal basis for the chosen country. Trackers that run after an explicit
149
+ "reject" stay an error everywhere, Switzerland included (Art. 30 Abs. 2 lit. b DSG). If a Swiss
150
+ site also targets visitors in the EU, scan it with `--region eu` as well.
151
+
152
+ `--lang de` writes findings, fixes and the report in German:
153
+
154
+ ```bash
155
+ npx privalint https://ihre-seite.ch --region ch --lang de
156
+ ```
157
+
158
+ ## Use it from code
159
+
160
+ ```ts
161
+ import { scan } from 'privalint';
162
+
163
+ const result = await scan('https://your-site.de', { maxPages: 10, region: 'at', lang: 'de' });
164
+ for (const finding of result.findings) {
165
+ console.log(finding.severity, finding.ruleId, finding.pageUrl);
166
+ }
167
+ ```
168
+
169
+ ## How it works
170
+
171
+ 1. Every page is opened in a **fresh browser context**: no cookies and no storage, the same as a
172
+ first visit.
173
+ 2. privalint waits for the network to go idle plus a settle delay (`--settle`), so tags that load
174
+ late are caught too.
175
+ 3. It records every request, the final cookie jar, all links and `<link>` resource hints.
176
+ 4. On the start page it looks for the consent banner, either by the selectors of common consent
177
+ managers (OneTrust, Cookiebot, Usercentrics, Borlabs, Complianz and others) or by button labels
178
+ in six languages. It clicks "reject", reloads and records what still loads.
179
+ 5. Each rule looks at those snapshots and reports findings with evidence and a suggested fix.
180
+ 6. With `--max-pages`, links to the same site are followed breadth-first. Query strings are
181
+ dropped, pages are deduplicated after redirects, and pages that fail to load (downloads,
182
+ timeouts) are listed but don't stop the crawl.
183
+
184
+ ### Limitations
185
+
186
+ - Some sites don't show their consent banner to headless browsers. privalint then reports
187
+ "no banner found" and skips the reject check. Run it locally with `--headed` to scan in a
188
+ visible window; with `--channel msedge` or `--channel chrome` it uses your installed browser
189
+ instead of the bundled one. privalint never disguises itself as a different browser.
190
+ - Some sites block automated browsers completely and serve a block page instead. privalint scans
191
+ what it gets, so check the scanned URL and the page count in the report.
192
+ - Only "reject" is tested. Whether "accept" works, and whether rejecting is as easy as accepting
193
+ beyond the presence of a button, is not judged.
194
+ - Tracker detection uses a hand-maintained list ([`src/data/trackers.ts`](src/data/trackers.ts)).
195
+ A vendor missing from the list goes unnoticed. Additions are very welcome.
196
+ - First-party detection compares hostnames and ignores the public suffix list, so sibling
197
+ subdomains on shared hosts like `*.github.io` count as first party.
198
+ - Server-side tracking can't be seen from the browser.
199
+ - Many consent managers decide by IP address whether to show a banner. GitHub-hosted runners
200
+ have US addresses, so some sites load trackers there that EU visitors never get, and the
201
+ result differs from what an EU visitor sees. Use a runner or proxy in the EU for results you
202
+ want to rely on.
203
+ - Chromium is launched with Playwright's defaults, which turn off the Chromium sandbox. Don't
204
+ scan sites you don't trust on a machine that holds secrets.
205
+
206
+ ## Roadmap
207
+
208
+ - PR comments from the Action
209
+ - Config file (`privalint.config.ts`) for allowlists and per-rule severity
210
+ - Check the settings layer of banners and the "accept" path
211
+ - More trackers, embeds and consent managers (see the
212
+ [`good first issue`](https://github.com/jexcellence/privalint/labels/good%20first%20issue) label)
213
+ - Larger tracker list built from the Apache-licensed
214
+ [Open Cookie Database](https://github.com/jkwakman/Open-Cookie-Database)
215
+
216
+ ## Contributing
217
+
218
+ Bug reports, false positives, new trackers and new rules are all welcome. Start with
219
+ [CONTRIBUTING.md](CONTRIBUTING.md).
220
+
221
+ ## License
222
+
223
+ [MIT](LICENSE)
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,183 @@
1
+ #!/usr/bin/env node
2
+ import { writeFile } from 'node:fs/promises';
3
+ import { extname } from 'node:path';
4
+ import { parseArgs } from 'node:util';
5
+ import { LANGS } from './i18n.js';
6
+ import { REGIONS } from './legal.js';
7
+ import { VERSION } from './meta.js';
8
+ import { FORMATS, format, formatText } from './reporters/index.js';
9
+ import { defaultRules } from './rules/index.js';
10
+ import { scan } from './scanner.js';
11
+ const FAIL_LEVELS = ['error', 'warning', 'never'];
12
+ const CHANNELS = ['chrome', 'msedge'];
13
+ const EXIT_OK = 0;
14
+ const EXIT_FINDINGS = 1;
15
+ const EXIT_ERROR = 2;
16
+ const HELP = `privalint ${VERSION}
17
+ Find GDPR problems on a live website: trackers before consent or after
18
+ "reject", Google Fonts, missing Impressum. Runs the site in a real browser.
19
+
20
+ Usage
21
+ privalint <url> [options]
22
+
23
+ Options
24
+ -p, --max-pages <n> Crawl up to n pages of the same site (default 1)
25
+ -f, --format <format> ${FORMATS.join(' | ')} (default text)
26
+ -o, --output <file> Write the report to a file and print a text summary.
27
+ Repeatable; .sarif, .json and .md pick their format,
28
+ other extensions use --format
29
+ --fail-on <level> error | warning | never (default error)
30
+ --disable <rules> Comma-separated rule ids to skip
31
+ --settle <ms> Wait this long after load for late tags (default 2000)
32
+ --timeout <ms> Navigation timeout per page (default 30000)
33
+ --locale <locale> Browser locale (default de-DE)
34
+ --lang <lang> Language of the report: en | de (default en)
35
+ --region <region> Whose law to cite: de | at | ch | eu (default de)
36
+ --skip-consent Don't click "reject" on the consent banner
37
+ --headed Open a visible browser window instead of running headless
38
+ --channel <name> Use an installed browser: chrome or msedge
39
+ --list-rules Print the available rules and exit
40
+ -h, --help Show this help
41
+ -v, --version Show the version
42
+
43
+ Exit codes
44
+ 0 no findings at or above --fail-on
45
+ 1 findings at or above --fail-on
46
+ 2 invalid arguments or the scan failed
47
+ `;
48
+ const FORMAT_BY_EXTENSION = {
49
+ '.sarif': 'sarif',
50
+ '.json': 'json',
51
+ '.md': 'markdown',
52
+ };
53
+ async function main(argv) {
54
+ const { values, positionals } = parseArgs({
55
+ args: argv,
56
+ allowPositionals: true,
57
+ options: {
58
+ 'max-pages': { type: 'string', short: 'p' },
59
+ format: { type: 'string', short: 'f', default: 'text' },
60
+ output: { type: 'string', short: 'o', multiple: true },
61
+ 'fail-on': { type: 'string', default: 'error' },
62
+ disable: { type: 'string' },
63
+ settle: { type: 'string' },
64
+ timeout: { type: 'string' },
65
+ locale: { type: 'string' },
66
+ lang: { type: 'string', default: 'en' },
67
+ region: { type: 'string', default: 'de' },
68
+ 'skip-consent': { type: 'boolean' },
69
+ headed: { type: 'boolean' },
70
+ channel: { type: 'string' },
71
+ 'list-rules': { type: 'boolean' },
72
+ help: { type: 'boolean', short: 'h' },
73
+ version: { type: 'boolean', short: 'v' },
74
+ },
75
+ });
76
+ if (values.help) {
77
+ process.stdout.write(HELP);
78
+ return EXIT_OK;
79
+ }
80
+ if (values.version) {
81
+ process.stdout.write(`${VERSION}\n`);
82
+ return EXIT_OK;
83
+ }
84
+ if (values['list-rules']) {
85
+ for (const rule of defaultRules) {
86
+ process.stdout.write(`${rule.id.padEnd(22)} ${rule.title}\n`);
87
+ }
88
+ return EXIT_OK;
89
+ }
90
+ const target = parseTarget(positionals);
91
+ const reportFormat = oneOf(values.format, FORMATS, '--format');
92
+ const failOn = oneOf(values['fail-on'], FAIL_LEVELS, '--fail-on');
93
+ const rules = selectRules(values.disable);
94
+ const result = await scan(target.href, {
95
+ rules,
96
+ maxPages: wholeNumber(values['max-pages'], '--max-pages', 1),
97
+ settleMs: wholeNumber(values.settle, '--settle', 0),
98
+ timeoutMs: wholeNumber(values.timeout, '--timeout', 1),
99
+ locale: values.locale,
100
+ lang: oneOf(values.lang, LANGS, '--lang'),
101
+ region: oneOf(values.region, REGIONS, '--region'),
102
+ consentCheck: values['skip-consent'] !== true,
103
+ headed: values.headed === true,
104
+ channel: values.channel === undefined ? undefined : oneOf(values.channel, CHANNELS, '--channel'),
105
+ onPage: (page) => process.stderr.write(page.error === undefined
106
+ ? `scanned ${page.finalUrl}\n`
107
+ : `skipped ${page.url}: ${page.error}\n`),
108
+ });
109
+ const outputs = values.output ?? [];
110
+ for (const file of outputs) {
111
+ const kind = FORMAT_BY_EXTENSION[extname(file).toLowerCase()] ?? reportFormat;
112
+ await writeFile(file, format(result, kind));
113
+ }
114
+ if (outputs.length > 0) {
115
+ process.stdout.write(`${formatText(result, process.stdout.isTTY)}\n`);
116
+ process.stdout.write(`\nReport written to ${outputs.join(', ')}\n`);
117
+ }
118
+ else {
119
+ process.stdout.write(format(result, reportFormat, { color: process.stdout.isTTY }));
120
+ if (reportFormat === 'text') {
121
+ process.stdout.write('\n');
122
+ }
123
+ }
124
+ return shouldFail(result.findings, failOn) ? EXIT_FINDINGS : EXIT_OK;
125
+ }
126
+ function parseTarget(positionals) {
127
+ const [raw, ...rest] = positionals;
128
+ if (raw === undefined || rest.length > 0) {
129
+ throw new Error('Expected exactly one URL. Run privalint --help for usage.');
130
+ }
131
+ const withScheme = /^https?:\/\//i.test(raw) ? raw : `https://${raw}`;
132
+ try {
133
+ return new URL(withScheme);
134
+ }
135
+ catch {
136
+ throw new Error(`Not a valid URL: ${raw}`);
137
+ }
138
+ }
139
+ function oneOf(value, allowed, flag) {
140
+ if (value !== undefined && allowed.includes(value)) {
141
+ return value;
142
+ }
143
+ throw new Error(`${flag} must be one of: ${allowed.join(', ')}`);
144
+ }
145
+ function wholeNumber(value, flag, min) {
146
+ if (value === undefined) {
147
+ return undefined;
148
+ }
149
+ const parsed = Number(value);
150
+ if (!Number.isInteger(parsed) || parsed < min) {
151
+ throw new Error(`${flag} must be a whole number of at least ${min}`);
152
+ }
153
+ return parsed;
154
+ }
155
+ function selectRules(disable) {
156
+ const disabled = new Set((disable ?? '')
157
+ .split(',')
158
+ .map((id) => id.trim())
159
+ .filter(Boolean));
160
+ const unknown = [...disabled].filter((id) => !defaultRules.some((rule) => rule.id === id));
161
+ if (unknown.length > 0) {
162
+ throw new Error(`Unknown rule: ${unknown.join(', ')}. See privalint --list-rules.`);
163
+ }
164
+ return defaultRules.filter((rule) => !disabled.has(rule.id));
165
+ }
166
+ function shouldFail(findings, failOn) {
167
+ switch (failOn) {
168
+ case 'never':
169
+ return false;
170
+ case 'warning':
171
+ return findings.some((f) => f.severity === 'error' || f.severity === 'warning');
172
+ default:
173
+ return findings.some((f) => f.severity === 'error');
174
+ }
175
+ }
176
+ main(process.argv.slice(2)).then((code) => {
177
+ process.exitCode = code;
178
+ }, (error) => {
179
+ const message = error instanceof Error ? error.message : String(error);
180
+ process.stderr.write(`privalint: ${message}\n`);
181
+ process.exitCode = EXIT_ERROR;
182
+ });
183
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { BrowserContext, Page } from 'playwright';
2
+ import type { ConsentObservation, RecordedRequest } from './types.js';
3
+ export interface ConsentCheckOptions {
4
+ settleMs: number;
5
+ timeoutMs: number;
6
+ }
7
+ /**
8
+ * Looks for a consent banner, clicks its reject button if there is one,
9
+ * reloads the page and records what still gets loaded. The page must already
10
+ * be loaded; `requests` is the live array the page's request listener writes to.
11
+ */
12
+ export declare function observeConsent(page: Page, context: BrowserContext, requests: RecordedRequest[], options: ConsentCheckOptions): Promise<ConsentObservation>;