How Machine-Checkable Is IAB Privacy Signaling?
IAB privacy signaling is a transport: the checkable layer is string shape, not a person-fact
Digital advertising fails without throwing: a tag that bills and renders nothing, identifiers in a pixel nobody reviewed, a payload that cannot be told from fraud, an agent that transacts with nothing checking it. IAB Tech Lab and AAO specifications cover some of that. The rest is outside the PDF. The work here is open-source tools and measurements for those failures, including the places the documents do not reach.
Every arrow is a boundary between two companies. RTBlint and VASTlint sit on the payload as it crosses. Pixellint sits on the measurement artifact before it fires. Each box links to the source that does it.
The specifications exist and are public. What is missing is a check at the moment a payload crosses between two companies. Some of that check is in the IAB Tech Lab and AAO documents. A lot of it is not. That gap is where revenue leaks, where user data travels unreviewed, where fraud becomes indistinguishable from error, and where autonomous systems now transact with nothing verifying them. Every tool here is free, auditable, cites the document behind each finding, and is built to run inside that gap.
A tag that fails renders nothing and still bills. The rules that catch it are the unglamorous ones: a missing impression element, a duplicate one that double-counts, a media file on plain HTTP that secure inventory blocks, quartile tracking nobody wired so measurement receives no signal at all.
Tracking URLs are where identifiers actually travel, and they are the least reviewed artifact in the chain. Pixellint flags raw email addresses sent where the vendor requires SHA-256 hashing, credentials embedded in a URL, macros fired unexpanded to a third party, and any tracker still riding plaintext HTTP.
A malformed payload and a fraudulent one look identical to everything downstream, because both are unverifiable. These tools do not detect fraud. They remove the ambiguity fraud hides in, by checking that the verification resources meant to catch it are present, correctly declared, and reachable.
Buying is moving to agents on both sides of the transaction, with no human reading a validator UI in between. Every tool ships an MCP server so an agent can check its own output deterministically, and VASTlint implements AdCP 3.0 governance so a pipeline can declare and enforce content standards rather than assume them.
Each chip is a real rule or tool id you can look up in the catalog. Nothing on this page describes something that is not shipping.
Five preprints. Four ask what the documents leave open: how much of OpenRTB any validator can enforce, what header-bidding traffic actually sends, whether structural VAST validation is cheap enough for a live bid path, and how much of IAB privacy signaling a receiver can check. The fifth measures the CTV verification architecture that makes fraud cheap to run. Datasets and code are published with them.
IAB privacy signaling is a transport: the checkable layer is string shape, not a person-fact
91.5% of pricing-relevant claims a buyer receives about a CTV server-side impression are self-declared, against 70.0% for the same claims on web display; no scored OpenRTB field on either path is attested
42.0% of bid requests on a random header-bidding sample are flagged on type, enumerated-value, or required-field checks, 95% CI [36.0, 47.2]; major publishers sit at 40.4% [35.8, 45.0]; the intervals overlap
417 normative sentences extracted from OpenRTB 2.6-202606 and 3.0, screened to 288 conformance statements and hand-coded into four enforceability classes
A 17 KB CTV tag validates in 350 microseconds, 0.35% of a 100 ms bid window; a 44 KB tag in 2.1 ms, inside a typical creative-selection budget
Each panel is a different artifact. Bid JSON, a wrapper chain into a CTV slot, a network request that has not fired yet. Without a check the failure is silent. With the validator it has a rule id.
A bid request with a protobuf boolean where the OpenRTB JSON snapshot wants 0 or 1 does not throw. The DSP drops it. Fill from that request is gone. RTBlint grades the payload against the spec revision the partner is on, before it enters the auction.
A wrapper chain that dies on an HTTP media file still counts an impression. The player reports nothing useful. VASTlint walks the chain at intake, names the hop, and the deterministic HTTPS fixes apply in one pass, before the tag is trafficked.
A misspelled event name or a raw email in a tracker looks healthy in the network panel. Pixellint checks the URL against the vendor's own docs before it fires, while the tag is still in ad ops, not after attribution has already failed.
| Query | Value | Vendor contract |
|---|---|---|
| id | 14288931 | ok |
| ev | Purchase | ok |
| em | unhashed | sent in the clearmust be SHA-256 |
No install, no account, no key. Paste a payload and it grades against the same engine the CLI and the MCP server run. Pixellint is CLI and local MCP while it is early.
Every figure below was read from a public registry API and can be checked against the same endpoint today. None of it is estimated, projected, or rounded up.
| Channel | Package | Count |
|---|---|---|
| npm | vastlint | 13,220 |
| npm | rtblint-core | 1,857 |
| npm | pixellint | 479 |
| RubyGems | vastlint | 3,859 |
| crates.io | vastlint-core | 5,913 |
| crates.io | vastlint-cli | 1,392 |
| crates.io | rtblint-core | 548 |
| crates.io | rtblint | 285 |
| crates.io | rtblint-mcp | 282 |
| crates.io | pixellint-core | 401 |
| crates.io | pixellint | 241 |
| crates.io | pixellint-mcp | 245 |
| Docker Hub | aleksuix/vastlint | 3,435 |
| GitHub releases | vastlint | 2,474 |
| GitHub releases | pixellint | 1 |
| GitHub releases | vastlint-erlang | 285 |
| PyPI | vastlint | 3,538 |
| VS Code Marketplace | aleksuix.vastlint | 516 |
| Total | 38,971 | |
How these are counted. Read straight from each registry’s public API on 2026-08-26. Nothing is estimated or projected. crates.io and RubyGems report lifetime downloads. npm has no lifetime endpoint, so that figure sums the daily range API from each package’s first publish date. PyPI uses the without_mirrors series, which excludes mirror traffic and is the smaller of the two figures available. Registries count machines, not people, so treat this as reach, not headcount.
An agent that assembles a campaign needs a deterministic verdict, not a screenshot of a validator UI. Every tool here ships an MCP server for that. 20 callable tools, structured JSON, no account.
Findings come back as structured JSON: a stable rule id, a severity, the line it came from, and fix guidance. An agent can act on that. It cannot act on a screenshot of a validator UI. Each server links to its source.
{
"mcpServers": {
"vastlint": { "type": "http", "url": "https://vastlint.org/mcp" },
"rtblint": { "type": "http", "url": "https://rtblint.org/mcp" },
"pixellint": { "command": "pixellint-mcp" }
}
}IAB Tech Lab and AAO specifications define part of the bid, the tag, and the pixel. Implementing those documents closely is one job. Measuring the failures they do not name is the other.
The specs that define how a bid, a creative, and a measurement event are structured. Every rule in the catalog names the document it came from.
The Ad Context Protocol defines how agents negotiate and transact advertising. Validation is the part that keeps an autonomous pipeline honest.
Implementing a specification closely enough to validate it surfaces the places where the specification itself is wrong, ambiguous, or untested. Those findings go back to the people who maintain it, as patches to the specs and their reference implementations.
A finding names the spec section, the XSD, the RFC, or the documented revenue impact behind it. If a rule cannot cite something, it does not ship. That is what makes a result arguable with a partner instead of just an opinion.
A single Rust core compiles to a CLI, a WASM module, a native library, and language bindings. Your CI, your browser, your BEAM service, and your agent all get identical verdicts, because they run identical code.
Apache 2.0, with a written commitment: no paid tier that hides rules, no cloud version that requires an account, no rate limits, no telemetry without opt-in. Validation infrastructure only works if everyone can run it.