Security

How PageVerified handles keys, data and requests.

This page describes what the service does today. It makes no claim to a certification, an external audit or an uptime guarantee.

API keys

  • A free key, or a key bought with card checkout, is generated from 24 random bytes and shown once, when it is issued.
  • Only a SHA-256 hash of a key is stored in the key table.
  • A key bought by an agent over HTTP 402 is derived from a server-side secret and that purchase's own values, so the buyer can collect it again for a limited time if the first response is lost.
  • When a client signs in with OAuth, its key is carried in encrypted form inside the sign-in code and the tokens issued to that client.
  • Keys are sent as Authorization: Bearer <key> over HTTPS.

What is stored

  • Tool calls. The receipt for a call holds the key, the tool, the credits, the time and whether it succeeded. The content of the request is not written to the receipt.
  • Captures. A capture stores the URL, the format, the captured file, its SHA-256 hash, its size and the time. Download links to a captured file are signed and expire after 24 hours. By default a capture has a public receipt page at /e/<id>: anyone who has the capture ID can open it and get a fresh download link. Send "public_receipt": false with the capture to turn that off.
  • IP addresses. Rate limits on free keys and directory submissions use a keyed one-way hash of the IP address, not the address itself.
  • Email. No email is collected to issue a free key. For a card purchase, Stripe passes the buyer's email to PageVerified, and it is stored with the purchase and the paid key. A key holder may also add one email address to a key, for one confirmation message and then notices about that key's credits. It is used for nothing else, never written to logs, and deleted on unsubscribe.
  • Cards. Card details go to Stripe only and never reach PageVerified.

The full table, with retention periods, is on the data handling page. The privacy policy covers personal information.

Guards on outbound requests

Capture and several tools fetch a URL that the caller supplies. Each of those requests is checked before it is made:

  • Only http and https URLs are accepted, and URLs that carry a username or password are refused.
  • The host must resolve to a public address. Private, loopback, link-local and other special-purpose ranges are refused, for IPv4 and IPv6, as are localhost, .local and .internal names.
  • Requests are made from Cloudflare's network, not from a private network that holds PageVerified's own systems.
  • When a tool follows a redirect, each hop is checked the same way.
  • During a capture, the requests the page makes are checked, WebSocket connections are closed and service workers are blocked. If the page itself was redirected to a private address, the capture is refused.
  • Tool fetches follow robots.txt for the PageVerifiedBot user agent and skip sites whose terms forbid automated access.

Signed Capture Records

A Capture Record is a small signed statement of what was captured: the URL, format, SHA-256 hash, size and capture time. Records are signed with EdDSA or ES256. The matching public keys are published as a JSON Web Key Set at /.well-known/pageverified-keys.json, and POST /api/v1/capture/record/verify checks a record against them.

The timestamp comes from PageVerified's own server clock. A record shows that PageVerified signed those bytes at that time. It is not a forensic chain of custody.

Outside timestamps are switched on. After a record is signed, its SHA-256 is sent to FreeTSA and CNX Timestamp Authority, and their signed answers are stored with the record. For a capture with a public receipt, the record's text and its signature are stored too. The page, the file and the URL are never sent to them. These are kept for 365 days, or until the capture is deleted if that is sooner.

Anyone can check a record, free and without a key, at /verify.

Credits and payments

  • Credits are reserved when a call starts. If the call fails, they are returned, so a failed call costs nothing.
  • A Stripe payment confirmation is accepted only after its signature is checked. A paid key is issued once per purchase.
  • If the database is not configured, requests that would write to it are refused with a 503 instead of being accepted and lost.

Transport and headers

The site is served over HTTPS with Strict-Transport-Security, X-Content-Type-Options: nosniff, X-Frame-Options: SAMEORIGIN and Referrer-Policy: strict-origin-when-cross-origin.

Service providers

  • Cloudflare runs the website, the API and the capture browser.
  • Supabase hosts the database and private file storage.
  • Stripe processes card payments.
  • Coinbase settles USDC payments made by agents, and public Base network nodes are used to confirm them.

Report a vulnerability

Email support@pageverified.com with what you found and how to reproduce it. Please do not access other people's data or disrupt the service while testing. PageVerified does not run a paid bug bounty.