Reference
This page lists what PXTA looks for, how it rates what it finds, and every option. The same reference is in the README on GitHub.
The Three Passes
When a site shows a consent banner, PXTA loads every page three times, with a fresh browser profile for each pass:
| Pass | What PXTA does first | A tracker sending data here means |
|---|---|---|
| Banner not answered | Nothing: the banner is left alone | it fired before consent |
| After reject | Rejects on the start page | it ignored the visitor’s “no” |
| After accept | Accepts on the start page | this is what the site sends with consent |
With no banner, each page is loaded once, and the report says the site never asked.
On each page PXTA waits until two seconds pass without a new request (at most 15 seconds), scrolls to the bottom, types the marker address [email protected] into email fields without submitting, then fires the events a page gets when the visitor leaves and records the beacons they set off. Requests from iframes and workers are included.
Consent Banners
| Platform | How PXTA finds it | How PXTA answers |
|---|---|---|
| OneTrust | OneTrust object, #onetrust-banner-sdk |
the Reject All or Accept All button, else RejectAll() / AllowAll() |
| Cookiebot | Cookiebot object, #CybotCookiebotDialog |
the Deny or Allow all button, else submitCustomConsent() |
| Didomi | Didomi object, #didomi-host |
the Disagree or Agree button, else setUserDisagreeToAll() / setUserAgreeToAll() |
| Usercentrics | UC_UI object, #usercentrics-root |
the Deny or Accept All button, else denyAllConsents() / acceptAllConsents() |
| Any other | buttons that say “Reject all”, “Decline”, “Only necessary”, “Alle ablehnen”, “Tout refuser” and similar, next to text about cookies or consent | a click on that button |
Banners are found in the page, in open shadow roots and inside iframes, and PXTA waits up to six seconds for one to appear. A banner inside a closed shadow root is found by what it covers on screen and answered through its platform’s API. After answering, PXTA checks the banner went away. If it can’t reject or accept, the report says so and that pass is left out.
Trackers
| Tracker | Collection endpoint |
|---|---|
| Meta Pixel | facebook.com/tr |
| Google Analytics 4 | google-analytics.com/g/collect, analytics.google.com/g/collect |
| Google Ads conversion tag | googleads.g.doubleclick.net/pagead/viewthroughconversion/, googleadservices.com/pagead/conversion/, www.google.com/pagead/1p-conversion/ |
| Google Tag Manager | www.googletagmanager.com/gtm.js (a loader, listed when present) |
| TikTok Pixel | analytics.tiktok.com/api/v2/pixel |
| LinkedIn Insight Tag | px.ads.linkedin.com/collect, /wa/ |
| Microsoft Advertising UET | bat.bing.com/action/ |
| Pinterest Tag | ct.pinterest.com/v3/, /user/ |
| Snap Pixel | tr.snapchat.com/p |
| X (Twitter) Pixel | analytics.twitter.com/i/adsct, t.co/i/adsct |
A third-party request that isn’t in the catalog still appears, as “unclassified”, when it looks like a beacon or carries personal data. Each catalog entry is a small JSON file in catalog/; --catalog folder adds your own.
What Gets Decoded
Every request is unpacked before the rules run: query strings, form bodies, JSON bodies, several events in one body, a URL inside a parameter (and that URL’s own parameters), JSON inside a parameter, base64 text, and up to three rounds of percent-encoding.
The Rules
| Rule | Finds |
|---|---|
| Email addresses | plain, URL-encoded, double-encoded, base64, inside JSON, inside the page address |
| Email hashes | SHA-256 and MD5 of addresses PXTA knows: those you pass with --email, its marker address, and addresses shown on the site or seen in other requests |
| Hashed customer fields | fields the tag labels as a hashed email, phone or name, such as Meta’s advanced matching or Google’s enhanced conversions |
| Phone numbers and names | in fields named for them |
| User IDs | the site’s own user ID, such as Google Analytics uid |
| Search terms | what the visitor searched for on the site, found anywhere in a tracker request |
| Sensitive pages | the address or title of a page about health, money trouble, or a checkout or account |
| Consent Mode | Google’s gcs value: granted before an answer or after a reject, or denied while personal data still goes out |
Findings and Severity
The codes are stable: a code is never renamed, so scripts can rely on them.
| Code | Severity | Meaning |
|---|---|---|
personal-data |
high, low after accept | a tracker request carried personal data |
sensitive-page |
high, low after accept | a tracker got the address or title of a sensitive page |
search-term |
high, low after accept | a tracker got what the visitor searched for |
consent-mode-granted |
high after reject, medium before an answer | Google tags reported consent as granted |
consent-mode-mismatch |
high, low after accept | Consent Mode said denied, and personal data went out anyway |
tracker-after-reject |
medium | a tracker sent data after the visitor rejected |
tracker-before-consent |
medium | a tracker sent data before the banner was answered, or on a site with no banner |
banner-not-handled |
for information | PXTA couldn’t answer the banner, so a pass is missing |
tracker-present |
for information | a tracker sent data from these pages |
The severities are fixed and written down so they can be argued with. They aren’t a legal judgment: PXTA records traffic, and whether that traffic breaks a law is for a lawyer to say.
Options
| Option | Default | What it does |
|---|---|---|
--out |
pxta-report |
folder for report.html, findings.json and requests.har |
--pages |
20 | most pages to visit |
--depth |
3 | links deep from the start page |
--sitemap |
sitemap address to add pages from | |
--passes |
none,reject,accept |
which consent passes to report; the unanswered pass always runs, since it finds the pages |
--email |
an address whose hashes to look for (repeatable) | |
--no-fill |
off | don’t type the marker address into email fields |
--wait |
15s | longest time on one page |
--idle |
2s | a page is done after this long without a new request |
--banner-wait |
6s | how long to look for a consent banner |
--delay |
1s | pause between page loads |
--ignore-robots |
off | visit pages robots.txt disallows, on your own site only |
--resume |
off | carry on an interrupted audit from --out |
--browser |
found automatically | Chrome, Chromium or Edge to use (or PXTA_BROWSER) |
--catalog |
folder of extra tracker catalog files | |
--quiet |
off | print only the summary |
pxta audit exits with 0 when the audit finished, whatever it found, 2 on an error, and 130 when it was interrupted. PXTA saves its progress after every page, so --resume picks up where an interrupted audit stopped, and it restarts the browser by itself if it crashes mid-crawl.
What PXTA Can’t See
- Data a site sends from its own servers, such as conversion APIs and server-side tag managers.
- Pages behind a login, and anything that happens only after a click PXTA doesn’t make.
- Trackers it doesn’t know, apart from the unclassified-beacon rule.