Invalid traffic is not the only thing that gets an ad account suspended. The way your ads are set up is policy too. A misconfigured plugin that stacks two units in the same pixel space, an ad unit a theme hid with display:none, a header bidder refreshing a slot every few seconds, a stray casino keyword in user-generated content — any of these can trip an AdSense or Ad Exchange policy review. The Ad Policy Scanner is the part of PubSentry that watches for these ad-setup policy violations (ASPVs) on your live pages, grades them, and tells you exactly which element on which URL is the problem — before a network does it for you.
It runs inside the same single async tag. There is nothing extra to install:
<script async src="https://pubsentry.com/t.js" data-site="st_xxxx"></script>
Why setup violations are their own risk
Impression IVT and click abuse are about who is visiting. ASPVs are about how your inventory is configured, and they carry a distinct kind of risk:
- They are detectable by the network's own crawler. Google renders your page. If it sees an ad behind another ad, or an ad unit that is zero pixels tall, that is a finding it can act on without any traffic anomaly at all.
- They accumulate silently. A stacking bug introduced by a plugin update sits on ten thousand pages until something flags it. By then it looks deliberate.
- They are fixable. Unlike a sophisticated bot you can only score probabilistically, a hidden ad unit is a concrete element with a concrete selector. The scanner's job is to hand you that selector.
What the scanner checks
When an ad fires, the tag runs a point-in-time DOM scan of the live, rendered page and emits structured findings. Each finding carries a stable check_id, a category, a severity (critical / high / medium / low), a plain-English detail, and — where it can — a locator (the element id or a tag.class selector) so you can go straight to the offending node.
The built checks, grouped by category:
- Stacking (
critical). Pairwise overlap of ad slots. If two detected ad elements share meaningful pixel area, they are stacked — the single fastest way to draw a policy strike. - Hidden (
critical/high/medium). Ad units the page is rendering but the user can't see:display:none, near-zero size (under 2×2px),opacity:0, or positioned far off-screen. A hidden ad unit still requests and counts impressions — exactly the pattern networks treat as deceptive. - Density (
medium/low). Too many ad slots above the fold (four or more), or a very high total ad-unit count on the page (twelve or more). These are advisory, not bans — but they are the kind of thing a manual reviewer notices. - Placement (
medium). An ad slot rendered flush against clickable navigation or buttons, where a real reader's tap on a menu lands on the ad instead. Accidental-click risk is an explicit policy concern. - Refresh (
high). Tracked over the session, not in a single snapshot. A lightweight monitor watches ad-iframesrcchanges; if a slot refreshes faster than policy allows (more than once inside ~30 seconds), it flags abusive auto-refresh with the observed count. - Content (
medium/high). A word-level scan of the page text for AdSense-risky terms (a representative, extensible list — gambling, adult, counterfeit, "make money fast", and similar). It separately flags cloaked text: risky terms inside inline-hidden elements (display:none,visibility:hidden,font-size:0), which is the classic "show the crawler something different from the user" violation and is treated as the more serious case.
How the scanner identifies an ad slot is deliberately broad: AdSense ins.adsbygoogle, Google Publisher Tag div-gpt-* containers, ad iframes (matched by known ad-network src domains), and elements whose id/class match ad-slot patterns (ad-slot, ad-unit, gpt-ad, and friends).
Fail-open, like everything in the tag
The scan obeys the same hard rule as the rest of PubSentry: it can never throw into your page and never delay an ad. The whole routine is wrapped so any error returns whatever findings it has so far; layout-dependent checks simply no-op where the browser can't give geometry. A scanner bug will never break your page or cost you an impression. This is the same FPR=0-grade conservatism that governs the detection engine — the scanner reports, it does not block your ads or interfere with rendering.
From findings to a grade you can act on
Findings ride along on the same beacon as the impression event (only when there is something to report), get enriched and stored server-side, and aggregate into an Ad Policy scorecard in the dashboard.
Two design choices make that scorecard honest:
- Grading counts distinct problems, not occurrences. One stacking bug that appears on ten thousand pages is one problem to fix, not ten thousand violations. The grade (A–F) and 0–100 score are computed from distinct findings weighted by severity, so volume doesn't inflate the alarm.
- Any critical finding caps the grade at D. You cannot earn an A while a stacking or hidden-unit
criticalis open, no matter how clean everything else is.
The dashboard shows the letter grade, the score, a breakdown by category, and a findings table — each row with its name, severity, detail, the number of pages affected, when it was last seen, and the element locator. The same data feeds a policy_violation alert trigger (distinct high/critical findings over a window) so a new stacking bug from a plugin update can page you, and the Content Policy view lists risky/cloaked terms per URL.
Privacy
The scanner sends only what a finding needs: the check_id, category, severity, a short detail string, and an element selector. It reads your page's structure, not your visitors. As with every beacon, raw IP and User-Agent are never sent by the tag; where the server uses the request IP it is hashed with HMAC-SHA256 and then dropped, never stored in the clear.
Honest boundaries
- The catalog is the high-value, runtime-detectable subset — not "96 checks." Our reference catalog lists far more policy checks than are implemented; the scanner ships the DOM/runtime-detectable ones that matter most and is extensible by
check_id. We pin the real built counts and a test fails our build if a sales number ever drifts from what actually runs. The content word-list is representative, not exhaustive. - The scanner reports; it does not auto-fix. It hands you the exact element and severity. Remedying a stacking bug or removing a hidden unit is a change on your side (usually a theme or ad-plugin setting).
- Programmatic access is coming. The endpoints that feed the dashboard are session-scoped to your account, not a documented public integration. A stable public API and outbound webhooks for policy findings are on the roadmap (coming), not shipped. The
policy_violationalert is the supported way to get notified today.
The short version
Drop in the one tag and the Ad Policy Scanner reads every live page the way a network's crawler would — catching stacked, hidden, over-dense, badly-placed, abusively-refreshed, and policy-risky ad setups — then grades them by distinct severity and points you at the exact element to fix. It is account protection that runs ahead of the reviewer instead of after the strike, and it never touches your page's stability to do it.
