This document is an illustration-first crosswalk. On the left are the reconnaissance targets that kwara (an operator-attribution forensics toolkit specialised in the digital-advertising ecosystem) actually collects — URL parameters, HTML pixels, third-party requests from HTTP Archives, ads.txt monetisation declarations, TLS certificates, HTTP headers, cloaking behaviour differentials; the right column shows what each of these originally is inside the digital-advertising industry — artefacts that SSPs, DSPs, DMPs, ad exchanges, IAB transparency standards and conversion-tracking pixels manufacture for their own commercial purposes, artefacts that are deliberately public or impossible to erase.
Put the two columns side by side and a recurring structure emerges: to make money flow correctly, conversions count correctly, and the supply chain auditable by buyers, the advertising ecosystem laid a whole machine-readable layer of identifiers and declaration files across pages and protocols. The operators of scam and coordinated networks monetise inside this very ecosystem, and therefore have no choice but to carry the same set of identifiers. kwara's job, in essence, is to sample at a few fixed junctions of this advertising machine, then aggregate identical identifiers across domains into evidence of a common operator.
Programmatic advertising is a supply chain that runs from the advertiser (where the money starts) through buy-side technology, the marketplace and sell-side technology, down to the publisher (where the money ends), with every intermediary taking its cut [1][2]. The diagram below flattens that chain and marks, in amber, where kwara samples. The key observation: kwara never needs access to the inside of an exchange or SSP — it samples only at the end visible to the user's browser (URL, HTML, third-party requests) and in the declaration files publishers publish themselves (ads.txt), and can still infer the account identities at the other end of the supply chain.
This is the heart of the document. Each row is one kwara forensic target, mapped to its original identity in the ad industry, the industry's reason for building it, and what kwara reads from it. De-anonymisation strength follows kwara's own tiering (docs/analysis-design.md, section 1).
| kwara forensic target (function) | Signal layer | Corresponding ad-industry entity / standard | Why the industry built it | What kwara reveals | De-anonymisation |
|---|---|---|---|---|---|
redirect topologyscanner.scan_url() | Network layer | Shortlink / wrapper redirection | Routes clicks to the landing page | Redirect topology | Low |
IP / ASN / hosting providerasn_clusters() | Host layer | Hosting / CDN | Hosts the site | Co-located infrastructure | Low |
| WHOIS / registrar (domain sub-tab) | Domain layer | Domain Registrar | Domain registration | Registration fingerprint | Low |
TLS issuer/serial/SANshared_certificates() | Certificate layer | CA + Certificate Transparency | HTTPS trust root | Same server / same deployment batch | Medium |
utm_* / fbclid / gclidshared_params() · shared_param_keys() | URL parameter layer | Click ID + UTM (DSP / ad-account attribution) | Attributes clicks to a campaign | Same campaign / same backend | Medium |
Cross-domain redirect renamingwrapper_relationships() | Wrapper layer | Affiliate / redirect wrapping | Affiliate routing and destination hiding | Same chaining structure | Medium |
Shared third-party endpointsshared_endpoints() | HAR endpoint layer | Third-party trackers / backend APIs | Collects behavioural data | Same backend / direct-IP CDN bypass | Medium-high |
Pixel ID / GA / AdSenseshared_tracking_ids() · fingerprints | HTML pixel layer | Meta Pixel / GA4 / GTM / AdSense / TikTok… | Conversion tracking + monetisation | Same monetisation / measurement account | High |
URL+HTML cross-confirmationad_tracking_platforms() (both) | Cross-source merge | Click ID × pixel dual evidence | — | Independent confirmation (strong) | High |
ads.txt DIRECT / byte-identicalshared_ad_accounts() · adstxt.py | Monetisation declaration layer | IAB ads.txt / sellers.json / SupplyChain object | Supply-chain transparency, anti-domain-spoofing | Same payee / same template | High |
Origin leaks / fake versionsheader_analysis.py(4 functions) | HTTP header forensics layer | Origin server / WAF (behind CDN) | Service deployment | Origin disclosure / same template / active fabrication | High |
with-param vs strippedcloaking.detect_cloaking() | Cloaking layer | Ad / SEO fraud cloaking | Hides real content from reviewers | Active intent to evade scrutiny | Highest |
lightweight vs Playwrightopsec.compute_opsec_profile() | OPSEC path layer | WAF / bot gating | Blocks crawlers and review tools | Same WAF deployment template | Medium-high |
Account × content / temporal distributionaccount_content_matrix() | Behaviour layer | — (synthesised from the signals above) | — | Coordination distribution (no flag asserted) | Synthesised |
Shading the table by de-anonymisation strength mirrors how kwara's evidence chain climbs: the further down the table, the harder a signal is for the operator to erase without giving up monetisation itself.
When a user clicks an ad, the ad platform automatically appends a click ID to the landing-page URL — Google's gclid, Meta's fbclid, TikTok's ttclid, Microsoft's msclkid [12][14][16]. This parameter is the bridge between the ad side (campaign / ad group / bid) and the site side (the pixel's conversion events). The pixel on the landing page captures the click ID and writes it into a first-party cookie (e.g. Meta's _fbc), then reports back through the Conversions API so the conversion is attributed to the correct ad account [15]. utm_* is the human-readable labelling left over from Google's 2005 acquisition of Urchin [17].
kwara's param_attribution.py ships this entire click-ID / UTM lookup table as built-in constants: identify_param() maps utm_* to Google Analytics, fbclid to Meta, gclid to Google Ads. On top of it sit two deliberately separate aggregation functions:
shared_params(key, value) — same campaign: two posts carrying the same value (e.g. ?utm_term=145) = the same campaign. Overlong opaque tokens (JWTs, encrypted affiliate IDs) are compared by SHA-256 prefix instead.shared_param_keys(key, *) — same backend: the operator hands every victim a unique ?aff_id=A1, A2, A3… — every value appears exactly once, yet the existence of the key itself points to a single backend system. Three thresholds (minimum posts, minimum distinct values, maximum domains) filter out generic keys like ?q=.
Technically, a “pixel” or “tag” is a piece of JavaScript plus a network beacon (a tiny request used to send data home); the name “pixel” is a relic of the early 1×1 transparent image, which today survives only as a <noscript> fallback [18]. The forensically crucial property: account / property identifiers are hard-coded as string literals inside the initialisation call, with no obfuscation — the vendor's script must be able to read them, so anyone who views source can read them too:
fbq('init', '<pixel-id>') — bound to a specific Meta ad account in Events Manager [18]gtag('config', 'G-XXXXXXXXXX'); in legacy UA UA-XXXXXXX-X the middle segment is the account number, shared across every property of one owner [19]AW-XXXXXXXXX (unique per Google Ads account); AdSense: ca-pub- + 16 digits, written in ads.txt as pub-, i.e. the publisher account that receives the money [20]ttq.load('<id>'); LINE: _lt('init', {tagId:'…'})
kwara's fingerprints.extract_tracking_ids extracts by pure regex, no JS engine, currently covering 11 platforms. Two review-hardened design fixes matter: (1) context-anchoring — every pattern requires the ID to appear inside a real invocation (an gtag/fbq/ttq call or a vendor host URL), to avoid catching documentation examples; (2) placeholder filtering — dropping placeholder strings like G-XXXXXXXX. The aggregation function shared_tracking_ids admits a cluster when the same (platform, ID) spans ≥2 domains.
ca-pub- is a payee account; Meta Pixel and AW- bind billing and conversion attribution — the accumulated conversion history, optimisation learning, audience lists and payment relationships all hang off that account. Rotating a domain is free and instant; rotating an account forfeits all of it. Operators therefore rotate domains far more often than IDs — so IDs outlive domains and betray the cluster.
ads.txt (Authorized Digital Sellers) is a plain-text file introduced by IAB Tech Lab in June 2017 to combat domain spoofing and counterfeit inventory; the publisher places it at the domain root, declaring line by line who is authorised to sell its ad inventory[23][24]. Four fields per line:
| Field | Content | Forensic meaning |
|---|---|---|
| ① Ad system domain | Canonical domain of the SSP / exchange (e.g. google.com) | Which SSP is selling this inventory |
| ② Publisher account ID (seller_id) | The publisher's account identifier inside that SSP | Ties the site to one account on a named SSP |
| ③ Relationship | DIRECT or RESELLER | DIRECT = the site owner is paid directly; RESELLER = a third party is authorised to resell |
| ④ Certification authority ID (optional) | TAG ID | Auxiliary verification |
ads.txt alone gives only anonymous account IDs; the other half is sellers.json — published by each SSP at its own root, resolving seller_id back to named entities (name + domain + seller_type) [25][8]. Combine the two and an investigator goes: domain → ads.txt authorisation list → (per SSP) sellers.json reverse lookup → real payee identity. At runtime there is also the OpenRTB SupplyChain object recording the intermediary path hop by hop (asi/sid aligned with sellers.json)[26]. ads.txt 1.1 (2022) adds the OWNERDOMAIN/MANAGERDOMAIN self-declaration variables.
(seller_id, exchange) pairs and found 79k dark pools, of which ~8,700 contain at least one misinformation site [27]; Papadogiannakis et al. (2023) built AdSparency over 7M sites and measured ~15k pools [28].
kwara's adstxt.py fetches, for every landing domain, /ads.txt, yielding two operator signals: (1) DIRECT accounts shared across domains (frequency weighting required — the few accounts shared by every domain are common monetisation intermediaries, not one operator); (2) byte-identical ads.txt (same raw_sha256) = a shared template, the strongest operator signal. By design ads.txt is harder evidence than GA4 sharing: GA4 is an unconscious tracking leak, whereas ads.txt is a deliberate monetisation declaration. Even 403s (some cloakers WAF-block the ads.txt path too) are recorded rather than dropped — the status itself is a cloaking-adjacent OPSEC signal.
While screenshotting a landing page, Playwright also records every third-party request domain the page makes (the core data of an HTTP Archive / HAR). shared_endpoints() surfaces non-allowlisted third-party endpoints called by ≥2 landing domains — typically the operator's backend API or self-hosted analytics server. Behind this capability stands a whole academic web-measurement infrastructure: Englehardt & Narayanan's million-site measurement (OpenWPM, CCS 2016)[29], and the tracker→parent-company entity-resolution databases of WhoTracks.Me and Disconnect [30].
config.HAR_NOISE_HOSTS first filters out generic CDNs / SDKs; is_direct_ip floats requests that hit an IP rather than a hostname to the top — hard proof of a scam page bypassing its CDN to reach the origin backend directly. The lightweight-fetch path contributes no HAR; only Playwright screenshots record one.Beyond advertising identifiers, a network can be clustered through the infrastructure that carries its traffic. Certificate Transparency (CT) is the public, append-only log of every certificate issued by trusted CAs, published in near real time — letting analysts discover and pivot to newly issued domains [31]. Threat-intel pivoting techniques include shared issuer+serial, overlapping SANs (one cert covering multiple related domains) and issuance-time patterns, combined with passive DNS and ASN co-location for graph clustering.
shared_certificates() emits two cluster types at once: by_cert (same issuer+serial = hard proof of the same server) and by_issuance (batch issuance within a 24-hour window = the same automated deployment pipeline). E.g. several landing domains each receiving a certificate from the same CA within a few hours is exactly the pattern by_issuance catches.
This layer is not advertising-specific; it is the traces left by the servers that carry the ad pages. The scanner stores the full response headers of every redirect hop in redirect_hops.response_headers_json, header_analysis.py provides four orthogonal views. The key: origin response headers are not scrubbed by the Cloudflare proxy, so this signal sees through CDN cloaking:
per_domain_constants: headers that always appear, with stable values, across repeated scans of a domain. For instance a domain that keeps returning a non-standard custom header (such as x-server-hosted: …) — exposing the real origin behind Cloudflare.cross_domain_shared_template: multiple domains carrying the same (anomalous, non-default) header values = one server deployment template, evidentiary weight on par with GA4 sharing. detect_fake_versions: matched against a table of known non-existent versions (Apache 2.5.1, PHP 9.x…); a hit means the operator is actively fabricating the fingerprint — an active anti-detection signal.cookie_origin_signals: Set-Cookie; Domain=.realorigin leaks the origin; flag-setting consistency is itself a cross-domain fingerprint.Cloaking — serving crawlers / reviewers (Googlebot, ad-platform review bots, Facebook's crawler) different content from what real users see — is how scam sites and made-for-advertising (MFA) operators dodge search and ad review at once, and detecting it is itself an attribution signal. The academic foundation: Wang & Savage's Cloak and Dagger (CCS 2011) defined the taxonomy of user-agent / redirection / IP cloaking [32]; Google's Cloak of Visibility (IEEE S&P 2016) found 11.7% of top-100 Google search results led to cloaked content [33]. The common core of every detection method is cross-view comparison: fetch the same URL repeatedly with different UA / referrer / IP, follow every redirect hop, and flag whichever content or redirect chain diverges.
cloaking.detect_cloaking() turns the academic cross-view method into a “with-params vs stripped” dual-fetch comparison. The stripped body is also persisted as a cloaking_alt snapshot for later SEO-side analysis. A single domain flagged cloaking_suspect is already enough to rule that the operator deliberately evades investigation.
OPSEC path layer (opsec.compute_opsec_profile) is the same logic from another angle: compare the success rates of the lightweight HTTP fetch versus the Playwright capture path. “Blocks plain UA but admits Chromium” is a specific WAF deployment template; multiple domains in one case sharing the same OPSEC level, independently of GA4 / TLS / parameter signals, points to the same operator. E.g. within one case some domains block both capture paths while others admit both — a split independent of GA4 / TLS signals, yet pointing to the same operator boundary.
The entire document collapses into one principle: identifiers that exist to earn or to measure (AdSense ca-pub-, analytics IDs, ads.txt account IDs) and infrastructure that exists to carry traffic (certs, IPs, ASNs) both carry a high rotation OPSEC cost — a new AdSense account must pass review again, a new certificate leaves a fresh pivot point in CT, and splitting tracking across accounts wrecks the operator's own revenue and analytics. Threat-intel practice keeps finding that operations are rarely unmasked by exotic detection; they are unmasked by identifier / infrastructure reuse and overlooked metadata [34].
This is the design motive behind kwara's ad_tracking_platforms() and its signal_source status. For every platform it inspects URL-parameter evidence and HTML-pixel evidence side by side, yielding four states, and both must require the URL and HTML evidence to intersect on the same ua_id or domain — this is the watershed between “both signal types present” and “the two signals cross-confirm”, and it is a contract that must be kept. Mislabelling the former as the latter is the UI lying to the analyst.
UA-XXXXXXX-X IDs cluster directly on the middle account number; GA4's G- and GTM lack an owner-revealing suffix and are less catalogued, pushing researchers toward Wayback history and other signals [21]. capture_status='ok' and non-empty), never simply the newest — otherwise one re-capture that failed a Cloudflare challenge would silently bury earlier good evidence. Applies to shared_tracking_ids, ad_tracking_platforms, shared_endpoints. The previous eleven sections in one sentence: kwara's value lies in no single signal, but in stacking the mutually independent “sticky” traces of the ad ecosystem, cross-confirming them, and packaging the result as evidence reproducible without trusting the tool or the analyst.Any single sampling point — a pixel ID, an ads.txt DIRECT line, a certificate — other tools can look up too. What is genuinely hard to replicate is joining all six layers into a single case's attribution while holding both lines at once: no false accusation, and full reproducibility.
both The contract requires URL evidence and HTML-pixel evidence to intersect on the same ua_id or domain before anything is promoted to “independently confirmed” — the line that separates “both signal types present” from “the two signals corroborate each other”.both, frequency weighting and confidence labelling, finally packaged into a reproducible evidence pack.| Dimension | kwara | Reverse-lookup tools SpyOnWeb/reverse-analytics | Ad-fraud vendors DoubleVerify/HUMAN/Adalytics | Academic crawlers OpenWPM/AdSparency |
|---|---|---|---|---|
| Signal coverage | All layers: URL / pixel / ads.txt / HAR / certificate / header / cloaking | Mostly single (GA / AdSense ID lookup) | Mainly supply chain & brand safety | Study-dependent, usually one dimension |
| Cross-source cross-confirmation | Yes (both contract) | No | Partial (internal, not exposed) | Study-dependent |
| Active evasive intent cloaking/OPSEC | Yes | No | Partial (detection-focused) | Few studies |
| False-positive protection | Yes(frequency weighting + false-flag caveats) | Weak | Commercial black box | Study-dependent |
| Per-case reproducible evidence pack | Yes(reproducible ZIP) | No | Commercial reports, not per-case reproducible | Paper datasets, not case-oriented |
| Design orientation | Single-case attribution, fit for formal submission | Provides leads | Industry-scale commercial detection | Academic measurement |
tmax, completing in ~100 ms.[9]The ad-industry side of this study was gathered by the randomfindings deep-research tool along four parallel tracks (supply-chain roles / IAB standards / tracking pixels / forensic precedents); below is the deduplicated merged list. kwara-side facts are cited from in-workspace code such as docs/analysis-design.md and adstxt.py, param_attribution.py, fingerprints.py, and are not listed separately.
Editor's note: two official IAB PDFs (ads.txt 1.1 and the sellers.json final spec) returned binary / decoding errors during automated retrieval; verbatim legal wording was cross-corroborated against high-consistency secondary sources (Equativ, Index Exchange, Google Ad Manager Help) — re-check the official PDFs before quoting exact wording. buyers.json / DemandChain (the demand-side counterpart) had no authoritative spec page findable this round and limited adoption, so it was left out of the main text.