privalint 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +194 -2
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +175 -0
- package/dist/cli.js.map +1 -0
- package/dist/consent.d.ts +12 -0
- package/dist/consent.js +185 -0
- package/dist/consent.js.map +1 -0
- package/dist/data/third-parties.d.ts +13 -0
- package/dist/data/third-parties.js +42 -0
- package/dist/data/third-parties.js.map +1 -0
- package/dist/data/trackers.d.ts +21 -0
- package/dist/data/trackers.js +135 -0
- package/dist/data/trackers.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/meta.d.ts +3 -0
- package/dist/meta.js +6 -0
- package/dist/meta.js.map +1 -0
- package/dist/reporters/index.d.ts +11 -0
- package/dist/reporters/index.js +19 -0
- package/dist/reporters/index.js.map +1 -0
- package/dist/reporters/json.d.ts +2 -0
- package/dist/reporters/json.js +2 -0
- package/dist/reporters/json.js.map +1 -0
- package/dist/reporters/markdown.d.ts +9 -0
- package/dist/reporters/markdown.js +47 -0
- package/dist/reporters/markdown.js.map +1 -0
- package/dist/reporters/sarif.d.ts +10 -0
- package/dist/reporters/sarif.js +78 -0
- package/dist/reporters/sarif.js.map +1 -0
- package/dist/reporters/shared.d.ts +5 -0
- package/dist/reporters/shared.js +34 -0
- package/dist/reporters/shared.js.map +1 -0
- package/dist/reporters/text.d.ts +2 -0
- package/dist/reporters/text.js +52 -0
- package/dist/reporters/text.js.map +1 -0
- package/dist/rules/google-fonts.d.ts +2 -0
- package/dist/rules/google-fonts.js +51 -0
- package/dist/rules/google-fonts.js.map +1 -0
- package/dist/rules/impressum-link.d.ts +2 -0
- package/dist/rules/impressum-link.js +46 -0
- package/dist/rules/impressum-link.js.map +1 -0
- package/dist/rules/index.d.ts +11 -0
- package/dist/rules/index.js +20 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/links.d.ts +6 -0
- package/dist/rules/links.js +14 -0
- package/dist/rules/links.js.map +1 -0
- package/dist/rules/pre-consent-trackers.d.ts +2 -0
- package/dist/rules/pre-consent-trackers.js +32 -0
- package/dist/rules/pre-consent-trackers.js.map +1 -0
- package/dist/rules/privacy-policy-link.d.ts +2 -0
- package/dist/rules/privacy-policy-link.js +26 -0
- package/dist/rules/privacy-policy-link.js.map +1 -0
- package/dist/rules/reject-option.d.ts +2 -0
- package/dist/rules/reject-option.js +46 -0
- package/dist/rules/reject-option.js.map +1 -0
- package/dist/rules/third-party-cdns.d.ts +2 -0
- package/dist/rules/third-party-cdns.js +23 -0
- package/dist/rules/third-party-cdns.js.map +1 -0
- package/dist/rules/third-party-embeds.d.ts +2 -0
- package/dist/rules/third-party-embeds.js +23 -0
- package/dist/rules/third-party-embeds.js.map +1 -0
- package/dist/rules/third-party-hits.d.ts +4 -0
- package/dist/rules/third-party-hits.js +13 -0
- package/dist/rules/third-party-hits.js.map +1 -0
- package/dist/rules/tracker-hits.d.ts +9 -0
- package/dist/rules/tracker-hits.js +29 -0
- package/dist/rules/tracker-hits.js.map +1 -0
- package/dist/rules/trackers-after-reject.d.ts +2 -0
- package/dist/rules/trackers-after-reject.js +27 -0
- package/dist/rules/trackers-after-reject.js.map +1 -0
- package/dist/scanner.d.ts +33 -0
- package/dist/scanner.js +167 -0
- package/dist/scanner.js.map +1 -0
- package/dist/types.d.ts +97 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/url.d.ts +26 -0
- package/dist/url.js +71 -0
- package/dist/url.js.map +1 -0
- package/package.json +60 -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.md
CHANGED
|
@@ -1,3 +1,195 @@
|
|
|
1
|
-
#
|
|
1
|
+
# privalint
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://github.com/jexcellence/privalint/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/privalint)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
7
|
+
Lint your website for the GDPR mistakes that get people warning letters: trackers that fire before
|
|
8
|
+
consent or keep running after "reject", Google Fonts loaded from Google, embeds and a missing
|
|
9
|
+
Impressum.
|
|
10
|
+
|
|
11
|
+
privalint opens your site in a real Chromium browser with no cookies or storage, like a first
|
|
12
|
+
visit. It records what loads before anyone touches the consent banner, then clicks "reject" and
|
|
13
|
+
checks what still loads. Run it from your terminal or as a GitHub Action on every pull request.
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx privalint https://your-site.de
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
<p align="center">
|
|
20
|
+
<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">
|
|
21
|
+
</p>
|
|
22
|
+
|
|
23
|
+
<sub>Output for the demo page in <a href="test/fixtures/demo/index.html"><code>test/fixtures/demo</code></a>, long lines
|
|
24
|
+
shortened. Regenerate it with <code>npm run demo</code>.</sub>
|
|
25
|
+
|
|
26
|
+
> [!IMPORTANT]
|
|
27
|
+
> privalint runs automated checks. A clean result means it found none of the problems it looks
|
|
28
|
+
> for. It does not make your site GDPR compliant and it is not legal advice.
|
|
29
|
+
|
|
30
|
+
## What it checks
|
|
31
|
+
|
|
32
|
+
| Rule | Severity | What it finds |
|
|
33
|
+
| -------------------------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------- |
|
|
34
|
+
| [`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. |
|
|
35
|
+
| [`trackers-after-reject`](docs/rules/trackers-after-reject.md) | error | Trackers that still load after clicking "reject" on the consent banner and reloading. |
|
|
36
|
+
| [`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. |
|
|
37
|
+
| [`google-fonts`](docs/rules/google-fonts.md) | error | Fonts fetched from `fonts.googleapis.com` / `fonts.gstatic.com`. Leftover preconnects are a warning. |
|
|
38
|
+
| [`third-party-embeds`](docs/rules/third-party-embeds.md) | warning | YouTube, Vimeo, Google Maps, reCAPTCHA, Gravatar and social embeds loading before consent. |
|
|
39
|
+
| [`third-party-cdns`](docs/rules/third-party-cdns.md) | info | Scripts and styles from jsDelivr, unpkg, cdnjs and other public CDNs. |
|
|
40
|
+
| [`impressum-link`](docs/rules/impressum-link.md) | warning | Pages without a link to an Impressum. A contact link alone is noted as info. |
|
|
41
|
+
| [`privacy-policy-link`](docs/rules/privacy-policy-link.md) | warning | Pages without a link to a privacy policy. |
|
|
42
|
+
|
|
43
|
+
More rules are on the [roadmap](#roadmap). Each one is a small file with tests, see
|
|
44
|
+
[CONTRIBUTING.md](CONTRIBUTING.md).
|
|
45
|
+
|
|
46
|
+
## GitHub Action
|
|
47
|
+
|
|
48
|
+
```yaml
|
|
49
|
+
name: Privacy
|
|
50
|
+
|
|
51
|
+
on:
|
|
52
|
+
pull_request:
|
|
53
|
+
schedule:
|
|
54
|
+
- cron: "0 6 * * 1"
|
|
55
|
+
|
|
56
|
+
jobs:
|
|
57
|
+
privalint:
|
|
58
|
+
runs-on: ubuntu-latest
|
|
59
|
+
permissions:
|
|
60
|
+
contents: read
|
|
61
|
+
security-events: write # only needed for the SARIF upload
|
|
62
|
+
steps:
|
|
63
|
+
- uses: jexcellence/privalint@v0
|
|
64
|
+
with:
|
|
65
|
+
url: https://your-site.de
|
|
66
|
+
max-pages: 20
|
|
67
|
+
|
|
68
|
+
- uses: github/codeql-action/upload-sarif@v4
|
|
69
|
+
if: always()
|
|
70
|
+
with:
|
|
71
|
+
sarif_file: privalint.sarif
|
|
72
|
+
category: privalint
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The report shows up in the job summary. With the SARIF upload, findings also appear under
|
|
76
|
+
**Security → Code scanning**.
|
|
77
|
+
|
|
78
|
+
Scanning a preview deployment instead of production catches problems before they ship. Pass the
|
|
79
|
+
preview URL from your hosting provider's deployment event or action output as `url`.
|
|
80
|
+
|
|
81
|
+
| Input | Default | |
|
|
82
|
+
| ------------- | ----------------- | ------------------------------------------- |
|
|
83
|
+
| `url` | | Page to scan. Required. |
|
|
84
|
+
| `max-pages` | `1` | How many pages of the same site to crawl. |
|
|
85
|
+
| `fail-on` | `error` | `error`, `warning` or `never`. |
|
|
86
|
+
| `disable` | | Comma-separated rule ids to skip. |
|
|
87
|
+
| `sarif-file` | `privalint.sarif` | Where to write SARIF. Empty to skip. |
|
|
88
|
+
| `job-summary` | `true` | Add the Markdown report to the job summary. |
|
|
89
|
+
|
|
90
|
+
Outputs: `exit-code`, and `sarif-file` when a report was written.
|
|
91
|
+
|
|
92
|
+
The action uses the runner's Node.js (20.12 or newer) and leaves your job's Node version alone.
|
|
93
|
+
On self-hosted runners without it, add `actions/setup-node` before this step.
|
|
94
|
+
|
|
95
|
+
> [!NOTE]
|
|
96
|
+
> Code scanning expects each finding to point at a file in your repository, which a live page
|
|
97
|
+
> doesn't have. privalint maps every page to a stable path such as `privalint/your-site.de/kontakt`.
|
|
98
|
+
> Alerts show up with message, evidence and fix, but there is no source file to jump to. The
|
|
99
|
+
> [demo scan](.github/workflows/demo.yml) runs this setup every week.
|
|
100
|
+
|
|
101
|
+
## CLI
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
privalint <url> [options]
|
|
105
|
+
|
|
106
|
+
-p, --max-pages <n> Crawl up to n pages of the same site (default 1)
|
|
107
|
+
-f, --format <format> text | json | markdown | sarif (default text)
|
|
108
|
+
-o, --output <file> Write the report to a file and print a text summary.
|
|
109
|
+
Repeatable; .sarif, .json and .md pick their format,
|
|
110
|
+
other extensions use --format
|
|
111
|
+
--fail-on <level> error | warning | never (default error)
|
|
112
|
+
--disable <rules> Comma-separated rule ids to skip
|
|
113
|
+
--settle <ms> Wait this long after load for late tags (default 2000)
|
|
114
|
+
--timeout <ms> Navigation timeout per page (default 30000)
|
|
115
|
+
--locale <locale> Browser locale (default de-DE)
|
|
116
|
+
--skip-consent Don't click "reject" on the consent banner
|
|
117
|
+
--headed Open a visible browser window instead of running headless
|
|
118
|
+
--channel <name> Use an installed browser: chrome or msedge
|
|
119
|
+
--list-rules Print the available rules and exit
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Exit codes: `0` nothing at or above `--fail-on`, `1` findings, `2` bad arguments or a failed scan.
|
|
123
|
+
|
|
124
|
+
The first run needs a browser. If Playwright hasn't installed one yet:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
npx playwright install chromium
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Use it from code
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
import { scan } from 'privalint';
|
|
134
|
+
|
|
135
|
+
const result = await scan('https://your-site.de', { maxPages: 10 });
|
|
136
|
+
for (const finding of result.findings) {
|
|
137
|
+
console.log(finding.severity, finding.ruleId, finding.pageUrl);
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## How it works
|
|
142
|
+
|
|
143
|
+
1. Every page is opened in a **fresh browser context**: no cookies and no storage, the same as a
|
|
144
|
+
first visit.
|
|
145
|
+
2. privalint waits for the network to go idle plus a settle delay (`--settle`), so tags that load
|
|
146
|
+
late are caught too.
|
|
147
|
+
3. It records every request, the final cookie jar, all links and `<link>` resource hints.
|
|
148
|
+
4. On the start page it looks for the consent banner, either by the selectors of common consent
|
|
149
|
+
managers (OneTrust, Cookiebot, Usercentrics, Borlabs, Complianz and others) or by button labels
|
|
150
|
+
in six languages. It clicks "reject", reloads and records what still loads.
|
|
151
|
+
5. Each rule looks at those snapshots and reports findings with evidence and a suggested fix.
|
|
152
|
+
6. With `--max-pages`, links to the same site are followed breadth-first. Query strings are
|
|
153
|
+
dropped, pages are deduplicated after redirects, and pages that fail to load (downloads,
|
|
154
|
+
timeouts) are listed but don't stop the crawl.
|
|
155
|
+
|
|
156
|
+
### Limitations
|
|
157
|
+
|
|
158
|
+
- Some sites don't show their consent banner to headless browsers. privalint then reports
|
|
159
|
+
"no banner found" and skips the reject check. Run it locally with `--headed` to scan in a
|
|
160
|
+
visible window; with `--channel msedge` or `--channel chrome` it uses your installed browser
|
|
161
|
+
instead of the bundled one. privalint never disguises itself as a different browser.
|
|
162
|
+
- Some sites block automated browsers completely and serve a block page instead. privalint scans
|
|
163
|
+
what it gets, so check the scanned URL and the page count in the report.
|
|
164
|
+
- Only "reject" is tested. Whether "accept" works, and whether rejecting is as easy as accepting
|
|
165
|
+
beyond the presence of a button, is not judged.
|
|
166
|
+
- Tracker detection uses a hand-maintained list ([`src/data/trackers.ts`](src/data/trackers.ts)).
|
|
167
|
+
A vendor missing from the list goes unnoticed. Additions are very welcome.
|
|
168
|
+
- First-party detection compares hostnames and ignores the public suffix list, so sibling
|
|
169
|
+
subdomains on shared hosts like `*.github.io` count as first party.
|
|
170
|
+
- Server-side tracking can't be seen from the browser.
|
|
171
|
+
- Many consent managers decide by IP address whether to show a banner. GitHub-hosted runners
|
|
172
|
+
have US addresses, so some sites load trackers there that EU visitors never get, and the
|
|
173
|
+
result differs from what an EU visitor sees. Use a runner or proxy in the EU for results you
|
|
174
|
+
want to rely on.
|
|
175
|
+
- Chromium is launched with Playwright's defaults, which turn off the Chromium sandbox. Don't
|
|
176
|
+
scan sites you don't trust on a machine that holds secrets.
|
|
177
|
+
|
|
178
|
+
## Roadmap
|
|
179
|
+
|
|
180
|
+
- PR comments from the Action
|
|
181
|
+
- Config file (`privalint.config.ts`) for allowlists and per-rule severity
|
|
182
|
+
- Check the settings layer of banners and the "accept" path
|
|
183
|
+
- More trackers, embeds and consent managers (see the
|
|
184
|
+
[`good first issue`](https://github.com/jexcellence/privalint/labels/good%20first%20issue) label)
|
|
185
|
+
- Larger tracker list built from the Apache-licensed
|
|
186
|
+
[Open Cookie Database](https://github.com/jkwakman/Open-Cookie-Database)
|
|
187
|
+
|
|
188
|
+
## Contributing
|
|
189
|
+
|
|
190
|
+
Bug reports, false positives, new trackers and new rules are all welcome. Start with
|
|
191
|
+
[CONTRIBUTING.md](CONTRIBUTING.md).
|
|
192
|
+
|
|
193
|
+
## License
|
|
194
|
+
|
|
195
|
+
[MIT](LICENSE)
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
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 { VERSION } from './meta.js';
|
|
6
|
+
import { FORMATS, format, formatText } from './reporters/index.js';
|
|
7
|
+
import { defaultRules } from './rules/index.js';
|
|
8
|
+
import { scan } from './scanner.js';
|
|
9
|
+
const FAIL_LEVELS = ['error', 'warning', 'never'];
|
|
10
|
+
const CHANNELS = ['chrome', 'msedge'];
|
|
11
|
+
const EXIT_OK = 0;
|
|
12
|
+
const EXIT_FINDINGS = 1;
|
|
13
|
+
const EXIT_ERROR = 2;
|
|
14
|
+
const HELP = `privalint ${VERSION}
|
|
15
|
+
Find GDPR problems on a live website: trackers before consent or after
|
|
16
|
+
"reject", Google Fonts, missing Impressum. Runs the site in a real browser.
|
|
17
|
+
|
|
18
|
+
Usage
|
|
19
|
+
privalint <url> [options]
|
|
20
|
+
|
|
21
|
+
Options
|
|
22
|
+
-p, --max-pages <n> Crawl up to n pages of the same site (default 1)
|
|
23
|
+
-f, --format <format> ${FORMATS.join(' | ')} (default text)
|
|
24
|
+
-o, --output <file> Write the report to a file and print a text summary.
|
|
25
|
+
Repeatable; .sarif, .json and .md pick their format,
|
|
26
|
+
other extensions use --format
|
|
27
|
+
--fail-on <level> error | warning | never (default error)
|
|
28
|
+
--disable <rules> Comma-separated rule ids to skip
|
|
29
|
+
--settle <ms> Wait this long after load for late tags (default 2000)
|
|
30
|
+
--timeout <ms> Navigation timeout per page (default 30000)
|
|
31
|
+
--locale <locale> Browser locale (default de-DE)
|
|
32
|
+
--skip-consent Don't click "reject" on the consent banner
|
|
33
|
+
--headed Open a visible browser window instead of running headless
|
|
34
|
+
--channel <name> Use an installed browser: chrome or msedge
|
|
35
|
+
--list-rules Print the available rules and exit
|
|
36
|
+
-h, --help Show this help
|
|
37
|
+
-v, --version Show the version
|
|
38
|
+
|
|
39
|
+
Exit codes
|
|
40
|
+
0 no findings at or above --fail-on
|
|
41
|
+
1 findings at or above --fail-on
|
|
42
|
+
2 invalid arguments or the scan failed
|
|
43
|
+
`;
|
|
44
|
+
const FORMAT_BY_EXTENSION = {
|
|
45
|
+
'.sarif': 'sarif',
|
|
46
|
+
'.json': 'json',
|
|
47
|
+
'.md': 'markdown',
|
|
48
|
+
};
|
|
49
|
+
async function main(argv) {
|
|
50
|
+
const { values, positionals } = parseArgs({
|
|
51
|
+
args: argv,
|
|
52
|
+
allowPositionals: true,
|
|
53
|
+
options: {
|
|
54
|
+
'max-pages': { type: 'string', short: 'p' },
|
|
55
|
+
format: { type: 'string', short: 'f', default: 'text' },
|
|
56
|
+
output: { type: 'string', short: 'o', multiple: true },
|
|
57
|
+
'fail-on': { type: 'string', default: 'error' },
|
|
58
|
+
disable: { type: 'string' },
|
|
59
|
+
settle: { type: 'string' },
|
|
60
|
+
timeout: { type: 'string' },
|
|
61
|
+
locale: { type: 'string' },
|
|
62
|
+
'skip-consent': { type: 'boolean' },
|
|
63
|
+
headed: { type: 'boolean' },
|
|
64
|
+
channel: { type: 'string' },
|
|
65
|
+
'list-rules': { type: 'boolean' },
|
|
66
|
+
help: { type: 'boolean', short: 'h' },
|
|
67
|
+
version: { type: 'boolean', short: 'v' },
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
if (values.help) {
|
|
71
|
+
process.stdout.write(HELP);
|
|
72
|
+
return EXIT_OK;
|
|
73
|
+
}
|
|
74
|
+
if (values.version) {
|
|
75
|
+
process.stdout.write(`${VERSION}\n`);
|
|
76
|
+
return EXIT_OK;
|
|
77
|
+
}
|
|
78
|
+
if (values['list-rules']) {
|
|
79
|
+
for (const rule of defaultRules) {
|
|
80
|
+
process.stdout.write(`${rule.id.padEnd(22)} ${rule.title}\n`);
|
|
81
|
+
}
|
|
82
|
+
return EXIT_OK;
|
|
83
|
+
}
|
|
84
|
+
const target = parseTarget(positionals);
|
|
85
|
+
const reportFormat = oneOf(values.format, FORMATS, '--format');
|
|
86
|
+
const failOn = oneOf(values['fail-on'], FAIL_LEVELS, '--fail-on');
|
|
87
|
+
const rules = selectRules(values.disable);
|
|
88
|
+
const result = await scan(target.href, {
|
|
89
|
+
rules,
|
|
90
|
+
maxPages: wholeNumber(values['max-pages'], '--max-pages', 1),
|
|
91
|
+
settleMs: wholeNumber(values.settle, '--settle', 0),
|
|
92
|
+
timeoutMs: wholeNumber(values.timeout, '--timeout', 1),
|
|
93
|
+
locale: values.locale,
|
|
94
|
+
consentCheck: values['skip-consent'] !== true,
|
|
95
|
+
headed: values.headed === true,
|
|
96
|
+
channel: values.channel === undefined ? undefined : oneOf(values.channel, CHANNELS, '--channel'),
|
|
97
|
+
onPage: (page) => process.stderr.write(page.error === undefined
|
|
98
|
+
? `scanned ${page.finalUrl}\n`
|
|
99
|
+
: `skipped ${page.url}: ${page.error}\n`),
|
|
100
|
+
});
|
|
101
|
+
const outputs = values.output ?? [];
|
|
102
|
+
for (const file of outputs) {
|
|
103
|
+
const kind = FORMAT_BY_EXTENSION[extname(file).toLowerCase()] ?? reportFormat;
|
|
104
|
+
await writeFile(file, format(result, kind));
|
|
105
|
+
}
|
|
106
|
+
if (outputs.length > 0) {
|
|
107
|
+
process.stdout.write(`${formatText(result, process.stdout.isTTY)}\n`);
|
|
108
|
+
process.stdout.write(`\nReport written to ${outputs.join(', ')}\n`);
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
process.stdout.write(format(result, reportFormat, { color: process.stdout.isTTY }));
|
|
112
|
+
if (reportFormat === 'text') {
|
|
113
|
+
process.stdout.write('\n');
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return shouldFail(result.findings, failOn) ? EXIT_FINDINGS : EXIT_OK;
|
|
117
|
+
}
|
|
118
|
+
function parseTarget(positionals) {
|
|
119
|
+
const [raw, ...rest] = positionals;
|
|
120
|
+
if (raw === undefined || rest.length > 0) {
|
|
121
|
+
throw new Error('Expected exactly one URL. Run privalint --help for usage.');
|
|
122
|
+
}
|
|
123
|
+
const withScheme = /^https?:\/\//i.test(raw) ? raw : `https://${raw}`;
|
|
124
|
+
try {
|
|
125
|
+
return new URL(withScheme);
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
throw new Error(`Not a valid URL: ${raw}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function oneOf(value, allowed, flag) {
|
|
132
|
+
if (value !== undefined && allowed.includes(value)) {
|
|
133
|
+
return value;
|
|
134
|
+
}
|
|
135
|
+
throw new Error(`${flag} must be one of: ${allowed.join(', ')}`);
|
|
136
|
+
}
|
|
137
|
+
function wholeNumber(value, flag, min) {
|
|
138
|
+
if (value === undefined) {
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
141
|
+
const parsed = Number(value);
|
|
142
|
+
if (!Number.isInteger(parsed) || parsed < min) {
|
|
143
|
+
throw new Error(`${flag} must be a whole number of at least ${min}`);
|
|
144
|
+
}
|
|
145
|
+
return parsed;
|
|
146
|
+
}
|
|
147
|
+
function selectRules(disable) {
|
|
148
|
+
const disabled = new Set((disable ?? '')
|
|
149
|
+
.split(',')
|
|
150
|
+
.map((id) => id.trim())
|
|
151
|
+
.filter(Boolean));
|
|
152
|
+
const unknown = [...disabled].filter((id) => !defaultRules.some((rule) => rule.id === id));
|
|
153
|
+
if (unknown.length > 0) {
|
|
154
|
+
throw new Error(`Unknown rule: ${unknown.join(', ')}. See privalint --list-rules.`);
|
|
155
|
+
}
|
|
156
|
+
return defaultRules.filter((rule) => !disabled.has(rule.id));
|
|
157
|
+
}
|
|
158
|
+
function shouldFail(findings, failOn) {
|
|
159
|
+
switch (failOn) {
|
|
160
|
+
case 'never':
|
|
161
|
+
return false;
|
|
162
|
+
case 'warning':
|
|
163
|
+
return findings.some((f) => f.severity === 'error' || f.severity === 'warning');
|
|
164
|
+
default:
|
|
165
|
+
return findings.some((f) => f.severity === 'error');
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
main(process.argv.slice(2)).then((code) => {
|
|
169
|
+
process.exitCode = code;
|
|
170
|
+
}, (error) => {
|
|
171
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
172
|
+
process.stderr.write(`privalint: ${message}\n`);
|
|
173
|
+
process.exitCode = EXIT_ERROR;
|
|
174
|
+
});
|
|
175
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -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>;
|
package/dist/consent.js
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Button selectors of common consent managers. Best effort: vendors change
|
|
3
|
+
* their markup, so the label heuristic below is the fallback for everything
|
|
4
|
+
* that isn't listed or no longer matches.
|
|
5
|
+
*/
|
|
6
|
+
const KNOWN_CMPS = [
|
|
7
|
+
{
|
|
8
|
+
name: 'Cookiebot',
|
|
9
|
+
accept: '#CybotCookiebotDialogBodyLevelButtonLevelOptinAllowAll, #CybotCookiebotDialogBodyButtonAccept',
|
|
10
|
+
reject: '#CybotCookiebotDialogBodyButtonDecline',
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
name: 'OneTrust',
|
|
14
|
+
accept: '#onetrust-accept-btn-handler',
|
|
15
|
+
reject: '#onetrust-reject-all-handler',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: 'Usercentrics',
|
|
19
|
+
accept: '[data-testid="uc-accept-all-button"]',
|
|
20
|
+
reject: '[data-testid="uc-deny-all-button"]',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: 'Borlabs Cookie',
|
|
24
|
+
accept: '#BorlabsCookieBox [data-cookie-accept-all]',
|
|
25
|
+
reject: '#BorlabsCookieBox [data-cookie-refuse]',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: 'Complianz',
|
|
29
|
+
accept: '.cmplz-cookiebanner .cmplz-accept',
|
|
30
|
+
reject: '.cmplz-cookiebanner .cmplz-deny',
|
|
31
|
+
},
|
|
32
|
+
{ name: 'CookieYes', accept: '.cky-btn-accept', reject: '.cky-btn-reject' },
|
|
33
|
+
{
|
|
34
|
+
name: 'Didomi',
|
|
35
|
+
accept: '#didomi-notice-agree-button',
|
|
36
|
+
reject: '#didomi-notice-disagree-button',
|
|
37
|
+
},
|
|
38
|
+
{ name: 'consentmanager', accept: '.cmpboxbtnyes', reject: '.cmpboxbtnno' },
|
|
39
|
+
{ name: 'iubenda', accept: '.iubenda-cs-accept-btn', reject: '.iubenda-cs-reject-btn' },
|
|
40
|
+
{ name: 'Klaro', accept: '.klaro .cm-btn-success', reject: '.klaro .cm-btn-decline' },
|
|
41
|
+
{
|
|
42
|
+
name: 'CookieConsent',
|
|
43
|
+
accept: '#cc-main [data-role="all"]',
|
|
44
|
+
reject: '#cc-main [data-role="necessary"]',
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
// Unanchored on purpose: "Alle Cookies akzeptieren" and "Accept all cookies" must both match.
|
|
48
|
+
// Labels that also match REJECT_LABEL ("Nur notwendige akzeptieren") are never treated as accept.
|
|
49
|
+
const ACCEPT_LABEL = /akzeptieren|annehmen|zustimmen|einwilligen|einverstanden|alles? (cookies )?(zulassen|erlauben)|\baccept|\ballow all|\bagree\b|accepter|aceptar|accetta|accepteren|alles toestaan/i;
|
|
50
|
+
const REJECT_LABEL = /ablehnen|verweigern|nur (technisch )?(notwendige|essenzielle|erforderliche)|\breject|\bdecline|\bdeny\b|\brefuse|(only|use) (strictly )?(necessary|essential|required)|refuser|continuer sans accepter|rechazar|rifiuta|weigeren|alleen noodzakelijke/i;
|
|
51
|
+
/** Second option on "pay or OK" banners: accept tracking or take out a subscription. */
|
|
52
|
+
const SUBSCRIBE = /abonn|\babo\b|subscri|werbefrei|ohne werbung|ad-free|without ads|\bpur\b/i;
|
|
53
|
+
/** A frame without any of these words is not showing a consent banner. */
|
|
54
|
+
const CONSENT_CONTEXT = /cookie|consent|einwillig|tracking|datenschutz|privacy/i;
|
|
55
|
+
const CLICKABLE = 'button, [role="button"], a, input[type="button"], input[type="submit"]';
|
|
56
|
+
/**
|
|
57
|
+
* Looks for a consent banner, clicks its reject button if there is one,
|
|
58
|
+
* reloads the page and records what still gets loaded. The page must already
|
|
59
|
+
* be loaded; `requests` is the live array the page's request listener writes to.
|
|
60
|
+
*/
|
|
61
|
+
export async function observeConsent(page, context, requests, options) {
|
|
62
|
+
const banner = await findBanner(page);
|
|
63
|
+
if (banner === undefined) {
|
|
64
|
+
return { bannerFound: false, rejected: false };
|
|
65
|
+
}
|
|
66
|
+
const observation = {
|
|
67
|
+
bannerFound: true,
|
|
68
|
+
rejected: false,
|
|
69
|
+
...(banner.cmp === undefined ? {} : { cmp: banner.cmp }),
|
|
70
|
+
...(banner.acceptLabel === undefined ? {} : { acceptLabel: banner.acceptLabel }),
|
|
71
|
+
...(banner.rejectLabel === undefined ? {} : { rejectLabel: banner.rejectLabel }),
|
|
72
|
+
...(banner.payOrOk ? { payOrOk: true } : {}),
|
|
73
|
+
};
|
|
74
|
+
if (banner.reject === undefined) {
|
|
75
|
+
return observation;
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
const cookiesBefore = new Set((await context.cookies()).map(cookieKey));
|
|
79
|
+
const firstRequest = requests.length;
|
|
80
|
+
await banner.reject.click({ timeout: 5_000 });
|
|
81
|
+
await settle(page, options);
|
|
82
|
+
await page.reload({ waitUntil: 'load', timeout: options.timeoutMs });
|
|
83
|
+
await settle(page, options);
|
|
84
|
+
const cookies = (await context.cookies())
|
|
85
|
+
.filter((cookie) => !cookiesBefore.has(cookieKey(cookie)))
|
|
86
|
+
.map(({ name, domain, path, expires }) => ({ name, domain, path, expires }));
|
|
87
|
+
return {
|
|
88
|
+
...observation,
|
|
89
|
+
rejected: true,
|
|
90
|
+
afterReject: { requests: requests.slice(firstRequest), cookies },
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
95
|
+
return { ...observation, error: message.split('\n')[0] ?? message };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async function findBanner(page) {
|
|
99
|
+
for (const cmp of KNOWN_CMPS) {
|
|
100
|
+
const accept = await firstVisible(page.locator(cmp.accept));
|
|
101
|
+
if (accept !== undefined) {
|
|
102
|
+
const reject = await firstVisible(page.locator(cmp.reject));
|
|
103
|
+
return describe(accept, reject, cmp.name);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// Unknown or self-built banners: go by button labels, in every frame.
|
|
107
|
+
for (const frame of page.frames()) {
|
|
108
|
+
const text = await frame
|
|
109
|
+
.locator('body')
|
|
110
|
+
.innerText({ timeout: 2_000 })
|
|
111
|
+
.catch(() => '');
|
|
112
|
+
if (!CONSENT_CONTEXT.test(text)) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const clickable = frame.locator(CLICKABLE);
|
|
116
|
+
const accept = await firstVisible(clickable.filter({ hasText: ACCEPT_LABEL }).filter({ hasNotText: REJECT_LABEL }));
|
|
117
|
+
if (accept !== undefined) {
|
|
118
|
+
const reject = await firstVisible(clickable.filter({ hasText: REJECT_LABEL }));
|
|
119
|
+
return describe(accept, reject);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
async function describe(accept, reject, cmp) {
|
|
125
|
+
const banner = { acceptLabel: await label(accept) };
|
|
126
|
+
if (reject === undefined && (await offersSubscription(accept))) {
|
|
127
|
+
banner.payOrOk = true;
|
|
128
|
+
}
|
|
129
|
+
if (cmp !== undefined) {
|
|
130
|
+
banner.cmp = cmp;
|
|
131
|
+
}
|
|
132
|
+
if (reject !== undefined) {
|
|
133
|
+
banner.reject = reject;
|
|
134
|
+
banner.rejectLabel = await label(reject);
|
|
135
|
+
}
|
|
136
|
+
return banner;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Walks up from the accept button to the banner's outermost container below
|
|
140
|
+
* <body> and looks for a subscription offer among its buttons and links. The
|
|
141
|
+
* site's own navigation lives outside that container, so its "Abo" link
|
|
142
|
+
* doesn't count.
|
|
143
|
+
*/
|
|
144
|
+
async function offersSubscription(accept) {
|
|
145
|
+
return accept
|
|
146
|
+
.evaluate((element, source) => {
|
|
147
|
+
const pattern = new RegExp(source, 'i');
|
|
148
|
+
const parentOf = (node) => {
|
|
149
|
+
const root = node.getRootNode();
|
|
150
|
+
return node.parentElement ?? (root instanceof ShadowRoot ? root.host : null);
|
|
151
|
+
};
|
|
152
|
+
let container = element;
|
|
153
|
+
for (let parent = parentOf(container); parent !== null; parent = parentOf(container)) {
|
|
154
|
+
if (parent === document.body || parent === document.documentElement) {
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
container = parent;
|
|
158
|
+
}
|
|
159
|
+
return [...container.querySelectorAll('a, button, [role="button"]')].some((control) => pattern.test(control.innerText ?? ''));
|
|
160
|
+
}, SUBSCRIBE.source)
|
|
161
|
+
.catch(() => false);
|
|
162
|
+
}
|
|
163
|
+
async function firstVisible(locator) {
|
|
164
|
+
const count = await locator.count().catch(() => 0);
|
|
165
|
+
for (let i = 0; i < Math.min(count, 10); i++) {
|
|
166
|
+
const candidate = locator.nth(i);
|
|
167
|
+
if (await candidate.isVisible().catch(() => false)) {
|
|
168
|
+
return candidate;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return undefined;
|
|
172
|
+
}
|
|
173
|
+
async function label(locator) {
|
|
174
|
+
const text = await locator.innerText({ timeout: 1_000 }).catch(() => '');
|
|
175
|
+
const value = text.trim() || ((await locator.getAttribute('value').catch(() => null)) ?? '');
|
|
176
|
+
return value.replace(/\s+/g, ' ').trim().slice(0, 80);
|
|
177
|
+
}
|
|
178
|
+
async function settle(page, options) {
|
|
179
|
+
await page.waitForLoadState('networkidle', { timeout: options.timeoutMs }).catch(() => {
|
|
180
|
+
// See capturePage: some pages never go idle.
|
|
181
|
+
});
|
|
182
|
+
await page.waitForTimeout(options.settleMs);
|
|
183
|
+
}
|
|
184
|
+
const cookieKey = (cookie) => `${cookie.name}|${cookie.domain}|${cookie.path}`;
|
|
185
|
+
//# sourceMappingURL=consent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consent.js","sourceRoot":"","sources":["../src/consent.ts"],"names":[],"mappings":"AASA;;;;GAIG;AACH,MAAM,UAAU,GAA4B;IAC1C;QACE,IAAI,EAAE,WAAW;QACjB,MAAM,EACJ,+FAA+F;QACjG,MAAM,EAAE,wCAAwC;KACjD;IACD;QACE,IAAI,EAAE,UAAU;QAChB,MAAM,EAAE,8BAA8B;QACtC,MAAM,EAAE,8BAA8B;KACvC;IACD;QACE,IAAI,EAAE,cAAc;QACpB,MAAM,EAAE,sCAAsC;QAC9C,MAAM,EAAE,oCAAoC;KAC7C;IACD;QACE,IAAI,EAAE,gBAAgB;QACtB,MAAM,EAAE,4CAA4C;QACpD,MAAM,EAAE,wCAAwC;KACjD;IACD;QACE,IAAI,EAAE,WAAW;QACjB,MAAM,EAAE,mCAAmC;QAC3C,MAAM,EAAE,iCAAiC;KAC1C;IACD,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,EAAE,iBAAiB,EAAE;IAC3E;QACE,IAAI,EAAE,QAAQ;QACd,MAAM,EAAE,6BAA6B;QACrC,MAAM,EAAE,gCAAgC;KACzC;IACD,EAAE,IAAI,EAAE,gBAAgB,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,EAAE,cAAc,EAAE;IAC3E,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,wBAAwB,EAAE,MAAM,EAAE,wBAAwB,EAAE;IACvF,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,wBAAwB,EAAE,MAAM,EAAE,wBAAwB,EAAE;IACrF;QACE,IAAI,EAAE,eAAe;QACrB,MAAM,EAAE,4BAA4B;QACpC,MAAM,EAAE,kCAAkC;KAC3C;CACF,CAAC;AAEF,8FAA8F;AAC9F,kGAAkG;AAClG,MAAM,YAAY,GAChB,mLAAmL,CAAC;AAEtL,MAAM,YAAY,GAChB,wPAAwP,CAAC;AAE3P,wFAAwF;AACxF,MAAM,SAAS,GAAG,2EAA2E,CAAC;AAE9F,0EAA0E;AAC1E,MAAM,eAAe,GAAG,wDAAwD,CAAC;AAEjF,MAAM,SAAS,GAAG,wEAAwE,CAAC;AAO3F;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,IAAU,EACV,OAAuB,EACvB,QAA2B,EAC3B,OAA4B;IAE5B,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,CAAC;IACtC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;IACjD,CAAC;IAED,MAAM,WAAW,GAAuB;QACtC,WAAW,EAAE,IAAI;QACjB,QAAQ,EAAE,KAAK;QACf,GAAG,CAAC,MAAM,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,CAAC;QACxD,GAAG,CAAC,MAAM,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,MAAM,CAAC,WAAW,EAAE,CAAC;QAChF,GAAG,CAAC,MAAM,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,MAAM,CAAC,WAAW,EAAE,CAAC;QAChF,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC7C,CAAC;IACF,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QAChC,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,IAAI,CAAC;QACH,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC;QACxE,MAAM,YAAY,GAAG,QAAQ,CAAC,MAAM,CAAC;QAErC,MAAM,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;QAC9C,MAAM,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAC5B,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;QACrE,MAAM,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QAE5B,MAAM,OAAO,GAAqB,CAAC,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;aACxD,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,aAAa,CAAC,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;aACzD,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;QAE/E,OAAO;YACL,GAAG,WAAW;YACd,QAAQ,EAAE,IAAI;YACd,WAAW,EAAE,EAAE,QAAQ,EAAE,QAAQ,CAAC,KAAK,CAAC,YAAY,CAAC,EAAE,OAAO,EAAE;SACjE,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACvE,OAAO,EAAE,GAAG,WAAW,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,OAAO,EAAE,CAAC;IACtE,CAAC;AACH,CAAC;AAUD,KAAK,UAAU,UAAU,CAAC,IAAU;IAClC,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;QAC7B,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;QAC5D,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;YAC5D,OAAO,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC;IAED,sEAAsE;IACtE,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;QAClC,MAAM,IAAI,GAAG,MAAM,KAAK;aACrB,OAAO,CAAC,MAAM,CAAC;aACf,SAAS,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;aAC7B,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;QACnB,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAChC,SAAS;QACX,CAAC;QACD,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QAC3C,MAAM,MAAM,GAAG,MAAM,YAAY,CAC/B,SAAS,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,YAAY,EAAE,CAAC,CACjF,CAAC;QACF,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC;YAC/E,OAAO,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAClC,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,KAAK,UAAU,QAAQ,CAAC,MAAe,EAAE,MAA2B,EAAE,GAAY;IAChF,MAAM,MAAM,GAAW,EAAE,WAAW,EAAE,MAAM,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;IAC5D,IAAI,MAAM,KAAK,SAAS,IAAI,CAAC,MAAM,kBAAkB,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;QAC/D,MAAM,CAAC,OAAO,GAAG,IAAI,CAAC;IACxB,CAAC;IACD,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;QACtB,MAAM,CAAC,GAAG,GAAG,GAAG,CAAC;IACnB,CAAC;IACD,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC;QACvB,MAAM,CAAC,WAAW,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,CAAC;IAC3C,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;GAKG;AACH,KAAK,UAAU,kBAAkB,CAAC,MAAe;IAC/C,OAAO,MAAM;SACV,QAAQ,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC5B,MAAM,OAAO,GAAG,IAAI,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QACxC,MAAM,QAAQ,GAAG,CAAC,IAAa,EAAkB,EAAE;YACjD,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;YAChC,OAAO,IAAI,CAAC,aAAa,IAAI,CAAC,IAAI,YAAY,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC/E,CAAC,CAAC;QACF,IAAI,SAAS,GAAY,OAAO,CAAC;QACjC,KAAK,IAAI,MAAM,GAAG,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,KAAK,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;YACrF,IAAI,MAAM,KAAK,QAAQ,CAAC,IAAI,IAAI,MAAM,KAAK,QAAQ,CAAC,eAAe,EAAE,CAAC;gBACpE,MAAM;YACR,CAAC;YACD,SAAS,GAAG,MAAM,CAAC;QACrB,CAAC;QACD,OAAO,CAAC,GAAG,SAAS,CAAC,gBAAgB,CAAC,4BAA4B,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CACpF,OAAO,CAAC,IAAI,CAAE,OAAuB,CAAC,SAAS,IAAI,EAAE,CAAC,CACvD,CAAC;IACJ,CAAC,EAAE,SAAS,CAAC,MAAM,CAAC;SACnB,KAAK,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC;AACxB,CAAC;AAED,KAAK,UAAU,YAAY,CAAC,OAAgB;IAC1C,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;IACnD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC7C,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QACjC,IAAI,MAAM,SAAS,CAAC,SAAS,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC;YACnD,OAAO,SAAS,CAAC;QACnB,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,KAAK,UAAU,KAAK,CAAC,OAAgB;IACnC,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;IACzE,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,MAAM,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAC7F,OAAO,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACxD,CAAC;AAED,KAAK,UAAU,MAAM,CAAC,IAAU,EAAE,OAA4B;IAC5D,MAAM,IAAI,CAAC,gBAAgB,CAAC,aAAa,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE;QACpF,6CAA6C;IAC/C,CAAC,CAAC,CAAC;IACH,MAAM,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;AAC9C,CAAC;AAED,MAAM,SAAS,GAAG,CAAC,MAAsD,EAAE,EAAE,CAC3E,GAAG,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type HostPattern } from '../url.js';
|
|
2
|
+
export interface ThirdParty extends HostPattern {
|
|
3
|
+
vendor: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Embedded content that transfers the visitor's IP address (and often sets
|
|
7
|
+
* cookies) as soon as it loads. Trackers are listed separately in trackers.ts.
|
|
8
|
+
*/
|
|
9
|
+
export declare const embeds: readonly ThirdParty[];
|
|
10
|
+
/** Public CDNs for scripts, styles and icon fonts. */
|
|
11
|
+
export declare const cdns: readonly ThirdParty[];
|
|
12
|
+
export declare const findEmbed: (url: string) => ThirdParty | undefined;
|
|
13
|
+
export declare const findCdn: (url: string) => ThirdParty | undefined;
|