Skip to content
Botscent

The trust model

Which verdict you can use to decide access, and what a verified signature proves.

The trust model says which verdict you can use to decide access. Only the request's own verdict, checked with isVerified, is fit for that.

app/api/checkout/route.ts
import { inspect, isVerified } from 'botscent/server'

export async function POST(request: Request) {
  const own = await inspect(request)
  const verified = isVerified(own, 'chatgpt')
  // => true only for a signature verified against ChatGPT's bundled keys
  return Response.json({ verified })
}

Which verdict to use for what

VerdictWhere Botscent computes itUse it for
The request's own verdict, with isVerified trueOn your server, from a verified signature or the platform's verified-bot fieldAccess
The request's own verdict, with isVerified falseOn your server, from declarations and product shapes that anyone can produceMeasurement and adapting what you show
The page verdictIn the visitor's browserMeasurement and adapting what you show
A page reportIn the visitor's browser, sent by the page's own scriptsMeasurement and adapting what you show
A verdict from combinePartly in the visitor's browserMeasurement and adapting what you show

The page verdict, a page report and a joined verdict all come from the visitor's browser. Scripts on the page can change them. So none of them is fit to decide access.

What isVerified checks

isVerified has two forms:

  • isVerified(verdict) is true when reasons holds signer.web-bot-auth.verified or signer.edge-verified-bot, without the page. prefix. Only inspect gives those reasons.
  • isVerified(verdict, name) is true when reasons holds signer.web-bot-auth.verified, agent_name is name, and no reason has the page. prefix.

In Python, the function is botscent.is_verified, with the same two forms.

The platform's verified-bot field never passes the check with a name. The field says that some agent was verified, not which agent. So isVerified(verdict) && verdict.agent_name === 'chatgpt' can join the platform's check of one agent with a name that the agent only declared.

Warning: Do not use agent_name or a reason string to allow access. Anyone can send the headers that produce them. Use isVerified(verdict, 'chatgpt') for one agent.

What a verified signature proves

A verified signature proves that the agent operator's infrastructure signed the request. It does not prove who the person is, or which model runs the session.

Each signature has a time window, from 5 s before its created time to its expires time. Someone who captures a signed request can replay the signature to the same host within that window.

Keys are fixed in each release

The server half makes no request to fetch keys. Each release bundles the signers' keys as the maintainers fetched them for that release. The release notes give the date of that fetch.

So a signer's key changes reach you only when you upgrade:

  • A key that a signer adds after the release gives signer.web-bot-auth.declared until you upgrade.
  • A key that a signer removes, because it rotated or leaked, stays verified until you upgrade. Nothing in a deployed copy can revoke it.

If you allow access with isVerified, keep the package current. The age of your release limits how old your keys can be. A scheduled job in the repository opens a pull request when a signer's published keys change.

What no verdict tells you

  • No verdict proves that a person is present. human means only that Botscent saw no agent evidence.
  • No verdict describes the person behind an agent.
  • No verdict says who performed a particular action.
  • Botscent does not authorize anything. Your code decides what a verified agent can do.