@duckduckgo/autoconsent 14.77.1 → 14.78.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.
- package/.agents/skills/publicwww-search/SKILL.md +54 -0
- package/.agents/skills/verify-pr/SKILL.md +71 -0
- package/.claude/skills/publicwww-search/SKILL.md +6 -0
- package/.claude/skills/verify-pr/SKILL.md +6 -0
- package/AGENTS.md +77 -282
- package/CHANGELOG.md +13 -0
- package/CLAUDE.md +1 -0
- package/dist/addon-firefox/manifest.json +1 -1
- package/dist/addon-mv3/manifest.json +1 -1
- package/eslint.config.mjs +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: publicwww-search
|
|
3
|
+
description: Search PublicWWW to find websites using specific HTML/JS/CSS snippets. Use this skill to check if a cookie popup is from a widespread third-party CMP, to find additional test URLs for a CMP rule, or to discover how many sites use a particular consent banner pattern.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# PublicWWW Source Code Search
|
|
7
|
+
|
|
8
|
+
Search website source code via the [PublicWWW](https://publicwww.com) API. Requires `PUBLICWWW_KEY` env var.
|
|
9
|
+
|
|
10
|
+
**Caveat:** PublicWWW indexes server-rendered HTML source only. CMPs injected purely via client-side JavaScript (e.g. tag managers, async script loaders) may not appear in results.
|
|
11
|
+
|
|
12
|
+
## API
|
|
13
|
+
|
|
14
|
+
Download search results as CSV:
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
https://publicwww.com/websites/SEARCH_QUERY/?export=csvsnippetsu&key=$PUBLICWWW_KEY
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
CSV format: `url;rank;snippet` (one site per line, sorted by popularity rank).
|
|
21
|
+
|
|
22
|
+
Use `export=csv` instead of `csvsnippetsu` to get `domain;rank` without snippets.
|
|
23
|
+
|
|
24
|
+
## Query Syntax
|
|
25
|
+
|
|
26
|
+
- Exact phrase: `"onetrust-banner-sdk"`
|
|
27
|
+
- Combine terms: `"cookie-banner" "reject-all"`
|
|
28
|
+
- Exclude: `"cookie-banner" -wordpress`
|
|
29
|
+
- TLD filter: `"sd-cmp" site:de`
|
|
30
|
+
- File type: `"__cmp" filetype:js` (also `filetype:css`)
|
|
31
|
+
- Page depth: `depth:all "math.min.js"` (0–5, default homepage only)
|
|
32
|
+
- Escape inner quotes: `"data-testid=\"cookie-reject\""`
|
|
33
|
+
|
|
34
|
+
Full syntax: https://publicwww.com/syntax.html
|
|
35
|
+
|
|
36
|
+
## Examples
|
|
37
|
+
|
|
38
|
+
Check if a banner ID is from a shared CMP:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
curl -s "https://publicwww.com/websites/%22sd-cmp%22/?export=csv&key=$PUBLICWWW_KEY" | head -20
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Find German sites using OneTrust:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
curl -s "https://publicwww.com/websites/%22onetrust-banner-sdk%22+site%3Ade/?export=csv&key=$PUBLICWWW_KEY" | head -10
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Search JS files for a CMP API:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
curl -s "https://publicwww.com/js/%22__cmp%22+filetype%3Ajs/?export=csv&key=$PUBLICWWW_KEY" | head -10
|
|
54
|
+
```
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: verify-pr
|
|
3
|
+
description: Verifies an autoconsent pull request by running local CI checks, reviewing rule quality, and inspecting Jenkins E2E results. Use when reviewing, verifying, or approving a PR, when checking if a PR is ready to merge, or when the user asks to validate PR changes.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Verify PR
|
|
7
|
+
|
|
8
|
+
Copy this checklist and track progress:
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
PR Verification:
|
|
12
|
+
- [ ] Step 1: Local checks pass
|
|
13
|
+
- [ ] Step 2: Rule review complete
|
|
14
|
+
- [ ] Step 3: CI results checked
|
|
15
|
+
- [ ] Step 4: Browser verification done
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Step 1: Local Checks
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm run lint
|
|
22
|
+
npm run rule-syntax-check
|
|
23
|
+
npm run test:lib
|
|
24
|
+
npx playwright test tests/<cmp>.spec.ts --project webkit
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
If any check fails, fix the issue and re-run before proceeding.
|
|
28
|
+
|
|
29
|
+
## Step 2: Rule Review
|
|
30
|
+
|
|
31
|
+
### JSON rule PRs
|
|
32
|
+
|
|
33
|
+
- [ ] Test spec exists in `tests/` with reachable URLs
|
|
34
|
+
- [ ] Selectors are stable (no CSS module hashes, dynamic IDs, or framework-generated IDs)
|
|
35
|
+
- [ ] `optOut` targets a reject/decline button, not a privacy policy link or close button
|
|
36
|
+
- [ ] `prehideSelectors` are narrow (no `body` or full-page selectors)
|
|
37
|
+
- [ ] `minimumRuleStepVersion: 2` if rule uses `removeClass`, `setStyle`, or `addStyle`
|
|
38
|
+
- [ ] Generated rule fixes (`rules/generated/`) are consistent across all region variants
|
|
39
|
+
|
|
40
|
+
### Code-based rule PRs
|
|
41
|
+
|
|
42
|
+
- [ ] No hardcoded site-specific values
|
|
43
|
+
- [ ] Fallback paths for regional variants
|
|
44
|
+
- [ ] Uses existing DOM helpers (`this.click()`, `this.waitForElement()`, etc.)
|
|
45
|
+
|
|
46
|
+
## Step 3: CI Results
|
|
47
|
+
|
|
48
|
+
**GitHub Actions** (`.github/workflows/checks.yml`): Runs `lint` and `test:lib`. Must pass.
|
|
49
|
+
|
|
50
|
+
**Jenkins**: Runs Playwright E2E in 9 regions (US, GB, AU, CA, DE, FR, NL, CH, NO) for modified rules only. Posts a PR comment with artifact ZIP and [review tool](https://zok.pw/autoconsent-review-tool/) link for inspecting screenshots.
|
|
51
|
+
|
|
52
|
+
### Flaky E2E failures
|
|
53
|
+
|
|
54
|
+
Before concluding a test is broken:
|
|
55
|
+
|
|
56
|
+
1. Check Jenkins screenshots in the review tool
|
|
57
|
+
2. Verify the site still shows the same popup
|
|
58
|
+
3. Region-only failures → consider adding `skipRegions`
|
|
59
|
+
4. CI retries twice — intermittent failures often self-resolve
|
|
60
|
+
|
|
61
|
+
## Step 4: Browser Verification
|
|
62
|
+
|
|
63
|
+
Build and load extension: `npm run prepublish` → load `dist/addon-mv3/` in Chrome.
|
|
64
|
+
|
|
65
|
+
Confirm on target site(s):
|
|
66
|
+
|
|
67
|
+
- Popup is detected and handled
|
|
68
|
+
- Page scrolls normally after opt-out
|
|
69
|
+
- Interactive elements remain clickable
|
|
70
|
+
|
|
71
|
+
For CMP rules: test on multiple sites using the same CMP (find sites in `data/coverage.json`).
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: publicwww-search
|
|
3
|
+
description: Search PublicWWW to find websites using specific HTML/JS/CSS snippets. Use this skill to check if a cookie popup is from a widespread third-party CMP, to find additional test URLs for a CMP rule, or to discover how many sites use a particular consent banner pattern.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
@.agents/skills/publicwww-search/SKILL.md
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: verify-pr
|
|
3
|
+
description: Verifies an autoconsent pull request by running local CI checks, reviewing rule quality, and inspecting Jenkins E2E results. Use when reviewing, verifying, or approving a PR, when checking if a PR is ready to merge, or when the user asks to validate PR changes.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
@.agents/skills/verify-pr/SKILL.md
|
package/AGENTS.md
CHANGED
|
@@ -10,8 +10,9 @@ lib/cmps/ Code-based CMP rule classes (sourcepoint, onetrust, etc.)
|
|
|
10
10
|
lib/rules.ts Type definitions for AutoConsentCMPRule and rule steps
|
|
11
11
|
lib/eval-snippets.ts Eval snippets for main-world JS execution
|
|
12
12
|
rules/autoconsent/ Hand-maintained JSON rules
|
|
13
|
-
rules/generated/
|
|
13
|
+
rules/generated/ Crawler-generated JSON rules (auto_XX_domain_hash.json)
|
|
14
14
|
rules/build.ts Merges all rules into rules.json, consentomatic.json, compact-rules.json
|
|
15
|
+
data/ Coverage data (coverage.json) and site lists
|
|
15
16
|
tests/ Playwright E2E test specs (one per CMP)
|
|
16
17
|
tests-wtr/ Web Test Runner unit tests for DOM actions and rule logic
|
|
17
18
|
playwright/runner.ts Test harness: generateCMPTests(name, urls, options)
|
|
@@ -42,310 +43,103 @@ npm run watch # auto-rebuild on changes to lib/, addon/, rules/
|
|
|
42
43
|
| `npm run create-rule` | Scaffold a new JSON rule + test spec |
|
|
43
44
|
|
|
44
45
|
## Code Style
|
|
45
|
-
|
|
46
46
|
- **Preserve existing comments.** Do not remove JSDoc comments, TODO comments, or inline explanations unless the related code is also being removed. Rewriting a comment to reflect updated logic is fine.
|
|
47
47
|
|
|
48
|
-
## Working with
|
|
49
|
-
|
|
50
|
-
### JSON Rules
|
|
51
|
-
|
|
52
|
-
JSON rules live in `rules/autoconsent/` (hand-maintained) and `rules/generated/` (auto-generated). Each file defines one CMP rule following the `AutoConsentCMPRule` type in `lib/rules.ts`.
|
|
53
|
-
|
|
54
|
-
```json
|
|
55
|
-
{
|
|
56
|
-
"name": "example-cmp",
|
|
57
|
-
"prehideSelectors": ["#cookie-banner"],
|
|
58
|
-
"detectCmp": [{ "exists": "#cookie-banner" }],
|
|
59
|
-
"detectPopup": [{ "visible": "#cookie-banner" }],
|
|
60
|
-
"optIn": [{ "waitForThenClick": "#accept-all" }],
|
|
61
|
-
"optOut": [{ "waitForThenClick": "#reject-all" }],
|
|
62
|
-
"test": [{ "cookieContains": "consent=rejected" }],
|
|
63
|
-
"minimumRuleStepVersion": 1
|
|
64
|
-
}
|
|
65
|
-
```
|
|
48
|
+
## Working with autoconsent rules
|
|
66
49
|
|
|
50
|
+
### Rule syntax
|
|
67
51
|
For the complete rule syntax reference (all step types, element selectors, conditionals, etc.), see [docs/rule-syntax.md](docs/rule-syntax.md).
|
|
68
52
|
|
|
69
|
-
###
|
|
70
|
-
|
|
71
|
-
`prehideSelectors` inject CSS early (before the CMP is even detected) to prevent the cookie popup from flickering on screen. They use `opacity: 0` (not `display: none`) so the popup still occupies layout space and detection via `visible` checks still works. If opt-out doesn't start within 2 seconds, the elements are automatically unhidden to avoid permanently hiding page content.
|
|
72
|
-
|
|
73
|
-
Keep prehideSelectors **narrow** — they are applied across all matching rules simultaneously, so an overly broad selector (e.g. `body`) could hide the entire page during the 2-second window.
|
|
74
|
-
|
|
75
|
-
### minimumRuleStepVersion
|
|
76
|
-
|
|
77
|
-
New step types are added to the autoconsent engine over time. `minimumRuleStepVersion` declares which version of the step format a rule requires. Clients that don't support the required version silently skip the rule, preventing failures on older app versions.
|
|
78
|
-
|
|
79
|
-
**Version history:**
|
|
80
|
-
- `1` (default) — all original step types (`exists`, `visible`, `waitFor`, `click`, `waitForThenClick`, `wait`, `hide`, `if`/`then`/`else`, `any`, `eval`, `cookieContains`, etc.)
|
|
81
|
-
- `2` — added `removeClass`, `setStyle`, `addStyle`
|
|
82
|
-
|
|
83
|
-
**When to set it:** Omit the field (or set to `1`) if the rule only uses original step types. Set to `2` if the rule uses `removeClass`, `setStyle`, or `addStyle`. When future versions add new step types, set accordingly.
|
|
84
|
-
|
|
85
|
-
### Code-Based Rules
|
|
86
|
-
|
|
87
|
-
For CMPs requiring complex non-linear logic, CMP API interaction, or complex multi-path flows, use a TypeScript class extending `AutoConsentCMPBase` in `lib/cmps/`. Examples: `sourcepoint-frame.ts`, `onetrust.ts`, `cookiebot.ts`, `consentmanager.ts`.
|
|
88
|
-
|
|
89
|
-
Code-based rules implement the `AutoCMP` interface: `detectCmp()`, `detectPopup()`, `optOut()`, `optIn()`, and optionally `test()`. They have access to DOM helpers like `this.click()`, `this.waitForElement()`, `this.waitForVisible()`, and `this.elementExists()`.
|
|
90
|
-
|
|
91
|
-
### When to Use Code vs JSON
|
|
92
|
-
|
|
93
|
-
- **JSON:** Linear consent flows, DOM-based detection, single-path opt-out. JSON rules are preferable because they can be shipped in DuckDuckGo apps without a full app release.
|
|
94
|
-
- **Code:** Multi-path branching, CMP JavaScript API calls, `Promise.race()` for competing UI states, complex state machines (e.g., Sourcepoint serving GDPR/CCPA/US National variants on different URL paths).
|
|
95
|
-
|
|
96
|
-
### Selector Strategy
|
|
97
|
-
|
|
98
|
-
Prefer selectors in this order (most stable first):
|
|
99
|
-
|
|
100
|
-
1. **Stable data attributes:** `[data-testid="cookie-reject"]`, `[data-action="sp-cc"]`, `[data-qa="allow-all-cookies"]`
|
|
101
|
-
2. **Stable IDs:** `#sp-cc-accept`, `#cookie-banner` — but avoid dynamic IDs from React Aria (`#react-aria*`), Radix (`#radix-\:*\:`), or CSS Modules (`.sd-cmp-3cRQ2`), which change between builds/sessions.
|
|
102
|
-
3. **Semantic class substrings:** `[class*="cookie-banner"]`, `[class*="reject"]` — avoid full body class lists (`body.home.wp-singular.page-template...`) which break across pages.
|
|
103
|
-
4. **Structural CSS:** `#banner button.secondary` — avoid deep `nth-child` chains from generated rules.
|
|
104
|
-
5. **XPath text matching (fallback):** `xpath///button[contains(., 'Reject')]` — use as a last resort since button text is language-specific and breaks across locales. Same caution applies to `aria-label` attributes, which are often localized.
|
|
105
|
-
6. **Array selectors** for shadow DOM / iframe piercing: `["host-element", "button"]` finds `button` inside the shadow root of `host-element`. Each string in the array narrows the search scope — if an intermediate element has an open `shadowRoot`, the next selector runs inside it; if it's a same-origin iframe, the next selector runs inside its `contentDocument`. Use when a CMP renders inside shadow DOM or a same-origin iframe.
|
|
106
|
-
|
|
107
|
-
When writing or reviewing selectors, also watch out for:
|
|
108
|
-
- **Hardcoded attribute values** that are site-specific — use generic selectors in code-based rules.
|
|
109
|
-
- **Over-qualified selectors** from generated rules — e.g. `div[id][name][role][aria-modal][tabindex][lang]` requiring every attribute to exist, or redundant `:nth-child(2)#some-id` where the ID alone suffices.
|
|
110
|
-
|
|
111
|
-
## Debugging and Fixing Rules
|
|
112
|
-
|
|
113
|
-
### Identifying Broken Rules
|
|
114
|
-
|
|
115
|
-
1. **Use a real browser** to investigate. A real browser in a computer-use subagent is **highly preferred** over Playwright or Puppeteer-based scripts — cookie popups often behave differently in headless/automated browsers.
|
|
116
|
-
2. **Playwright test failures** are a secondary signal. Run the specific test:
|
|
117
|
-
```bash
|
|
118
|
-
npx playwright test tests/sirdata.spec.ts --project webkit
|
|
119
|
-
```
|
|
120
|
-
3. **Check test output** for which stage failed: `cmpDetected`, `popupFound`, `autoconsentDone`, `optOutResult`, `selfTestResult`.
|
|
121
|
-
4. **Use the test extension** (`dist/addon-mv3/`) for manual debugging. Load it in Chrome, visit the site, and check the devtools panel for step-by-step logs.
|
|
122
|
-
|
|
123
|
-
### Common Failure Modes
|
|
124
|
-
|
|
125
|
-
**Race conditions:** Consent popups load asynchronously. Use `waitFor` / `waitForThenClick` / `waitForVisible` instead of bare `exists` / `click`. Add `{ "wait": 500 }` before critical actions when the CMP has known async initialization. In code-based rules, use `Promise.race()` for multiple possible UI states. **Never** use `{ "wait": N }` in `detectCmp` or `detectPopup` — the engine handles retries internally.
|
|
126
|
-
|
|
127
|
-
**Incorrect consent action selectors:** Generated rules sometimes target a privacy policy link instead of the reject button. Ensure `optOut` steps target an actual reject/decline button.
|
|
128
|
-
|
|
129
|
-
**Region-dependent behavior:** Many CMPs show different dialogs by region (GDPR in EU, CCPA in US). See [Regional Differences](#regional-differences) below.
|
|
130
|
-
|
|
131
|
-
### Fixing JSON Rules
|
|
132
|
-
|
|
133
|
-
1. Read the existing rule to understand its current selectors and flow.
|
|
134
|
-
2. Identify the broken step from test output or by inspecting the site.
|
|
135
|
-
3. Edit the JSON file — apply the fix to every occurrence of the selector within the file (`detectCmp`, `detectPopup`, `optOut`, and `test` often use similar selectors).
|
|
136
|
-
4. For site-specific rules, double-check if the popup is still site-specific. If not, consider if a generic rule is more appropriate.
|
|
137
|
-
5. **Always update the corresponding test spec** in `tests/`. If no spec exists, create one.
|
|
138
|
-
6. **Cross-check other rules** — search for the same selectors or CMP provider name across `rules/autoconsent/` and `rules/generated/` to find other rules that may need the same change.
|
|
139
|
-
7. For generated rules, the same CMP may appear across multiple region files (`auto_CH_*.json`, `auto_DE_*.json`, etc.). Apply the fix to all affected files.
|
|
140
|
-
8. Run `npm run lint` to validate.
|
|
141
|
-
|
|
142
|
-
### Fixing Code-Based Rules
|
|
143
|
-
|
|
144
|
-
1. Read the CMP class in `lib/cmps/` and trace the failing code path.
|
|
145
|
-
2. Avoid hardcoded attribute values that are site-specific.
|
|
146
|
-
3. Add path/state detection for new CMP variants. Check `location.pathname`, button presence, or URL parameters.
|
|
147
|
-
4. Add fallback paths when variants may not have the expected buttons.
|
|
148
|
-
|
|
149
|
-
### Adding Fallback Paths
|
|
150
|
-
|
|
151
|
-
Use `if`/`then`/`else` for region-dependent or variant-dependent flows:
|
|
152
|
-
|
|
153
|
-
```json
|
|
154
|
-
{
|
|
155
|
-
"if": { "exists": "#reject-button" },
|
|
156
|
-
"then": [{ "waitForThenClick": "#reject-button" }],
|
|
157
|
-
"else": [{
|
|
158
|
-
"if": { "exists": "#manage-cookies" },
|
|
159
|
-
"then": [
|
|
160
|
-
{ "waitForThenClick": "#manage-cookies" },
|
|
161
|
-
{ "waitForThenClick": "#reject-all" }
|
|
162
|
-
],
|
|
163
|
-
"else": [
|
|
164
|
-
{ "waitForThenClick": "[role='button'][title='Close']" }
|
|
165
|
-
]
|
|
166
|
-
}]
|
|
167
|
-
}
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
## Adding New Rules
|
|
171
|
-
|
|
172
|
-
1. Run `npm run create-rule` to scaffold the JSON + test spec.
|
|
173
|
-
2. **Check if the popup is from a third-party CMP provider** (e.g. OneTrust, Cookiebot, Sourcepoint). If so, prefer extending or fixing the existing generic rule rather than creating a site-specific one.
|
|
174
|
-
3. Fill in `detectCmp`, `detectPopup`, `optOut`, `optIn` with stable selectors. Do **not** use `{ "wait": N }` steps in `detectCmp` or `detectPopup` — detection must be fast and non-blocking (the engine retries automatically).
|
|
175
|
-
4. Add a `test` array — prefer `cookieContains` when the CMP stores consent in cookies.
|
|
176
|
-
- JSON rules can also use `{ "eval": "SNIPPET_NAME" }` steps to execute predefined JavaScript snippets from `lib/eval-snippets.ts`. Useful for calling CMP APIs (e.g., `window.Cookiebot`, `__cmp('getCMPData')`) in detection, opt-out, or test phases. Each snippet is a named function that returns a boolean. New snippets must be added to `lib/eval-snippets.ts` and referenced by name in the rule JSON.
|
|
177
|
-
5. **Always create or update the corresponding test spec** in `tests/`.
|
|
178
|
-
6. **Cross-check other rules** — search for the same selectors or CMP provider name across `rules/autoconsent/` and `rules/generated/` to see if other rules need the same change or already cover this CMP.
|
|
179
|
-
7. Use `data/coverage.json` to find example sites for testing. It contains per-CMP, per-region URLs: `{ "CmpName": { "REGION": { "exampleSites": [...] } } }`.
|
|
180
|
-
8. Run `npm run lint` and `npm run test:lib`.
|
|
181
|
-
9. Test with Playwright: `npx playwright test tests/my-cmp.spec.ts --project webkit`.
|
|
182
|
-
|
|
183
|
-
### When Generated Rules Need Fixes
|
|
53
|
+
### Regional Differences
|
|
184
54
|
|
|
185
|
-
|
|
186
|
-
- Deep `nth-child` chains that break on layout changes
|
|
187
|
-
- Dynamic IDs from UI frameworks
|
|
188
|
-
- Long body class lists
|
|
189
|
-
- Over-qualified selectors requiring many attributes simultaneously
|
|
55
|
+
CMPs behave differently by region:
|
|
190
56
|
|
|
191
|
-
|
|
57
|
+
- **EU/EEA (GDPR):** Full consent dialog with explicit reject/accept options.
|
|
58
|
+
- **US (CCPA/state laws):** Often a simpler notice with "Close" or "Do Not Sell". Some CMPs show nothing.
|
|
59
|
+
- **Other regions:** Varies.
|
|
192
60
|
|
|
193
|
-
|
|
61
|
+
Use `if`/`then`/`else` to handle regional variants within a single rule.
|
|
194
62
|
|
|
195
|
-
|
|
63
|
+
**All rule changes MUST be tested across geographic regions** to catch regional popup variations. Test from real geographic locations using available regional-testing tooling (e.g. proxy-based remote browsers).
|
|
196
64
|
|
|
197
|
-
###
|
|
65
|
+
### Generic vs Site-Specific Rules
|
|
198
66
|
|
|
199
|
-
|
|
67
|
+
**Always prefer writing a generic CMP rule over a site-specific rule.** One CMP rule can cover multiple sites. See "Identifying a Consent Management Platform" below for common techniques. If the popup is genuinely custom-built, a site-specific rule is the right call.
|
|
200
68
|
|
|
201
|
-
###
|
|
69
|
+
### JSON Rules vs Code-based rules
|
|
202
70
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
**Scroll lock via CSS class:** `body` or `html` gets a class like `no-scroll`, `modal-open`, `overflow-hidden`. Fix with:
|
|
206
|
-
```json
|
|
207
|
-
{ "removeClass": "no-scroll", "selector": "body" }
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
**Scroll lock via inline style:** `body.style.overflow = "hidden"`. Fix with:
|
|
211
|
-
```json
|
|
212
|
-
{ "addStyle": "overflow: auto !important", "selector": "body" }
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
**Overlay preventing clicks:** A `position: fixed` div with high z-index covers the page. Fix by hiding it:
|
|
216
|
-
```json
|
|
217
|
-
{ "hide": "#overlay-selector" }
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
**Body position lock:** `body.style.position = "fixed"` with `top: -XXpx`. Fix with:
|
|
221
|
-
```json
|
|
222
|
-
{ "setStyle": "", "selector": "body" }
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
### Cosmetic Rule Structure
|
|
71
|
+
JSON rules live in `rules/autoconsent/` (hand-maintained) and `rules/generated/` (auto-generated). Each file defines one CMP rule following the `AutoConsentCMPRule` type in `lib/rules.ts`.
|
|
226
72
|
|
|
227
73
|
```json
|
|
228
74
|
{
|
|
229
|
-
"name": "example-
|
|
230
|
-
"cosmetic": true,
|
|
75
|
+
"name": "example-cmp",
|
|
231
76
|
"prehideSelectors": ["#cookie-banner"],
|
|
232
77
|
"detectCmp": [{ "exists": "#cookie-banner" }],
|
|
233
78
|
"detectPopup": [{ "visible": "#cookie-banner" }],
|
|
234
|
-
"
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
],
|
|
238
|
-
"optIn": [{ "waitForThenClick": "#accept-button" }]
|
|
79
|
+
"optIn": [{ "waitForThenClick": "#accept-all" }],
|
|
80
|
+
"optOut": [{ "waitForThenClick": "#reject-all" }],
|
|
81
|
+
"test": [{ "cookieContains": "consent=rejected" }]
|
|
239
82
|
}
|
|
240
83
|
```
|
|
241
84
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
## Triaging Broken Sites
|
|
245
|
-
|
|
246
|
-
When investigating a site where cookie popup handling is broken or missing:
|
|
247
|
-
|
|
248
|
-
### Step 1: Check Current State
|
|
249
|
-
|
|
250
|
-
Load the bundled extension in Chrome (`dist/addon-mv3/` after `npm run prepublish`), visit the site, and check the devtools panel for autoconsent logs. Determine whether:
|
|
251
|
-
- An existing rule matched but failed (which stage? `detectCmp`, `detectPopup`, `optOut`?)
|
|
252
|
-
- No rule matched at all
|
|
253
|
-
|
|
254
|
-
### Step 2: Diagnose
|
|
255
|
-
|
|
256
|
-
If an **existing rule matched but failed**: identify the broken step from the logs, inspect the site to understand what changed (new selectors, different layout, region variant), and fix the rule.
|
|
257
|
-
|
|
258
|
-
If **no rule matched**: determine the CMP type. Check if the popup is from a known CMP (OneTrust, Sourcepoint, Cookiebot, etc.) by inspecting the banner's HTML, class names, and script sources. If it's a known CMP, the existing rule may need updated detection selectors. If it's unknown, create a new rule.
|
|
259
|
-
|
|
260
|
-
**Always check if the popup is from a third-party CMP provider.** If so, prefer creating or extending a generic rule rather than a site-specific one. Use `data/coverage.json` to find additional example sites for the same CMP to verify the rule works broadly.
|
|
261
|
-
|
|
262
|
-
### Step 3: Determine Rule Type
|
|
85
|
+
Code-based rules live in `lib/cmps/`. Each file defines one CMP rule following the `AutoConsentCMPBase` type in `lib/rules.ts`.
|
|
263
86
|
|
|
264
|
-
-
|
|
265
|
-
- If the popup has **no reject option** (only accept/close) → create a cosmetic rule
|
|
266
|
-
- If the CMP requires **complex logic** (API calls, multiple UI states, iframe communication) → use a code-based CMP class
|
|
87
|
+
**JSON rules are always preferred over code-based rules.** Code-based rules are rarely needed, and should only be used for complex cases, when JSON format is not expressive enough.
|
|
267
88
|
|
|
268
|
-
###
|
|
89
|
+
### Selectors
|
|
269
90
|
|
|
270
|
-
|
|
271
|
-
2. Add or update the test spec in `tests/`
|
|
272
|
-
3. Run `npm run lint` to validate
|
|
273
|
-
4. Test locally: `npx playwright test tests/<cmp>.spec.ts --project webkit`
|
|
274
|
-
5. Test in multiple regions if the CMP is region-dependent (requires both `REGION` and `PROXY_SERVER` — see [Testing Across Regions](#testing-across-regions))
|
|
275
|
-
6. For cosmetic rules, verify no breakage (scrolling works, page is interactable)
|
|
91
|
+
Prefer selectors stable across builds and locales: data attributes (`[data-testid="..."]`) > stable IDs > class substrings (`[class*="..."]`) > structural CSS > XPath (last resort). **Do NOT use CSS module hashes** (4+ random chars like `.css-1a2b3c`) or framework-generated IDs.
|
|
276
92
|
|
|
277
|
-
|
|
93
|
+
Array selectors pierce shadow DOM and same-origin iframes. Each selector in the array
|
|
94
|
+
scopes into the previous match's `.shadowRoot` or `.contentDocument`:
|
|
278
95
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
-
|
|
282
|
-
- **US (CCPA/state laws):** Often a simpler notice with just a "Close" button, or a "Do Not Sell" link. Some CMPs show nothing at all in the US.
|
|
283
|
-
- **Other regions:** May show GDPR-like dialogs, simplified notices, or nothing.
|
|
284
|
-
|
|
285
|
-
### Handling Regional Variants in Rules
|
|
286
|
-
|
|
287
|
-
Use `if`/`then`/`else` conditionals to handle different UIs within a single rule. For code-based rules, add path detection (e.g., Sourcepoint's `/privacy-manager/index.html` vs `/us_pm/index.html`).
|
|
288
|
-
|
|
289
|
-
### Testing Across Regions
|
|
290
|
-
|
|
291
|
-
Two things are needed to test from a specific region:
|
|
292
|
-
|
|
293
|
-
1. **`REGION` env var** — filters which test URLs to run (from `data/coverage.json`). This only controls test selection, it does **not** change where requests come from.
|
|
294
|
-
2. **`PROXY_SERVER` env var** — routes browser traffic through a geographic proxy so sites see the correct region. Without a proxy, the site sees your real location regardless of `REGION`.
|
|
295
|
-
|
|
296
|
-
```bash
|
|
297
|
-
# Local: only filters tests, requests come from your real location
|
|
298
|
-
REGION=DE npx playwright test tests/sirdata.spec.ts --project webkit
|
|
299
|
-
|
|
300
|
-
# With proxy: tests are filtered AND requests are routed through the proxy
|
|
301
|
-
REGION=DE PROXY_SERVER=socks5://proxy.example:1080 npx playwright test tests/sirdata.spec.ts --project webkit
|
|
302
|
-
```
|
|
303
|
-
|
|
304
|
-
In CI, Jenkins loads region-specific `.env` files that set both `REGION` and `PROXY_SERVER` together.
|
|
305
|
-
|
|
306
|
-
Test specs support `skipRegions` and `onlyRegions` to control when tests run:
|
|
307
|
-
|
|
308
|
-
```typescript
|
|
309
|
-
generateCMPTests('Sirdata', ['https://gizmodo.com/'], {
|
|
310
|
-
skipRegions: ['US'], // skip test in these regions
|
|
311
|
-
onlyRegions: [], // only run in these regions
|
|
312
|
-
});
|
|
96
|
+
```json
|
|
97
|
+
["#shadow-host", "button.reject"]
|
|
98
|
+
["#cmp-container iframe", ".opt-out-btn"]
|
|
313
99
|
```
|
|
314
100
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
-
|
|
327
|
-
-
|
|
328
|
-
-
|
|
329
|
-
-
|
|
330
|
-
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
101
|
+
Single-string selectors cannot pierce — use arrays whenever the target is inside a
|
|
102
|
+
shadow root or same-origin iframe.
|
|
103
|
+
|
|
104
|
+
### General Guidelines and Gotchas
|
|
105
|
+
- **Regional testing is mandatory** for any rule change — CMPs behave differently under GDPR (EU), CCPA (US), and other jurisdictions. Run the rule against different regions using available regional-testing tooling before considering the change done.
|
|
106
|
+
- When verifying a rule, **look at the screenshots** on top of the API results — sometimes a rule reports success, but the popup is not actually handled - a screenshot will detect this.
|
|
107
|
+
- **Paywalls do not need to be handled.** If the website presents the choice to pay or agree to cookies, the correct solution is to disable the feature on that site, so no code changes required in this case.
|
|
108
|
+
- If the pop-up has an explicit "reject"-like button, you should first consider why HEURISTIC rule didn't handle it. A fix to the heuristic rule is always preferred to a new rule, as long as it doesn't cause potential false-positives on other sites.
|
|
109
|
+
- **selfTests are optional.** It is okay to NOT have a self-test, or have it failing as long as the popup is handled correctly. Confirm this with screenshots.
|
|
110
|
+
- If you cover a new CMP or a new flavor of the existing CMP, ALWAYS try to look for more examples of that case, and add to the spec file.
|
|
111
|
+
- `detectCmp` and `detectPopup` must be fast. Do NOT use waiting steps — the engine retries automatically.
|
|
112
|
+
- **`prehideSelectors` do not affect autoconsent visibility checks.** Prehide selectors are injected early to prevent flicker, and are intentionally implemented using opacity, which hides elements from the user, but not from built-in steps such as `waitForVisible` and `visible`. That said, _prehide selectors should be narrow_: overly broad selectors (e.g. `body`) could hide the entire page.
|
|
113
|
+
- Prefer DOM-based steps when possible — `eval` steps are a last resort.
|
|
114
|
+
- Set `minimumRuleStepVersion: 2` if using `removeClass`, `setStyle`, or `addStyle`.
|
|
115
|
+
- Prefer `cookieContains` in `test` when the CMP stores consent in cookies.
|
|
116
|
+
- Use `npm run create-rule` to scaffold a new rule and a spec file.
|
|
117
|
+
|
|
118
|
+
### Fixing breakage in cosmetic rules
|
|
119
|
+
When using `hide`, the CMP may lock scrolling or add overlays. Add fixes AFTER the `hide` step, marked `"optional": true`:
|
|
120
|
+
|
|
121
|
+
| Problem | Fix |
|
|
122
|
+
|---------|-----|
|
|
123
|
+
| Scroll lock via CSS class | `{ "removeClass": "no-scroll", "selector": "body", "optional": true }` |
|
|
124
|
+
| Scroll lock via inline style | `{ "addStyle": "overflow: auto !important", "selector": "body", "optional": true }` |
|
|
125
|
+
| Overlay blocking clicks | `{ "hide": "#overlay", "optional": true }` |
|
|
126
|
+
| Body position lock | `{ "setStyle": "", "selector": "body", "optional": true }` |
|
|
127
|
+
|
|
128
|
+
Using `removeClass`, `setStyle`, or `addStyle` requires `"minimumRuleStepVersion": 2`.
|
|
129
|
+
|
|
130
|
+
## Identifying a Consent Management Platform
|
|
131
|
+
The following techniques can help identify a generic CMP:
|
|
132
|
+
|
|
133
|
+
1. **DOM inspection:** Check class names on popup elements for vendor prefixes
|
|
134
|
+
(`onetrust-`, `didomi-`, `sp_choice_type_`, `cmp-`, `fc-`, `klaro-`, `pd-`, etc.).
|
|
135
|
+
2. **JS source analysis:** Inspect the popup buttons' click handlers or find the cookie
|
|
136
|
+
that stores consent and search for that cookie name in the page's scripts. Look for:
|
|
137
|
+
- Vendor names in variable/function names or `window` globals.
|
|
138
|
+
- Scripts in `node_modules/`, `vendor/`, or `wp-content/plugins/` paths.
|
|
139
|
+
- License comments with vendor URLs at the top of the script.
|
|
140
|
+
3. **Cross-site prevalence:** Use the `publicwww-search` skill to search for distinctive
|
|
141
|
+
selectors, script URLs, or copy strings. If the same popup markup appears on many
|
|
142
|
+
sites, it's a CMP.
|
|
349
143
|
|
|
350
144
|
## CMP Discovery with PublicWWW
|
|
351
145
|
|
|
@@ -355,9 +149,10 @@ Requires `PUBLICWWW_KEY` environment variable.
|
|
|
355
149
|
|
|
356
150
|
## Verification
|
|
357
151
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
152
|
+
After creating or modifying a rule:
|
|
153
|
+
|
|
154
|
+
1. `npm run build-rules` — rebuild rules.json (required for tests)
|
|
155
|
+
2. `npm run rule-syntax-check` — validate rule JSON against schema
|
|
156
|
+
3. `npx playwright test tests/<name>.spec.ts` — run the E2E test
|
|
157
|
+
4. `npm run prepublish` — full build including extension bundle
|
|
158
|
+
5. Check the rule works across geographic regions using available regional-testing tooling.
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
# v14.78.0 (Fri May 08 2026)
|
|
2
|
+
|
|
3
|
+
#### 🚀 Enhancement
|
|
4
|
+
|
|
5
|
+
- Improve agent context: restructure skills, slim AGENTS.md [#1319](https://github.com/duckduckgo/autoconsent/pull/1319) ([@cursoragent](https://github.com/cursoragent) [@muodov](https://github.com/muodov))
|
|
6
|
+
|
|
7
|
+
#### Authors: 2
|
|
8
|
+
|
|
9
|
+
- Cursor Agent ([@cursoragent](https://github.com/cursoragent))
|
|
10
|
+
- Maxim Tsoy ([@muodov](https://github.com/muodov))
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
1
14
|
# v14.77.1 (Thu May 07 2026)
|
|
2
15
|
|
|
3
16
|
#### 🐛 Bug Fix
|
package/CLAUDE.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@AGENTS.md
|
package/eslint.config.mjs
CHANGED