Skip to content

Device Attestation

The strongest thing a visitor can present is proof from someone else that they are a real person on a real device. WebDecoy consumes that proof and turns it into the attested-human trust level a route can require.

We consume attestation and never issue it. Running an attester would mean deciding who counts as a real person, which is exactly the business we say we are not in. For the evidence we generate ourselves, see Behavioral Signals.

Today that means Cloudflare Turnstile. Private Access Tokens are implemented but not yet available. See below.

If you already run Turnstile, a verified response earns that session the attested-human grade, the highest a wd_clearance token can carry. It works on every browser, which is why it is the practical route to that grade.

Enforcement → Setup (/enforcement/setup), step 2 Set up the clearance token, in the Turnstile attestation panel. Paste your Turnstile secret key and save. It sits with the token because it decides what grade a token can reach, not whether one is issued.

The secret is:

  • encrypted at rest, and never returned by any endpoint (not to you, not to support). The API reports only whether one is configured.
  • verified in the backend, never in the public issuance service, so it is never within reach of a browser-facing surface.

Clearing it is a single button, and costs nothing: sessions still reach human-likely on browser integrity and interaction cadence.

Pasting the Site key where the Secret key belongs is the most common Turnstile mistake, and its symptom is silence: attestation never happens, nobody reaches attested-human, and any route requiring that grade refuses every visitor with nothing on screen suggesting why.

So the panel reports only what is actually known, and never calls a stored string a verified one:

State Meaning
Not configured no secret stored
Stored, not checked a secret is stored, and whether Cloudflare still accepts it is unknown
Verified with Cloudflare the stored secret was probed and Cloudflare accepted it
Not working Cloudflare rejected the stored secret. Until it is fixed nobody can reach attested-human

A secret Cloudflare definitively rejects is refused at save, with what to fix. If Cloudflare is unreachable the secret is stored and the panel reports that the check did not run, because refusing a correct key over a network blip of ours would be the same false claim pointed the other way.

Check now re-probes live. Save-time verification cannot stay true, since a key can be rotated or deleted at Cloudflare and nothing tells us, so a live check beats a stored timestamp that would rot the same way.

Your widget produces a response token; it rides along with the clearance mint; the backend verifies it against Cloudflare’s siteverify using your secret; a success adds the turnstile evidence tag and grades the token attested-human.

Every failure path (bad token, unreachable Cloudflare, no secret configured) produces exactly the token the visitor would have received anyway. Attestation can raise a grade. It can never lower one or block a request.

Private Access Tokens (Privacy Pass, RFCs 9576–9578) let Apple platforms prove a request comes from a genuine device whose owner is in good standing, with no puzzle and no interaction at all. The visitor sees nothing.

The verification is built and tested (token parsing, blind-RSA signature checking, issuer key rotation, replay protection), but the feature is not yet usable, and we would rather say so than let you configure something that silently does nothing.

What’s missing: the challenge carries an origin_info field naming the origin where the token may be redeemed. A client whose origin does not match is entitled to ignore the challenge, and does so silently. Because one WebDecoy service issues challenges on behalf of every customer, that origin has to be registered per organization before it can be correct, which is the remaining work.

Why it is not urgent: PAT covers only iOS 16+/macOS Ventura+ signed into iCloud. Chrome has no equivalent shipped. A route gated on PAT alone would exclude most visitors, so Turnstile is the sensible way to reach attested-human today.

Nothing about this affects your traffic: with PAT unavailable, sessions mint exactly the tokens they already do.

Attestation raises a token to attested-human, but only when the actor behind it is not already known to be bot-like. A bot-like threat score caps the achievable grade regardless of attestation: attested-human needs a score of 40 or below, and human-likely 60 or below. A device can be genuine and still be driving an attack, so conflicting evidence wins.

Requiring attested-human on a route is a deliberate choice for a narrow set of paths, not a site-wide default. It is set from the grade selector on the route row under Enforcement → Policy. Every visitor without a configured attestation path tops out at human-likely, so gate on it only where you mean to exclude them.