diff --git a/scripts/check-site.py b/scripts/check-site.py index 03a60d1..17581a8 100644 --- a/scripts/check-site.py +++ b/scripts/check-site.py @@ -3,8 +3,9 @@ The page makes promises a reviewer should not have to re-check by hand: -- no third-party subresources (fonts included) — the self-hosted analytics - ingest is the one first-party exception, +- no third-party subresources (fonts included) — the cookie-free analytics + ingests (self-hosted Plausible, Cloudflare Web Analytics) are the only + exceptions, - the release metrics the deploy script injects are present and findable, - JSON-LD structured data parses and the FAQ stays in sync with it, - every in-page anchor resolves and the public anchor ids stay put, @@ -44,11 +45,14 @@ # The only host a subresource may load from (the page serves from this domain). OWN_DOMAIN = "tailroute.app" -# First-party hosts a subresource may additionally load from: the self-hosted -# Plausible ingest, same registrable domain, DNS-only behind grey cloud. It -# must stay that way — proxying it through a CDN challenges cross-origin -# event POSTs and pageviews drop silently. -FIRST_PARTY_HOSTS = frozenset({"pulse.tailroute.app"}) +# Analytics hosts a subresource may additionally load from. Both are +# cookie-free, aggregate-only page analytics: the self-hosted Plausible +# ingest (same registrable domain, DNS-only behind grey cloud — proxying it +# through a CDN challenges cross-origin event POSTs and pageviews drop +# silently) and the Cloudflare Web Analytics beacon, which per Cloudflare +# "does not collect or use your visitors' personal data". Nothing else may +# load cross-origin. +ANALYTICS_HOSTS = frozenset({"pulse.tailroute.app", "static.cloudflareinsights.com"}) # Claims the page must never make again: unsourced, stale, or contradictory. BANNED_PHRASES = ( @@ -310,8 +314,8 @@ def external_host(url): def subresource_host_allowed(host): - """Same-origin, or a first-party host (see FIRST_PARTY_HOSTS).""" - return not host or host == OWN_DOMAIN or host in FIRST_PARTY_HOSTS + """Same-origin, or an approved analytics host (see ANALYTICS_HOSTS).""" + return not host or host == OWN_DOMAIN or host in ANALYTICS_HOSTS def inline_styles(page): @@ -411,7 +415,7 @@ def check_third_party(pages, css_sources): fails.append(Failure( page.rel, element.line, f'<{element.tag} {attr}> loads from "{host}"' - f" — every subresource must be same-origin or first-party", + f" — every subresource must be same-origin or an approved analytics host", )) style = element.attrs.get("style") if style: @@ -422,7 +426,7 @@ def check_third_party(pages, css_sources): fails.append(Failure( page.rel, element.line, f'inline style loads from "{host}"' - f" — every subresource must be same-origin or first-party", + f" — every subresource must be same-origin or an approved analytics host", )) for source in css_sources: for pattern in CSS_URL_PATTERNS: @@ -431,7 +435,7 @@ def check_third_party(pages, css_sources): if not subresource_host_allowed(host): fails.append(Failure( source.rel, source.line_at(match.start()), - f'CSS loads from "{host}" — every subresource must be same-origin or first-party', + f'CSS loads from "{host}" — every subresource must be same-origin or an approved analytics host', )) return fails diff --git a/site/docs/index.html b/site/docs/index.html index adb668b..ceb63b9 100644 --- a/site/docs/index.html +++ b/site/docs/index.html @@ -19,6 +19,8 @@ + + + + diff --git a/tests/test-check-site.sh b/tests/test-check-site.sh index 350b762..f2bd961 100644 --- a/tests/test-check-site.sh +++ b/tests/test-check-site.sh @@ -283,11 +283,29 @@ PY assert_not_contains "FAIL third-party" "$CHECK_OUT" } +test_third_party_allows_cloudflare_beacon() { + local dir + dir=$(make_fixture) + write_good_page "$dir" + python3 - "$dir/index.html" <<'PY' +import sys + +path = sys.argv[1] +text = open(path, encoding="utf-8").read() +script = "" +open(path, "w", encoding="utf-8").write(text.replace("", script + "\n")) +PY + run_checker "$dir" + assert_eq "0" "$CHECK_RC" "the Cloudflare Web Analytics beacon must pass: $CHECK_OUT" + assert_not_contains "FAIL third-party" "$CHECK_OUT" +} + test_third_party_still_flags_third_party_analytics() { - # Only the self-hosted first-party ingest (pulse.tailroute.app, same - # registrable domain) is allowed; the SaaS host is third-party. The - # ingest must never be CDN-proxied instead: a proxy challenges - # cross-origin event POSTs and pageviews drop silently. + # Only the approved analytics hosts (see ANALYTICS_HOSTS: + # pulse.tailroute.app and static.cloudflareinsights.com) may load + # cross-origin; other analytics SaaS hosts are third-party. The + # Plausible ingest must never be CDN-proxied instead: a proxy + # challenges cross-origin event POSTs and pageviews drop silently. local dir dir=$(make_fixture) write_good_page "$dir"