src/request.rs in brave/adblock-rust, checked 2026-08-29: the constructor takes the request URL, the source URL, the request type and the method, and derives third_party by comparing the two domains.This is an old revision of the document!
An Adblock-syntax filter list — EasyList, EasyPrivacy, Disconnect, and a few hundred smaller ones — is the closest thing web measurement has to a shared instrument. It is free, comparable across papers, and reviewable rule by rule, which is why 198 papers in this corpus used or produced one and why almost every claim about advertising or tracking on the web ultimately rests on one.
It is also a moving, crowd-maintained, anglophone artefact with a known and large error rate in both directions, and the field uses it simultaneously as the instrument that produces a measurement and as the ground truth that the measurement is scored against. That circularity is this page's subject.
This page is about the list itself: what it is, which ones exist, how to record which one you used, how to apply it, where its coverage runs out, and how to say something defensible when your labels came from it. What to do with the requests once they are labelled is on Classifying web requests; that page carries the ML lineage, the request-unit ladder and the link-decoration material, and this page carries the list.
The one thing to take away. In this corpus the filter list is both the instrument and the definition, and almost nobody separates the two. Of the 167 papers that used a list as part of a classification step, 69 (41.3%) record not-applicable for validation and 61 (36.5%) record none at all — that is, roughly three quarters do not validate the labels, because the list is the label. Calzavara et al. [1Calzavara, Stefano; Casarin, Samuele; Squarcina, Marco; Maffei, Matteo (2026): "From Syntactic Matching to Taint Tracking and Back: A Comparative Study of Web Tracking Detection Techniques", in: Proceedings on Privacy Enhancing Technologies. (Link)] put a number on what that costs: comparing syntactic list matching against dynamic taint tracking request by request over the same crawl, they estimate 16%–19% of the list's own matches are false positives, rising to 27%–30% among requests only the list flagged, and find 7,021 requests (≈17% of their 40,605) that only taint tracking saw. Treat a filter-list hit as a noisy label with error bars in the high teens. Say so in your paper.
Three properties decide whether a list can answer your question at all. None of them is about syntax; the syntax is documented by the list maintainers and there is no reason to restate it here.
Rules split into network rules (stop the request), cosmetic rules (hide an element that already loaded, expressed as a CSS-superset selector) and exception rules (@@, un-block something a network rule would have caught). Only network rules can answer “would this request have been blocked”, and the split is not what people assume.
Measured live on 2026-08-29 by the script in Recording Which List You Used (its unedited output is in that section):
| List | Network | Cosmetic | Exception | What that means |
|---|---|---|---|---|
| EasyList | 54,722 | 24,512 | 758 | Nearly a third of it cannot answer a request question |
| EasyPrivacy | 55,099 | 33 | 836 | Effectively all network — this is the tracking list |
| EasyList Cookie List | 1,674 | 23,057 | 428 | Inverted: hiding a banner is a rendering problem |
| AdGuard URL-tracking (filter 17) | 2,520 | 2 | 91 | Purpose-built for link decoration |
| Peter Lowe's list | 3,528 | 0 | 0 | Domain-level only; high precision, low recall |
Exception rules are the part people silently drop. EasyList carries 758 and EasyPrivacy 836. An engine that evaluates them and a script that greps for rule strings will not agree, and the grep will over-count blocks with no error message.
$third-party, $domain=, the resource-type options ($script, $image, $xmlhttprequest, $sub_frame, …) and @@ exceptions all depend on where the request came from and what kind of thing it is. Brave's engine says so in its own constructor signature — Request::new(url, source_url, request_type, method).1)
So a crawl that logged only request URLs cannot be post-processed with a list, and it will fail silently rather than error. Record, per request:
$third-party and $domain= modifier evaluates against nothing;resourceType());
The crawler page compares which libraries expose each of these. Cosmetic filters are a separate matter: modern cosmetic syntax is a superset of CSS — procedural filters such as `:has-text()`, and HTML filtering (##^) — so do not try to evaluate them with your automation library's selector engine. Use the cosmetic API of the Ghostery or Brave engine to resolve selectors for a hostname first, then query with those.
EasyList publishes both a Version: and the exact Commit: that produced the file, and it changes several times an hour. Two fetches of the same URL on 2026-08-29, three minutes apart, returned two different builds:
| Fetched (UTC) | Version: the file declares | Network rules | sha256 (12) |
|---|---|---|---|
| 11:58 | 202608291151 | 54,722 | 6c8ceac99fab |
| 12:01 | 202608291144 | 54,721 | a93a0118564c |
Note the order. The later fetch returned the older build: easylist.to is behind a CDN and two requests minutes apart are not guaranteed to agree, let alone two requests days apart. This is why recording the Version: and Commit: is necessary but archiving the bytes is what actually makes you reproducible.
Seventeen days earlier the same list carried 52,557 network rules — +4.1% in 17 days.2) “We used EasyList” identifies nothing.
Status checked 2026-08-29 against the primary source in each row — the list's own canonical URL, or the GitHub API for repository state. Nothing here comes from a vendor blog or a listicle. Filter lists die quietly, and a dead list in a live crawl is a silent measurement error, so re-run external_checks_filter_lists.sh (on the provenance page, with its unedited output) rather than trusting this table's date.
| List | What it is for | Status, 2026-08-29 |
|---|---|---|
| EasyList | Advertisements, except first-party self-promotion. Full policy | Active, pushed 11:51 UTC the day this was checked. Version: and Commit: in the header |
| EasyPrivacy | Tracking and privacy. Four documented categories: generic URL/tracking patterns; self-hosted and CNAME first-party trackers; third-party-hosted tracking scripts; tracking servers blocked at URL level3) | Active |
| Disconnect tracking protection | Tracker domains grouped by company and category; also the entities.json entity map | Active (pushed 2026-08-28). CC BY-NC-SA 4.0 — read the LICENSE file before redistributing; it is not permissive.4) Firefox does not consume this repo directly — it ships the Safe-Browsing-format build from shavar-prod-lists (pushed 2026-08-27) |
| DuckDuckGo Tracker Radar | Automatically constructed tracker domains with company attribution, prevalence, fingerprinting and cookie signals. Data model | Active (pushed 2026-08-28). The derived blocklist is duckduckgo/tracker-blocklists — note the plural; the singular name returns 404 |
| Ghostery WhoTracks.me | Tracker prevalence dataset over the top sites, with company attribution | Active (repo pushed 2026-08-04, MIT). Both URLs moved: whotracks.me redirects to ghostery.com/whotracksme, and the repository is whotracksme/whotracks.me |
| AdGuard filters | Ads, tracking, annoyances, and a separate URL-tracking filter | Active (pushed 2026-08-29, GPL-3.0). GitHub Releases is empty — the canonical download is filters.adtidy.org/<platform>/filters/<N>.txt, whose header carries Version, TimeUpdated and Expires. The ID-to-list registry is FiltersRegistry. The base filter (ID 2) is 79,406 network and 51,193 cosmetic rules — larger than EasyList on both counts |
| Peter Lowe's list | Ad and tracking servers, domain-level, deliberately small | Active: 3,528 entries, Last-Modified the day this was checked. A useful high-precision, low-recall comparison point |
| AdGuard CNAME disguised trackers | The one list for the problem that defeats hostname matching: trackers reached through a first-party CNAME [6Dimova, Yana; Acar, Gunes; Olejnik, Lukasz; Joosen, Wouter; Van Goethem, Tom (2021): "The CNAME of the game: Large-scale analysis of DNS-based tracking evasion", Proceedings on Privacy Enhancing Technologies 2021:394–412. (DOI) (Link)] | Active (pushed 2026-08-17, MIT) — data/combined_disguised_trackers.txt, 224,830 lines on 2026-08-29. You cannot use it from request logs alone: you need the CNAME chain, which means recording DNS resolution at crawl time. Most crawlers do not, by default |
| justdomains | Not a list: a domain-only re-encoding of EasyList, EasyPrivacy, AdGuard DNS and others | Active as a conversion layer (HTTP 200). Cite the upstream list, not this. Three papers in this corpus label cookies with it |
| EasyList Cookie List | CSS selectors for cookie notices. Lives in the main easylist repo under easylist_cookie/ | Active (Last modified: 2026-08-28 00:50 UTC). No Version: field, only Last modified: — so record the date and the hash |
| DuckDuckGo autoconsent | CMP-specific rules that can express which purposes to accept or reject | Active and the best maintained of the three (pushed 2026-08-29, MPL-2.0). This is the one to build on |
| I Still Don't Care About Cookies | Removes notices and clicks through them, when the specific consent action does not matter | Active (pushed 2026-08-29), GPL-3.0, but 14,462 open issues |
| Consent-O-Matic | The original CMP rule set, with per-purpose consent | Alive but slower: last push 2026-02-07, 159 open issues |
mozilla/cookie-banner-rules-list | Firefox's cookie-banner handling rules | Archived on GitHub, last push 2025-01-07. Note the org: it is mozilla/, not mozilla-extensions/ — the latter 404s. Do not start here |
| hpHosts / Malwarebytes ATS | Historic hosts-file ad/tracking list | Dead. hosts-file.net still has a DNS record but HTTPS to it timed out on 2026-08-29; Malwarebytes stopped updating around 2020. It appears in older papers — 27 papers in this corpus used a hosts-file list — do not reuse it |
| DNS-blocking lists generally | Domain-level, no path granularity | See Website classification, and the smart-TV row in Coverage Holes, Measured |
The anglophone core is a sampling decision that nobody declares. EasyList and EasyPrivacy target what Sjösten et al. [7Sjösten, Alexander; Snyder, Peter; Pastor, Antonio; Papadopoulos, Panagiotis; Livshits, Benjamin (2020): "Filter List Generation for Underserved Regions", in: Proceedings of the ACM Web Conference. (DOI)] call “global” sites, “which in practice means either websites in English, or resources popular enough to appear on English-speaking sites in addition to sites targeting speakers of other languages”. If your population is a national top list, a non-English corpus, or a geographically diverse crawl, EasyList alone under-measures it, and the size of the gap is not a footnote.
Two papers in the whole 5,859-paper corpus are about regional lists. That is the finding: this is a systematically under-studied dependency of a very large literature.
The regional lists are real, maintained, and roughly the size you would want. Measured 2026-08-29 with the same script as above:
| List | Network | Cosmetic | Exception | Header |
|---|---|---|---|---|
| EasyList China | 11,377 | 6,935 | 986 | Version: 202608291151, Expires: 1 days |
| EasyList Germany | 2,149 | 3,650 | 164 | Version: 202608271302, with a Commit: field |
| Liste FR | 9,892 | 3,545 | 157 | Version: 202608291151 |
Note that EasyList China carries more than five times EasyList Germany's network rules; these are not interchangeable increments on a common base, and there is no reason to expect them to behave alike. The full index is at easylist.to (HTTP 200, 2026-08-29).
What to do about it. If your crawl visits sites in languages other than English, load the matching regional lists, say which, and report your results both with and without them. If you cannot, say that the anglophone lists are your instrument and treat non-English results as a lower bound — that is an honest limitation and reviewers accept it. What is not defensible is a per-country tracking comparison built on EasyList alone, because the instrument's sensitivity varies with the thing being compared.
Of the 178 papers in this corpus that name a filter list as a tool they used, 53 (29.8%) attach any version or date, and only 4 (2.2%) give something that identifies the actual rules — a list Version: string or a commit hash. The rest is “EasyList”, or “EasyList, 2022”. Given that the list publishes its own commit and changes several times an hour, this is the single largest reproducibility gap in this literature.
The four that got it right, and the form to copy:
| Paper | What it recorded |
|---|---|
| Matte et al. [8Matte, Célestin; Bielova, Nataliia; Santos, Cristiana Teixeira (2020): "Do Cookie Banners Respect my Choice? Measuring Legal Compliance of Banners from IAB Europe's Transparency and Consent Framework", in: Proceedings of the IEEE Symposium on Security and Privacy. (DOI)], IEEE S&P 2020 | Disconnect list, commit eb817fb1 (2019-12-10) |
| Demir et al. [9Demir, Nurullah; Hörnemann, Jan; Große-Kampmann, Matteo; Urban, Tobias; Pohlmann, Norbert; Holz, Thorsten; Wressnegger, Christian (2023): "On the Similarity of Web Measurements Under Different Experimental Setups", in: Proceedings of the ACM Internet Measurement Conference, pp. 356-369. ACM DOI 10.1145/3618257.3624795 is listed by DBLP but was not registered with the DOI resolver as of 2026-08-12; the DOI above resolves to the authors' institutional record of the same paper (DOI)], IMC 2023 | EasyList 202203300945 |
| Not Your Average App, PoPETs 20236) | EasyList 202105250854 |
| Li et al. [10Wang, Xinzhe; Zhuang, Zeyang; Meng, Wei; Cheng, James (2024): "Detecting and Understanding Self-Deleting JavaScript Code", in: Proceedings of the ACM Web Conference. (DOI)], TheWebConf 2024 | EasyList 202306271740 with EasyPrivacy |
A version is not enough on its own — archive the bytes. A Version: string is only resolvable while the maintainers keep the history; the .txt you actually matched against is the artefact. Run this at the start of the crawl, not when you write the paper, and put the JSON and the raw lists in your artefact.
#!/usr/bin/env python3 """Record exactly which filter list you used, so your crawl is reproducible. Filter lists change several times a day. "We used EasyList" is not a method; "EasyList, Version 202608291151, commit 3a4485aa, 54,722 network rules" is. Every Adblock-Plus-syntax list carries that metadata in its own header comments -- this script extracts it, counts the rules by kind, and writes a JSON record you can archive next to your crawl data and paste into your methodology section. Run it at the START of a crawl, not when you write the paper. python3 filterlist_provenance.py > filterlists.json python3 filterlist_provenance.py --save lists/ # also archive the raw .txt """ import argparse import hashlib import json import pathlib import re import sys import urllib.request from datetime import datetime, timezone # The lists a tracking measurement usually wants. The anglophone core is the # first block; if your crawl visits sites in any other language, the second # block is not optional -- see the regional-lists section of the page. LISTS = { "easylist": "https://easylist.to/easylist/easylist.txt", "easyprivacy": "https://easylist.to/easylist/easyprivacy.txt", "easylist-cookie": "https://secure.fanboy.co.nz/fanboy-cookiemonster.txt", "adguard-url-tracking": "https://filters.adtidy.org/windows/filters/17.txt", "peter-lowe": "https://pgl.yoyo.org/adservers/serverlist.php" "?hostformat=adblockplus&showintro=0&mimetype=plaintext", # Regional. Add the ones your population needs and delete the rest; the # full index is at easylist.to/pages/other-supplementary-filter-lists-and- # easylist-variants.html "easylist-china": "https://easylist-downloads.adblockplus.org/easylistchina.txt", "easylist-germany": "https://easylist.to/easylistgermany/easylistgermany.txt", "liste-fr": "https://easylist-downloads.adblockplus.org/liste_fr.txt", } # Header fields Adblock-syntax lists publish about themselves. Not every list # sets every field; a missing field is reported as None rather than guessed. HEADER_FIELDS = ("Version", "Last modified", "TimeUpdated", "Expires", "Commit", "Title") # A cosmetic rule hides an element that has already loaded; a network rule stops # the request. Only network rules answer "would this request have been blocked". # Note the order in classify(): a cosmetic *exception* (`site.com#@#.ad`) is # counted as cosmetic, not as an exception, because the question this count # answers is "how much of this list is about requests at all". COSMETIC = re.compile(r"#[@?$]?#|#\$#") def classify(line): if not line.strip() or line.startswith("!") or line.startswith("["): return "comment" if COSMETIC.search(line): return "cosmetic" if line.startswith("@@"): return "exception" return "network" def describe(name, url): req = urllib.request.Request(url, headers={"User-Agent": "filterlist-provenance/1.0"}) with urllib.request.urlopen(req, timeout=60) as resp: raw = resp.read() text = raw.decode("utf-8", errors="replace") lines = text.split("\n") header = {} for field in HEADER_FIELDS: m = re.search(rf"^!\s*{re.escape(field)}:\s*(.+)$", text[:8000], re.M) header[field] = m.group(1).strip() if m else None counts = {"comment": 0, "cosmetic": 0, "exception": 0, "network": 0} for line in lines: counts[classify(line)] += 1 return { "name": name, "url": url, "fetched_at": datetime.now(timezone.utc).isoformat(timespec="seconds"), "sha256": hashlib.sha256(raw).hexdigest(), "bytes": len(raw), "header": header, "rules": counts, }, raw def main(): ap = argparse.ArgumentParser() ap.add_argument("--save", metavar="DIR", help="also write the raw list files here") args = ap.parse_args() out = [] for name, url in LISTS.items(): try: record, raw = describe(name, url) except Exception as exc: # Fail loud: a list you could not fetch is a hole in your method, # not something to paper over with a stale local copy. print(f"FAILED {name} <{url}>: {exc}", file=sys.stderr) raise out.append(record) if args.save: d = pathlib.Path(args.save) d.mkdir(parents=True, exist_ok=True) (d / f"{name}.txt").write_bytes(raw) # Not every list sets `Version:`; EasyList Cookie List and Peter Lowe's # list only set `Last modified:`. Report whichever exists, and say so. stamp = ( record["header"]["Version"] or record["header"]["TimeUpdated"] or record["header"]["Last modified"] or "(no version or date in header)" ) print( f"{name:22} {stamp:24}" f" network={record['rules']['network']:7} cosmetic={record['rules']['cosmetic']:7}" f" exception={record['rules']['exception']:6} sha256={record['sha256'][:12]}", file=sys.stderr, ) json.dump(out, sys.stdout, indent=2) print(file=sys.stdout) if __name__ == "__main__": main()
Real output, 2026-08-29 (this is the run every rule count on this page comes from):
easylist 202608291151 network= 54722 cosmetic= 24512 exception= 758 sha256=6c8ceac99fab easyprivacy 202608291151 network= 55099 cosmetic= 33 exception= 836 sha256=b1812eb0ad01 easylist-cookie 2026-08-28 00:50 UTC network= 1674 cosmetic= 23057 exception= 428 sha256=2c39a370892c adguard-url-tracking 2.0.14.1 network= 2520 cosmetic= 2 exception= 91 sha256=7578d8eb0e3e peter-lowe Sat, 29 Aug 2026 10:08:46 GMT network= 3528 cosmetic= 0 exception= 0 sha256=28ca97374e62 easylist-china 202608291151 network= 11377 cosmetic= 6935 exception= 986 sha256=e5be0ec4ef4b easylist-germany 202608271302 network= 2149 cosmetic= 3650 exception= 164 sha256=1528a89f4222 liste-fr 202608291151 network= 9892 cosmetic= 3545 exception= 157 sha256=bacd552ed794
Run the crawl allowing everything, then classify. You can re-classify with a different list, or a different version of the same list, without re-crawling — which you cannot do if you blocked at request time. You also avoid changing the page you are measuring: a blocked resource changes what the page loads next, so a blocking crawl and an allowing crawl are not measuring the same web.
Engine maintenance status checked 2026-08-29 against the npm, PyPI and crates.io registries and the GitHub API. This is the section of the page that rots fastest.
| Library | Language | Status, 2026-08-29 | Use it? |
|---|---|---|---|
| brave/adblock-rust | Rust | Active (pushed 2026-08-26, MPL-2.0). The crate is named adblock, not adblock-rust — v0.13.3, published 2026-08-20 | Yes. Brave ships it, so it tracks uBlock-era syntax including $removeparam |
adblock-rs | Node | Active, v0.13.3 published 2026-08-20, tracks the Rust crate 1:1 | Yes, if you are in Node |
| @ghostery/adblocker | TypeScript/Node | Active, v2.18.2 published 2026-08-05 (repo pushed 2026-08-28). Renamed: @cliqz/adblocker is deprecated on npm, last published 2024-10-16 | Yes. Ghostery's own engine |
adblock (PyPI, ArniDagur/python-adblock) | Python | Stale: v0.6.0, uploaded 2022-07-17; repo last pushed 2023-03-28. Wraps an old adblock-rust | Only with a pinned, stated version |
braveblock (PyPI) | Python | Stale: v0.5.1, uploaded 2024-02-14 | Only with a pinned, stated version |
| abp-blocklist-parser | Python | Abandoned: last push 2020-11-02 | No. It predates $removeparam and most modern rule options |
| adblockparser | Python | Archived on GitHub (2019); PyPI v0.7 from 2016-10-17 | No — and nine papers in this corpus used it |
There is still no actively maintained Python binding to a modern filter-list engine, as of 2026-08-29. Both PyPI wrappers around Brave's engine are years behind the Rust crate (0.6.0 and 0.5.1 against 0.13.3), and the two pure-Python parsers are abandoned. If your pipeline is Python and your claim depends on rule semantics, either shell out to the Rust or Node engine, or state the parser version and accept that unsupported rule options are silently not matched. This is a real threat to validity and papers do not usually mention it.
Before you crawl at all: someone may already have. HTTP Archive runs a monthly request-level crawl of millions of pages and publishes it as a public BigQuery dataset, and the Web Almanac's Privacy chapter is that measurement joined against WhoTracks.me and AdGuard's CNAME-tracker list. Its Third Parties chapter answers a different question and categorises with Patrick Hulce's third-party-web dataset, not with filter lists — check which chapter's method you are reusing.7) Its limits are real and you must state them: it loads the landing page, does not interact, and carries no consent state. But if your question is prevalence or a longitudinal trend rather than behaviour under interaction, your own crawl may be the expensive way to get a worse answer.
“Install a blocker and see what it blocks” is now constrained by a documented rule budget rather than by engineering. Chrome's declarativeNetRequest limits, from the API reference read on 2026-08-29:8)
GUARANTEED_MINIMUM_STATIC_RULES = 30,000 rules across the enabled static rulesets;MAX_NUMBER_OF_REGEX_RULES — regular-expression rules of each type, each under 2 KB compiled.EasyList alone is 54,722 network rules and EasyPrivacy 55,099. Neither fits in the 30,000-rule static guarantee, let alone both, so an MV3 extension ships a subset chosen by its authors — which is what uBlock Origin Lite is, and why it is not feature-equivalent to uBlock Origin.9)
Firefox has its own caps and they are not Chrome's. MDN documents the same constant names but records the dynamic and session limits changing at Chrome 120 and Firefox 128 independently, so the values are per-browser and per-version.10) If your measurement compares blockers across browsers, read the constants at runtime and report them — the rule budget is a confound, not a constant.
Lukić and Papadopoulos [11Lukić, Karlo; Papadopoulos, Lazaros (2026): "Privacy vs. Profit: The Impact of Google's Manifest Version 3 (MV3) Update on Ad Blocker Effectiveness", in: Proceedings on Privacy Enhancing Technologies. (Link)] measured whether this degrades blocking and found no significant reduction in ad blocking and a significant increase in anti-tracking between MV2 and MV3 builds of four popular blockers, though MV3 left cosmetic placeholders in 21% of cases where MV2 left none. The blocking survived; whether an MV3 extension is a sound measurement instrument is a different question and it is not settled. If your method depends on the extension seeing every request, post-process a full crawl with the Rust or Node engine instead, where no rule cap applies.
You will be asked this in review, so have the numbers. Two things are being measured here and they are not the same: false negatives (the list does not know about a tracker) and bypasses (the rule exists and matches, and the request happens anyway).
The list is an anglophone desktop-web instrument. Every step away from that costs recall, and the cost has been measured:
| Where | Study | What the lists missed |
|---|---|---|
| Desktop web, behavioural reference | Fouad et al. [3Fouad, Imane; Bielova, Nataliia; Legout, Arnaud; Sarafijanovic-Djukic, Natasa (2020): "Missed by Filter Lists: Detecting Unknown Third-Party Trackers with Invisible Pixels", in: Proceedings on Privacy Enhancing Technologies, pp. 499-518. (DOI)], PETS 2020 | EasyList+EasyPrivacy missed 25.22% and Disconnect 30.34% of the trackers detected by invisible-pixel-seeded behavioural analysis |
| Desktop web, taint-tracking reference | Calzavara et al. [1Calzavara, Stefano; Casarin, Samuele; Squarcina, Marco; Maffei, Matteo (2026): "From Syntactic Matching to Taint Tracking and Back: A Comparative Study of Web Tracking Detection Techniques", in: Proceedings on Privacy Enhancing Technologies. (Link)], PoPETs 2026 | 7,021 requests (≈17% of 40,605) found only by taint tracking |
| Desktop web, JS-behaviour reference | Chen et al. [12Chen, Quan; Snyder, Peter; Livshits, Ben; Kapravelos, Alexandros (2021): "Detecting Filter List Evasion with Event-Loop-Turn Granularity JavaScript Signatures", in: Proceedings of the IEEE Symposium on Security and Privacy. (DOI)], IEEE S&P 2021 | 3,589 scripts with behaviour the lists blocked elsewhere, on 12.48% of sites |
| Desktop web, packet-metadata reference | Lee et al. [13Lee, Dongkeun; Joo, Minwoo; Lee, Wonjun (2023): "Net-track: Generic Web Tracking Detection Using Packet Metadata", in: Proceedings of the ACM Web Conference. (DOI)], TheWebConf 2023 | 34.5% of the classifier's apparent false positives were real trackers the lists had not found |
| Desktop web, deployable ML | Lee et al. [14Lee, Kiho; Lim, Chaejin; Jin, Beomjin; Kim, Taeyoung; Kim, Hyoungshick (2024): "AdFlush: A Real-World Deployable Machine Learning Solution for Effective Advertisement and Web Tracker Prevention", in: Proceedings of the ACM Web Conference. (DOI)], TheWebConf 2024 | 642 ads and trackers across 108 domains, confirmed and reported upstream |
| Desktop web, differential testing | Shuang et al. [15Shuang, He; Zhao, Lianying; Lie, David (2025): "Duumviri: Detecting Trackers and Mixed Trackers with a Breakage Detector", in: Proceedings of the Network and Distributed System Security Symposium. (Link)], NDSS 2025 | 22 previously unreported trackers (175 occurrences) in one evaluation set |
| Mobile apps | NoMoAds [16Shuba, Anastasia; Markopoulou, Athina; Shafiq, Zubair (2018): "NoMoAds: Effective and Efficient Cross-App Mobile Ad-Blocking", in: Proceedings on Privacy Enhancing Technologies. (DOI)], PETS 2018 | EasyList failed to detect 37% of ad requests in their app dataset |
| Mobile apps | NoMoATS [17Shuba, Anastasia; Markopoulou, Athina (2020): "NoMoATS: Towards Automatic Detection of Mobile Tracking", in: Proceedings on Privacy Enhancing Technologies. (DOI)], PETS 2020 | Of 37,438 requests, 2,963 (7.91%) were advertising/tracking requests no filter list (EasyList, EasyPrivacy, MoaAB) detected |
| Smart TVs | Varmarken et al. [18Varmarken, Janus; Le, Hieu; Shuba, Anastasia; Markopoulou, Athina; Shafiq, Zubair (2020): "The TV is Smart and Full of Trackers: Measuring Smart TV Advertising and Tracking", in: Proceedings on Privacy Enhancing Technologies. (DOI)], PETS 2020 | Four DNS blocklists on smart-TV testbed traffic: the best of them (Firebog) blocked only 22% and 27% of the distinct FQDNs contacted |
| VR / Web3 / long tail | Torres et al. [19Ferreira Torres, Christof; Willi, Fiona; Shinde, Shweta (2023): "Is Your Wallet Snitching On You? An Analysis on the Privacy Implications of Web3", in: 32nd USENIX Security Symposium (USENIX Security 23). (Link)], USENIX Sec 2023 | Five blocklists combined blocked 60 of 108 (56%) of the third parties on Web3 sites |
| Cryptomining | Rüth et al. [20Rüth, Jan; Zimmermann, Torsten; Wolsing, Konrad; Hohlfeld, Oliver (2018): "Digging into Browser-based Crypto Mining", in: Proceedings of the ACM Internet Measurement Conference. (DOI)], IMC 2018 | Up to 82% of mining sites identified by WebAssembly fingerprinting were not detected by block lists |
| Push / Service Workers | Subramani et al. [21Subramani, Karthika; Yuan, Xingzi; Setayeshfar, Omid; Vadrevu, Phani; Lee, Kyu Hyung; Perdisci, Roberto (2020): "When Push Comes to Ads: Measuring the Rise of (Malicious) Push Advertising", in: Proceedings of the ACM Internet Measurement Conference. (DOI)], IMC 2020 | Two popular blockers blocked zero ad-network Service Worker registrations; EasyList caught under 2% of the requests those workers then issued |
Nothing in this table is a criticism of the lists. They are maintained for browsing, by volunteers, on the web people browse. It is a statement about the instrument's sensitivity: if you point it anywhere other than the anglophone desktop web, report your recall or state that you did not measure it.
about:blank, srcdoc, blob), 73.7% of the URLs those frames requested should have been blocked by EasyList + EasyPrivacy + uBlock Origin lists and were not, because content blockers mishandle inherited origins. Nineteen distinct vulnerabilities across the blockers tested. If you post-process request logs with an engine you will count these as blocked; a live blocking crawl will not.AdFlush [14Lee, Kiho; Lim, Chaejin; Jin, Beomjin; Kim, Taeyoung; Kim, Hyoungshick (2024): "AdFlush: A Real-World Deployable Machine Learning Solution for Effective Advertisement and Web Tracker Prevention", in: Proceedings of the ACM Web Conference. (DOI)] and the filter lists it was compared against flagged almost exactly the same volume of traffic — 39.27% versus 39.92% of all requests — and the paper reads that as evidence AdFlush “can effectively serve as a substitute for filter lists”. The same paper reports 642 ads and trackers on 108 domains that the lists had missed. Both are true, and together they are the point: equal marginals are not equal sets. If you compare a classifier against a list, report the confusion matrix, not the two totals.
“Blocked ⇒ tracker” is a definition, not a finding. A filter list is a normative artefact: a rule exists because a volunteer decided this resource should not load, weighed against how much blocking it breaks. Scoring a classifier against it measures agreement with that decision, not accuracy about tracking — and reporting the disagreements as error discards exactly the cases that would have been discoveries. Two published results make this concrete rather than philosophical: 16%–19% of the list's matches are estimated false positives [1Calzavara, Stefano; Casarin, Samuele; Squarcina, Marco; Maffei, Matteo (2026): "From Syntactic Matching to Taint Tracking and Back: A Comparative Study of Web Tracking Detection Techniques", in: Proceedings on Privacy Enhancing Technologies. (Link)], and some true trackers are deliberately absent because blocking them breaks pages [5Amjad, Abdul Haddi; Munir, Shaoor; Shafiq, Zubair; Gulzar, Muhammad Ali (2024): "Blocking Tracking JavaScript at the Function Granularity", in: Proceedings of the ACM SIGSAC Conference on Computer and Communications Security. (DOI)].
The practice is not confined to request classifiers. 88 papers in this corpus name a filter list as the groundTruthSource for a classification step, across twelve different kinds of object — requests, domains, cookies, scripts, IP addresses, website categories, privacy policies, consent notices, network traffic, even email messages. The list has become the field's general-purpose definition of “advertising or tracking”, far outside what it was written to describe.
The clearest recent examples, each verified against the paper's own text:
The whole learned-classifier roster in this corpus whose stated ground truth is a filter list — eleven papers, and it reads as a who's-who of the reference baselines — is printed in full on the provenance page.
Nobody has escaped the list entirely, but four routes exist, in rising order of effort:
Everything in this section comes from a structured extraction over 5,859 full-text papers from CCS, IMC, NDSS, PETS, USENIX Security, TheWebConf and IEEE S&P, 2010–2026, one record per paper with a verbatim evidence quote per claim. The 2025 and 2026 venue-years are provisional — CCS and IMC 2026 have not been held, and IEEE S&P and WWW 2026 are incompletely selected — so any per-year row reaching them is under-represented by construction. Methodology and limits are at the end of this section.
A paper is in the population if it used or produced something that folds to a filter-list family. Engines (adblockparser, adblock-rust), heuristics that are not lists (Privacy Badger) and off-topic blocklists (spam, malware, IP reputation, censorship, certificate revocation) are excluded by name and counted separately.
| Signal | Papers |
|---|---|
| corpus | 5,859 |
| used or produced a filter list — the population | 198 (3.4% of the corpus) |
… named it in tools[] | 178 |
… named it in a classification[] step | 167 |
| … both | 147 |
… only in tools[] — an instrument, never a labelling step | 31 |
| named a list but only mentioned or compared against it — excluded | 18 |
“Mentioned” is not “used”. The 18 excluded papers are 8.4% of what a raw name search would score as adoption, and the compared ones are the dangerous rows: a paper that beats EasyList did not adopt it.
Of the 198 papers that used or produced a filter list. A paper naming several is counted under each, so shares do not sum to 100%. Names are free text and were folded into families; spellings is how many distinct strings the corpus uses, and unfolded is what the single most common exact string alone would have found.
| List family | Papers | Share of 198 | Spellings | Unfolded |
|---|---|---|---|---|
| EasyList | 112 | 56.6% | 28 | 92 |
| EasyPrivacy | 71 | 35.9% | 22 | 58 |
| Disconnect | 48 | 24.2% | 23 | 26 |
| Ghostery / WhoTracks.me | 34 | 17.2% | 9 | 20 |
| hosts-file lists (hpHosts, AdAway, MoaAB, Pi-hole, NoTrack, …) | 27 | 13.6% | 33 | 3 |
| Adblock Plus (the lists shipped with it) | 26 | 13.1% | 8 | 20 |
| uBlock Origin lists | 17 | 8.6% | 11 | 13 |
| DuckDuckGo Tracker Radar | 15 | 7.6% | 9 | 8 |
| unnamed or merely counted (“nine crowd-sourced filter lists”) | 10 | 5.1% | 10 | 1 |
| AdGuard | 8 | 4.0% | 13 | 4 |
| EasyList annoyance / anti-adblock variants | 7 | 3.5% | 8 | 2 |
| anti-adblock scripts and services | 3 | 1.5% | 4 | 1 |
| cryptomining lists (NoCoin, CoinBlockerLists, MinerBlock) | 3 | 1.5% | 4 | 3 |
| Acceptable Ads exception list | 1 | 0.5% | 1 | 1 |
Folding is not cosmetic. Counting exact strings would find 92 EasyList papers instead of 112 (−17.9%), 26 Disconnect papers instead of 48 (−45.8%, because Disconnect appears as Disconnect list, Disconnect.me, Disconnect blacklist, Disconnect Entity List and twenty more), and 3 hosts-file papers instead of 27. Any adoption table built on exact strings is wrong by tens of percent.
Of the 167 papers with at least one list-backed classification step. This is the finding that motivated this page: the filter list is not a request classifier, it is the field's general-purpose “is this advertising or tracking” oracle, and it is applied to objects it was never written to describe.
classification.target | Papers | Share of 167 |
|---|---|---|
web-request | 101 | 60.5% |
domain | 53 | 31.7% |
cookie | 8 | 4.8% |
website-category | 7 | 4.2% |
javascript | 5 | 3.0% |
other | 3 | 1.8% |
fingerprinting-script | 3 | 1.8% |
network-traffic | 2 | 1.2% |
consent-notice | 2 | 1.2% |
email-message | 1 | 0.6% |
Grouped by what that does to the instrument:
| Granularity | Papers | Share |
|---|---|---|
| The list's own unit — a URL pattern evaluated with request context | 101 | 60.5% |
Coarser than the list — a hostname or eTLD+1, so path, resource-type and $third-party options are discarded | 53 | 31.7% |
| Objects a filter list was not written to label — the list supplies provenance, not a rule match | 30 | 18.0% |
Nearly a third of the field throws away the list's granularity before using it. That is often deliberate and sometimes unavoidable — a DNS blocklist or a passive traffic capture gives you nothing finer than a hostname — but it changes what the label means, and it is rarely stated. The per-paper roster behind every non-request row is on the provenance page so you can check the claim.
| Question | Papers |
|---|---|
papers whose classification.groundTruthSource names a filter list (whole corpus) | 88 |
| … of the 198 list-using papers | 82 (41.4%) |
| distinct kinds of object those tuples label | 12 |
papers with a learned classifier (supervised-ml, unsupervised-ml, llm) whose stated ground truth is a filter list | 11 |
Validation of the list-backed classification steps, over the 167 papers. Sentinels are printed as themselves and never counted as an answer:
classification.validation | Papers | Share of 167 |
|---|---|---|
not-applicable (sentinel) | 69 | 41.3% |
none-reported (sentinel) | 61 | 36.5% |
| manual validation | 34 | 20.4% |
| comparison to another method | 14 | 8.4% |
The not-applicable row is mostly papers that applied a list as-is and reasonably consider the list itself the definition. That is exactly the assumption Calzavara et al. measured at 16%–19% false positives.
| Question | Papers | Share |
|---|---|---|
named a filter list in tools[] | 178 | — |
| attached any version or date | 53 | 29.8% |
attached something that identifies the rules — a list Version: or a commit | 4 | 2.2% |
| silent | 125 | 70.2% |
Read the 29.8% as an order of magnitude in both directions: the extraction fills the version field when the paper puts a version next to the name, so a paper that dates its lists in a crawl paragraph may not be credited — and several of the 53 give an extension version (Ghostery 5.4.1, Adblock Plus 3.7) rather than a list version, which does not identify the rules at all. The 2.2% is the figure that matters and it is not an artefact: a list Version: string is unmistakable, and only four papers have one.
| Venue | Corpus papers | Population | Share of venue |
|---|---|---|---|
| PETS | 510 | 55 | 10.8% |
| IMC | 638 | 36 | 5.6% |
| TheWebConf | 843 | 31 | 3.7% |
| IEEE S&P | 767 | 19 | 2.5% |
| CCS | 990 | 23 | 2.3% |
| USENIX Security | 1,410 | 24 | 1.7% |
| NDSS | 701 | 10 | 1.4% |
| Period | Corpus papers | Population | Per 1,000 |
|---|---|---|---|
| 2010–2013 | 511 | 6 | 11.7 |
| 2014–2017 | 769 | 25 | 32.5 |
| 2018–2021 | 1,439 | 58 | 40.3 |
| 2022–2024 | 1,955 | 70 | 35.8 |
| 2025–2026 (provisional) | 1,185 | 39 | 32.9 |
Filter-list use peaked in 2018–2021 and has been flat since, at roughly 3–4% of these seven venues. Nothing in the corpus supports “the field has moved on from filter lists”: machine learning was added alongside, not instead. Among the population, papers carrying any LLM-based classification step appear only in 2024 (2), 2025 (4) and 2026 (1) — real, new, and far too thin to call current practice on the strength of the corpus's most provisional years.
report_filter_lists.mjs; the list-name fold is req_fold.mjs, shared with Requests so the two pages cannot disagree about what “EasyList” means, and the granularity and regional folds are fl_fold.mjs. Every query, the scripts' unedited output, the full fold residue and the per-paper rosters are on the provenance page; corpus-level caveats are on corpus.not-applicable and none-reported are never folded into a stated value; where they are the largest rows, that is the finding.12 IP reputation blacklists, combined public blacklists). Off-topic blocklist families were excluded by name and counted: malware/phishing 10 papers, spam 8, IP reputation 7, content-category 6, censorship 3, certificate revocation 2.classification.target, classification.validation) are considerably more stable and are given as percentages.tools[] or classification[], that page additionally intersects with its request-classification task fold. Neither is wrong; the difference is a live example of why a population definition belongs in the provenance, not in a sentence.EasyList Version 202608291151, commit 3a4485aa — not “EasyList”. Archive the .txt with your artefact. 70% of papers give nothing; 98% give nothing that identifies the rules.adblock 0.13.3 and adblockparser 0.7 do not match the same rules, and the second silently ignores options it does not know.python-adblock against adblock 0.13.x, or the field should state loudly that Python pipelines must shell out.src/request.rs in brave/adblock-rust, checked 2026-08-29: the constructor takes the request URL, the source URL, the request type and the method, and derives third_party by comparing the two domains.NOASSERTION; the repository's own LICENSE file, fetched 2026-08-29, states “Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International”.almanac.httparchive.org/en/2024/.gorhill/uBlock remains actively maintained (pushed 2026-08-25) and works fully on Firefox, which still supports MV2. The MV3 replacement is uBlock Origin Lite (pushed 2026-08-26). Chrome's own Manifest V2 deprecation timeline gives 2026-08-31 as the date remaining MV2 extensions are removed from the Chrome Web Store — two days after this page was written, so re-check it.