Measure what a website serves to machine readers.
People keep saying that sites treat signed agents differently, or that they hide extra text for models. This tool is for anyone who wants those claims checked from the outside. Point it at a URL. It fetches the page as several honest identities and prints what each one received. It does not grade the site. The check report also prints what each identity costs a machine reader after extraction (cl100k_base), how much of that is not body, and the raw response size as a separate bandwidth figure. When a measurement from this run points at a follow-up, the report names it under next.
A live check of firecrawl.dev, with a published signing key:
content-parity check https://firecrawl.dev/ -key ~/.content-parity/signing.pem -agent-origin https://zulwatha.dev -db measure.sqlite
https://firecrawl.dev/
structural finding: sr-only-prose; signed vs unsigned content differs
cost (cl100k_base; extracted text; not a grade)
unsigned 4695 tokens body 3956 gap 739
unsigned 1203241 bytes raw
unsigned_b 4695 tokens body 3956 gap 739
unsigned_b 1203241 bytes raw
unsigned_c 4695 tokens body 3959 gap 736
unsigned_c 1203244 bytes raw
signed 4695 tokens body 3959 gap 736
signed 1203241 bytes raw
markdown 4695 tokens body 3959 gap 736
markdown 1203244 bytes raw
browser 4668 tokens body 3925 gap 743
browser 1256579 bytes raw
nav/header 148 footer 89 boilerplate 502
signed vs unsigned content: 4 text diffs
altered kind=text sim=1.0000 left=111 right=112
altered kind=text sim=1.0000 left=112 right=114
missing text: 50 ms
missing text: 50 ms
structural findings: 1
hidden-style sr-only-prose off=749081 len=57 If you are an AI agent, LLM, or automated system, use the Firecrawl onbo…
content-parity body -db measure.sqlite -domain firecrawl.dev -page https://firecrawl.dev/ -identity unsigned
reader-addressed: 1
reader-addressed if-you-are-ai off=657513 len=16 If you are an AI
content-parity body -db measure.sqlite -domain firecrawl.dev -page https://firecrawl.dev/ -identity unsigned
next
no machine-facing variant this run (markdown identity, same type as HTML). body-only cost 3956 tokens (unsigned body); served 4695 (unsigned extracted total); difference 739
boilerplate is 502 of 4695 extracted tokens (share 0.1069; unsigned breakdown)
structural finding sr-only-prose; stored evidence: content-parity body -db measure.sqlite -domain firecrawl.dev -page https://firecrawl.dev/ -identity unsigned
page home (given-url)
stable core 428 blocks coverage 0.9885
identities
unsigned served 200 433 blocks
unsigned_b served 200 433 blocks
unsigned_c served 200 433 blocks
signed served 200 433 blocks
markdown served 200 433 blocks
browser served 200 428 blocks measured, not compared
other comparisons
signed vs unsigned challenge: none
signed vs unsigned redirect: none
signed vs unsigned link targets: none
evidence
stored database: measure.sqlite
stored bodies: measure.bodies
content-parity body -db measure.sqlite -domain firecrawl.dev -page https://firecrawl.dev/ -identity unsigned
content-parity body -db measure.sqlite -domain firecrawl.dev -page https://firecrawl.dev/ -identity signed
The scan found a 1px, opacity:0 span that tells AI agents to follow onboarding files. A person looking at the page does not see that span. Naming this host is a choice. The page is public. The finding is text the site published in its own HTML. The tool reports the behavior, not an intent: text hidden from a human that addresses a reading system is reported regardless of why it was placed there.
curl -fsSL https://raw.githubusercontent.com/Zulwatha/content-parity/v0.2.1/scripts/install.sh | sh
docker build -t content-parity .
go install github.com/Zulwatha/content-parity/cmd/content-parity@v0.2.1
content-parity check https://firecrawl.dev/
Chrome is optional. Signed mode needs a key and a published directory. Without them, signed is recorded as unavailable and the other identities still run.
- The same GET with and without RFC 9421 / web-bot-auth signature headers. Content, final URL, challenge treatment. Nothing else in the open does this.
- Whether
Accept: text/markdownreceives a real variant, whether that variant is missingVary: Accept, and whether it dropped HTML body content. Navigation chrome and code samples do not count as lost content. - Whether stored HTML contains text the detector can defend as planted for machines.
- Token cost after extraction, under the
cl100k_baseencoding. Chrome versus body applies only to that count. Raw HTML size is bandwidth, not tokens.
Robots.txt, llms.txt, and markdown alternate links are recorded as explanation. They are never scored.
No model calls. No API key. No telemetry. Nothing leaves the machine except the request to the URL you named, and, in signed mode, the fetch of the key directory you publish.
No scores. No grades. Findings do not fail a run unless you set -fail-on.
The injection scan errs toward silence. An empty result means nothing could be defended, not that the page is clean.
We measured claims that are widely repeated and rarely tested. 300 public sites.
What a machine reader pays is a number on every page that received content. Median extracted tokens 2592 (cl100k_base). Share that is not body content 0.5081. Chrome share 0.2238. The seven markdown variants have median 962.
Then the measurements that are mostly no. Signed identity rarely changed the page. Content differed on 9/239 comparable hosts after subtracting per-site noise. The run recorded four signed-versus-unsigned challenge differences (4/265). A later re-fetch reproduced three. The published figure is 3/265. A finding that does not reproduce is not reported as one.
Planted machine-only text was uncommon. 1/281 scanned pages had a structural finding. That page is firecrawl.dev, named above. The earlier 1/273 figure used a smaller page set that skipped uncertain observations.
Markdown variants were scarce. Seven hosts served one. Lost body content is 3/7. Missing Vary: Accept is 6/7. Those are separate findings. The earlier combined 6/7 figure counted chrome as lost content and was corrected. The same seven stored variants were classified again after excluding code samples; the figure did not change.
The first cost figures counted raw HTML. Those figures are withdrawn. See docs/results.md.
The numbers, and how they were produced, are in docs/results.md and docs/methodology.md.
content-parity check https://example.com/
content-parity check https://example.com/ -json
content-parity check https://example.com/ -html report.html
content-parity check -sitemap https://example.com/sitemap.xml
content-parity check https://example.com/ -fail-on signed-content,structural-inject
-fail-on accepts signed-content, signed-challenge, signed-redirect, signed-link, structural-inject, addressed, markdown-fault, markdown-vary, and unstable.
JSON is for machines. The HTML report is one file with embedded CSS.
In GitHub Actions:
- uses: Zulwatha/content-parity/.github/actions/content-parity@v0.2.1
with:
url: https://example.com/
fail-on: signed-content,structural-injectCorpus commands, signed-mode setup, and the signer library are in docs/methodology.md, deploy/README.md, and signing/README.md.
Edge dashboards report on your own property from inside your own edge. This measures any site from the outside, independently. It shows evidence. It does not assign a grade.
Issues and pull requests are welcome. Run go test ./... before you send one. Tests use committed fixture servers. They do not call the live network.
MIT.