Skip to main content

captcha-detection-metrics

Interpreting and querying Firefox's `captcha_detection.*` Glean metrics and the `captcha-detection` custom ping (BigQuery `mozdata.firefox_desktop.captcha_detection`, `mozdata.fenix.captcha_detection`). Use when analyzing captcha prevalence or solve / pass / interacted rates per vendor (ArkoseLabs, Cloudflare Turnstile, Datadome, reCAPTCHA v2, hCaptcha, AWS WAF), cohorting the ping by privacy settings or browsing volume, or writing or reviewing a query or dashboard over it. Records which ratios are valid per vendor, which counters are dead or over-counting, and the filters that keep non-organic rows out.

来源信息

仓库
mozilla/firefox-aidev-plugins
最近来源活动
2026年9月11日 21:04
检测到的 SKILL.md 语言
英语
星标
12
Forks
17

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
captcha-detection-metrics
description
Interpreting and querying Firefox's `captcha_detection.*` Glean metrics and the `captcha-detection` custom ping (BigQuery `mozdata.firefox_desktop.captcha_detection`, `mozdata.fenix.captcha_detection`). Use when analyzing captcha prevalence or solve / pass / interacted rates per vendor (ArkoseLabs, Cloudflare Turnstile, Datadome, reCAPTCHA v2, hCaptcha, AWS WAF), cohorting the ping by privacy settings or browsing volume, or writing or reviewing a query or dashboard over it. Records which ratios are valid per vendor, which counters are dead or over-counting, and the filters that keep non-organic rows out.
# Analyzing the CAPTCHA detection metrics ## Introduction Context and guidance for analyzing the `captcha_detection.*` metrics defined in `metrics.yaml` and sent in the `captcha-detection` custom ping (`pings.yaml`). Everything lives in `toolkit/components/captchadetection/` in mozilla-central: `CaptchaDetectionChild.sys.mjs` (detects captchas in-frame), `CaptchaDetectionParent.sys.mjs` (records metrics), `CaptchaDetectionPingUtils.sys.mjs` (ping submission). File paths below are relative to that directory unless stated otherwise. Purpose (per `pings.yaml`): analyze how often CAPTCHAs appear in the wild, broken down by users' anti-tracking / anti-fingerprinting settings. This document is about *interpreting and querying* the data — it records the non-obvious things that are easy to get wrong. The ping is **captcha-triggered**: it is submitted when a client encounters a detected captcha (an outcome event flags unsubmitted data; `pages_visited` alone does not trigger submission). It is therefore NOT sent by every client — the reporting population is "clients who hit a detected captcha", which is the right denominator to keep in mind when sizing any analysis. Detection is DOM/heuristic based: an in-frame actor identifies a vendor's captcha and reports load and outcome events, which the parent turns into counters. The exact triggers are vendor-specific (see Metrics Overview). Because detection depends on the vendor's page structure, it can miss cases the heuristic doesn't match, and a trend change can reflect a vendor markup change rather than user behavior. ## Metrics Overview The ping carries, per client: - **Per-vendor counters** for each supported captcha provider (below). Every counter has a normal and a `_pbm` (Private Browsing Mode) variant, with one historical exception: `datadome_oc_pbm` was only added in Firefox 154 (bug 2054037). It is NULL, not 0, on submissions from older builds — including current ESR — so any window reaching back before 154 must treat it as missing rather than as a zero load count (see Datadome). - **Context metrics** recorded at collection time: `privacy_*` (tracking protection incl. cryptomining/known-fingerprinter toggles, fingerprinting protection, resistFingerprinting) and `network_cookie_*` (cookie behavior, opt-in partitioning). These describe the user's settings, not captcha events. - **`pages_visited`** (+ `_pbm`) — a browsing denominator. Counter-suffix legend: `oc` loaded, `ps` shown, `pc`/`cc` passed/completed, `pf`/`cf` failed, `ac` auto-completed, `bl` blocked. **Not all vendors record all suffixes** — see each subsection. Pair non-`_pbm` counters with `pages_visited` for normal browsing and `_pbm` counters with `pages_visited_pbm` for private browsing; never mix the two. **Counters are batched across pings, so a single ping can look inconsistent.** The ping accumulates counters and submits periodically (`CaptchaDetectionPingUtils`, ~once/24h), not once per captcha. A captcha's lifecycle can straddle a submission boundary: the widget loads (incrementing `oc`) before a ping is sent, and the user interacts / it resolves (`ps`/`ac`/`cc`/`cf`/`pc`/`pf`) only after — so the load lands in one ping and its outcome in the next (or an outcome lands in a ping whose load already shipped). Within a single ping you can therefore see an outcome with no matching load, or a load whose outcome never arrives, and a per-ping ratio can exceed 100%. These straddles are small and mostly cancel in aggregate; a *large* imbalance (an outcome far exceeding its load count) instead signals corrupt / non-organic data — which is why the invalid-ratio checks (Data Analysis Techniques) use a gross tolerance rather than a strict inequality. ### ArkoseLabs Counters: - `arkoselabs_oc` — an Arkose puzzle loaded (the game-like "FunCaptcha"). There's no checkbox to click first, so a load basically means the puzzle appeared. - `arkoselabs_pc` — the puzzle was solved (the Arkose server confirmed "correct"). - `arkoselabs_pf` — the puzzle was gotten wrong (the server said "wrong"). - `arkoselabs_solutions_required` — how many rounds ("waves") that one puzzle made the person do. Stored as a spread of values, not a single number. `pc`/`pf` come from a network-response observer on the Arkose API (`/fc/ca/`): the terminal `solved` boolean -> `pc` (true) or `pf` (false). **Arkose has a real `pf` fail counter**, so `pc/(pc+pf)` is a genuine solve rate (not a lower bound like the widget vendors). NOTE: `arkoselabs_ps` exists in the schema but is **never recorded (always 0)** — nothing in the code increments it. Ignore it. Also `solutions_required.count` is the deprecated internal field (bug 1799509, unpopulated) — the sample count is `pc + pf`, so mean rounds = `solutions_required.sum / (pc + pf)`. **Ratios (normal browsing):** - prevalence = `oc / pages_visited` (~0.0043%) — how often an Arkose puzzle shows up, out of all the pages people load. - **solve rate = `pc / (pc + pf)`** (~67%) — of the puzzles that got a clear win-or-lose answer, the share that were solved. This is the number to trust, because wins and losses are counted the same way. - completion = `(pc + pf) / oc` (~34%) — ArkoseLabs's version of the **interacted** (resolved) rate the other providers have; the dashboard groups it in the *Interacted* section. Of the puzzles that loaded, how many we actually saw end in a win or a loss. Only rough: people leave or close the tab, and we don't always catch the ending. - mean solutions required = `solutions_required.sum / (pc + pf)` (~2.26) — how many rounds a typical puzzle asks for. - Sanity check: wins + losses can't be more than loads (`pc + pf <= oc`), give or take the rare edge case. NOTE: `solutions_required` counts **waves within a single challenge, not retries**. FunCaptcha shows several image "waves" in sequence; the response observer counts each `/fc/ca/` wave and, when a terminal `solved` boolean arrives, records one `pc`/`pf` plus that accumulated wave-count, then resets. So the mean-rounds figure (~2.26) and the solve rate are **independent**: a 2-wave challenge is still ONE session ending in ONE outcome, and the waves are not themselves pass/fail events — so "avg ~2 waves" does NOT imply a ~50% solve rate. The ~67% solve rate means ~33% of *decided* challenges came back `solved=false` (genuine, server-confirmed fails); abandoned challenges never get a terminal boolean, so they are excluded from both `pc` and `pf` (this is why the solve rate is a true rate, not a lower bound like the widget vendors). ### AWS WAF — defined but DEAD (do not use) Counters (all scaffolded, never wired up): - `awswaf_ps` - `awswaf_pc` - `awswaf_pf` - `awswaf_solutions_required` They exist in the schema / Glean dictionary, but there is no AWS WAF handler or recording code (not in this tree, not in searchfox/mozilla-central), and the production table carries **zero data** for all of them over 90 days (verified: `max(awswaf_ps)=0`, no rows > 0). No ratios are computable. Treat as absent until an implementation lands; a cleanup bug to either implement or remove these metrics would be reasonable. ### Cloudflare Turnstile Counters: - `cloudflare_turnstile_oc` — a Turnstile challenge loaded on the page. Turnstile usually checks you in the background, so a load does NOT mean the person did anything. - `cloudflare_turnstile_cc` — the challenge ended in a pass (the green check). This includes passes that happen automatically with no clicking. - `cloudflare_turnstile_cf` — the challenge ended in a fail. What they mean — the child actor (`CaptchaDetectionChild.sys.mjs`): 1. Identifies a Turnstile challenge iframe by its URL and constructs a handler -> increments `..._oc`. 2. Watches the widget's shadow DOM for a `#success` / `#fail` div to become visible -> increments `..._cc` or `..._cf`. The observer disconnects after the first outcome, so **at most one `cc`/`cf` per `oc`**. `oc` fires when the challenge iframe loads (the handler is constructed on the iframe-URL match), independent of visibility: a Turnstile that never surfaces a `#success`/`#fail` div (invisible/managed mode, or torn down before it shows) yields an `oc` with no `cc`/`cf` outcome. Crucially, the handler only checks *which of* `#success`/`#fail` is displayed — there is **no interaction detection at all**. A managed / non-interactive Turnstile that passes on its own (the success checkmark appears with zero user action) increments `cc` identically to a pass the user actually worked through. So **`cc` is not evidence of user interaction**, and Turnstile records only `oc`/`cc`/`cf` (no `ps`/`ac`) — automatic and interactive passes cannot be distinguished. This gap is tracked in **bug 2054267** (under meta 2054266): add a Turnstile auto-vs-interactive signal so it can be segmented like reCAPTCHA/hCaptcha. Appropriate ratios (`oc`/`cc`/`cf` are separate counters at different lifecycle points — do NOT use raw `cc/oc` as a "solve rate"): - **Pass rate = `cc / (cc + cf)`** — of the challenges we actually saw finish (pass or fail), the share that passed. This is the cleanest "did it succeed" number, and it's always between 0 and 100%. Show `cc+cf` next to it, and don't trust it when only a handful of challenges finished. - **Interacted = `(cc + cf) / oc`** — of the Turnstiles that loaded, how many we saw finish at all. The rest never showed a pass or fail (they ran in the background, got closed, or our detector missed them) — that is NOT the same as the person "giving up." Despite the name, this is really "how many resolved", NOT "a human interacted": Turnstile often passes on its own, and those silent passes still count as `cc`. - **Prevalence = `oc / pages_visited`** — how often a Turnstile loads per page. Very small (around 0.005%). Good for "how common is Turnstile". - Don't use **`cc / oc`** as a "solve rate" — it mostly reflects how often Turnstile runs in the background, not whether people succeed. Caveat: pass rate is bounded 0-100% by construction; `Interacted`/`Prevalence` are not bounded at daily granularity (see the invariant under Data Analysis). ### Datadome Counters: - `datadome_oc` — Datadome stepped in with its full-page gate screen. This only fires when Datadome actually interrupts the person, not just because the site uses Datadome. - `datadome_ps` — that screen showed a puzzle to solve. - `datadome_pc` — the puzzle was passed. - `datadome_bl` — the person was hard-blocked (a "no entry" page, no puzzle to even try). Recording (`recordDatadomeEvent`): a load reports `ps` (puzzle) or `bl` (hard block) — mutually exclusive; a pass reports `pc`. So `ps + bl <= oc`, `pc <= ps`. There is **no auto-success** signal (a silent pass loads no interstitial). `datadome_oc_pbm` was missing until bug 2054037 added it in **Firefox 154**, so PBM *load* data only exists from 154 onward (older builds, current ESR included, report PBM `ps`/`bl`/`pc` but no `oc`). On those older submissions the column is NULL rather than 0, so a PBM prevalence denominator built from it silently drops them instead of dividing by zero — scope any `oc_pbm` ratio to 154+ explicitly. **Ratios (normal browsing):** - intervention prevalence = `oc / pages_visited` — how often Datadome interrupts someone per page (it actually stepped in, not just "the site uses it"). - block share = `bl / (ps + bl)` — when Datadome steps in, how often it's a flat-out block versus a puzzle you can actually solve (~27%). The main Datadome number. - solve rate given puzzle = `pc / ps` — of the puzzles shown, how many got passed. This is a floor, not the true rate: there is no "failed" counter, the pass can go uncounted if the page moves on, and lots of people just leave the site — all of which look like "not solved." - block exposure = `bl / pages_visited` — how often someone gets hard-blocked per page. A direct "how much does this hurt people" measure. Dropped as redundant (since `ps + bl ≈ oc`): puzzle share (= 1 − block share) and puzzle non-completion (= 1 − solve rate). ### Google reCAPTCHA v2 Counters: - `google_recaptcha_v2_oc` — the "I'm not a robot" checkbox loaded on the page. (Only the visible checkbox kind; the fully-invisible kind isn't counted.) It fires when the box loads, not when someone clicks it — so most of these are just sitting on the page, unused. - `google_recaptcha_v2_ps` — the person was shown an image puzzle ("pick all the buses"). - `google_recaptcha_v2_pc` — that image puzzle was solved. - `google_recaptcha_v2_ac` — the person was waved through with no puzzle at all. Recording (`updateGRecaptchaV2State`): `ImagesShown` -> `ps`; a checkmark -> `ac` if images were never shown, else `pc`. Per widget `ac` and `ps` are meant to be mutually exclusive with `pc <= ps` and `ac + ps <= oc` — but in release data **`ps` grossly over-counts** (see the "shown" note below), so `ac + ps <= oc` does NOT hold in practice. **No fail counter.** Interactions = `ac + ps`. **Ratios (normal browsing):** - loaded per page = `oc / pages_visited` — how often the checkbox loads per page. - interacted per page = `(ac + ps) / pages_visited` (or `(ac + ps) / oc` per load) — how often something actually happened with it (a wave-through or a puzzle). Most loaded boxes never get this far. - auto-success % of interactions = `ac / (ac + ps)` — of the boxes that did something, how many waved the person through with no puzzle. - success rate (challenged) = `pc / ps` — of the puzzles shown, how many got solved. - non-completion rate (challenged) = `(ps - pc) / ps` — the flip side: fails and give-ups lumped together (there is no separate "failed" counter). **Cross-provider caveat (validated against 90d release data, desktop + Android):** `pc/ps` is a within-provider **lower bound**, not a clean solve-success rate, and is **not comparable across providers**. The observed ~43% (reCAPTCHA) vs ~94% (hCaptcha) is NOT relative solve skill; two things drive it: - **`pc` loss + abandonment (the main driver):** the post-solve checkmark (`pc`) is often lost when solving submits the form / navigates away before it is recorded, and reCAPTCHA's multi-round image puzzles are genuinely abandoned more than hCaptcha's. Both push reCAPTCHA's `pc/ps` down (measurement + behaviour). - **Different definitions of "shown":** reCAPTCHA `ps` fires on challenge-element *existence* (`rc-imageselect` present in the bframe); hCaptcha `ps` fires on *visibility* (`aria-hidden` flips). reCAPTCHA's existence check **does over-count**, confirmed in 90d release data: `ps` re-fires on the persistent element, reaching **76,156 in a single ping** and pushing `ac + ps > oc` for ~165k clients (~0.4%). Being fixed / aligned in **bug 2054272** (under meta 2054266). `ps` is also interaction-gated — only ~1.6% of `oc` — so it does NOT mean "the widget was present"; it means a challenge was *instantiated for an engaged user*. Separately, `oc` counts widget *loads*: reCAPTCHA sits on ~6% of pageviews with only ~2.4% ever interacted (passive embeds / periodic token-refresh reloads), so oc-denominated ratios mean "widget present", not "user challenged". (That passive majority shows up in the *interacted* rate, not in `pc/ps` — ignored widgets never reach `ps`.) ### hCaptcha Counters: - `hcaptcha_oc` — the hCaptcha checkbox loaded on the page. Like reCAPTCHA, this fires at load, not when someone actually uses it. - `hcaptcha_ps` — the person was shown the puzzle. - `hcaptcha_pc` — the puzzle was passed. - `hcaptcha_ac` — the person was waved through with no puzzle. Same recording model and mutual exclusivity as reCAPTCHA v2; no fail counter. Unlike reCAPTCHA, hCaptcha `ps` fires when the challenge frame becomes **visible** (`aria-hidden` -> false) — a tighter "genuinely shown" denominator that does not have reCAPTCHA's existence over-count (max `ps` per ping ~400, not ~76k), so `ac + ps <= oc` holds far more tightly. That, plus hCaptcha being deployed as hard gates (~43% of loads are interacted vs reCAPTCHA's ~2.4%), is why its `pc/ps` runs ~94%: a measurement + deployment difference, not that hCaptcha is "easier" to solve. **Ratios (normal browsing):** identical shape to reCAPTCHA v2 with the `hcaptcha_` prefix — loaded/page `oc/pages`, interacted/page `(ac+ps)/pages`, auto-success% `ac/(ac+ps)`, success `pc/ps`, non-completion `(ps-pc)/ps`. ## Known metric gaps (tracked) Improvements to these metrics are tracked under meta **bug 2054266**: - **bug 2054267** — Cloudflare Turnstile cannot distinguish automatic from interactive passes (`cc` counts silent auto-passes; no `ac`/`ps`). - **bug 2054272** — reCAPTCHA `ps` uses element *existence* while hCaptcha uses *visibility*; investigate and align so "shown" means the same across providers. - AWS WAF metrics are scaffolded but dead (see above) — implement or remove. Keep these in mind when comparing providers: the counters were not all designed to the same definition. ## Data Analysis Techniques Ratios are provider-specific (see Metrics Overview). Independent of provider, the techniques below control *which rows* enter the numerator/denominator. Always pair a counter with the matching-mode `pages_visited` and report the denominator count so small samples are visible. ### Cohorts by privacy settings (composite key) Build a composite key from the `privacy_*` / `network_cookie_*` context metrics and treat each distinct combination as a cohort. Split into a **non-PBM key** (the non-`_pbm` settings, paired with non-PBM counters) and a **PBM key** (the
在 GitHub 查看
这个 SKILL.md 很大,SkillsMP 这里只预览前一段内容。 在 GitHub 查看