Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.19.0] - 2026-10-12

### Added
- Explicit `createClearanceFetch()` adapter and `readClearanceChallenge()` parser for the version-1 edge API challenge contract. Available as library exports and `window.WebDecoyClearance` in the standalone bundle.
- GET/HEAD recovery with one retry, shared recovery per adapter/protected origin, a bounded deadline, cancellation, and local observation hooks. Applications provide their own clearance flow; refusal remains server-owned.
- Policy refusals, ordinary origin 403s, unsupported contracts, omitted credentials, redirects, offline state, and all mutations do not trigger automatic recovery. No global fetch interception or XHR handling.

## [0.18.5] - 2026-10-12

### Fixed
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,9 @@ if (!result.allowed) {
sessions, including startup with an existing cookie. It coalesces repeated
startup, bounds mint failures with a timeout and backoff, and stops automatic
attempts when the server refuses clearance. Application fetch/XHR requests are
not intercepted or replayed. See the [browser client guide](packages/client/README.md#session-clearance).
not intercepted or replayed globally. Version 0.19.0 adds an opt-in
`createClearanceFetch()` helper for one GET/HEAD retry through your own clearance
flow; mutations and policy refusals never recover automatically. See the [browser client guide](packages/client/README.md#session-clearance).

## One bot policy, published and enforced

Expand Down
2 changes: 2 additions & 0 deletions llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,5 @@ Offline by default: an API key in the environment is ignored unless
- Web Bot Auth guide: https://github.com/WebDecoy/node/blob/main/docs/verify-ai-agents-web-bot-auth.md
- Dashboard: https://app.webdecoy.com
- Docs: https://docs.webdecoy.com

API recovery (`@webdecoy/client` 0.19.0+): `createClearanceFetch({ origin, recover, timeoutMs?, onEvent? })` returns an explicit fetch adapter. `recover({ origin, challenge, signal })` must earn/install the protected origin's clearance cookie and resolve true; `startClearance()` is not awaitable recovery. Exact v1 challenge headers, GET/HEAD only, one retry, shared callback per adapter, 10s default/60s max recovery-and-retry deadline. Refusal/timeout returns the original response; caller abort/network errors reject. Mutations, ordinary 403, revoked/crawler-refused/agent-impersonation, redirects and omitted credentials never recover. Cross-origin needs include credentials, host cookie and approved CORS exposing the challenge headers. No global interception, XHR support or automatic telemetry. Standalone global: `window.WebDecoyClearance`.
20 changes: 10 additions & 10 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

75 changes: 73 additions & 2 deletions packages/client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ edge validator continue to verify every token and enforce revocation.
The cookie is Secure, SameSite Lax and host-only. Accepted interaction aggregates
remain on renewal requests, and the server evaluates them again; pass
`behavior: false` to disable the behavioral upgrade. The client does not intercept
or retry application fetch/XHR requests: an edge API challenge is a JSON 403
that your application must handle explicitly.
or retry application fetch/XHR requests globally. An edge API challenge is a JSON
403; the opt-in helper below handles safe reads through your application’s flow.

## What it collects

Expand Down Expand Up @@ -131,3 +131,74 @@ and Next.js adapters and the full detection/scoring reference.
## License

MIT

## API challenge recovery

With `@webdecoy/client` **0.19.0 or later** and an updated edge validator, opt in
for the API reads your application wants to recover:

```ts
import { createClearanceFetch } from '@webdecoy/client';
import { earnClearance } from './clearance-flow'; // your application's flow

const fetchProtected = createClearanceFetch({
origin: location.origin,
timeoutMs: 10_000,
recover: ({ origin, challenge, signal }) =>
earnClearance({ origin, reason: challenge.reason, signal }),
onEvent: event => console.debug(event.type, event.status),
});

const controller = new AbortController();
const response = await fetchProtected('/api/catalog', {
credentials: 'same-origin',
signal: controller.signal, // your application's AbortController
});
// Handle response.ok/status/body just as with native fetch.
```

`earnClearance` is application code, not an SDK export. It must earn and install
the host's `wd_clearance` cookie through your configured issuance/verification
flow and return `true` only when ready to retry. `startClearance()` returns
`void`; awaiting it does not wait for a token. A self-hosted captcha widget token
is not a clearance cookie. A stronger-grade response needs a flow that earns the
route's required grade; simply replacing a token cannot grant that grade.

The helper retries **GET/HEAD once** after a recognized recoverable challenge.
Concurrent calls through one adapter share one recovery callback for its
configured origin; that callback receives the first challenge's reason. Use one
adapter for each origin/recovery flow. The edge evaluates every retry again.
POST/PATCH/PUT/DELETE and other methods pass through once with their original
body. There is no mutation replay opt-in and no XHR interception. The standalone
script exposes the same helpers at `window.WebDecoyClearance`.

Recovery and the retry share a deadline after the first challenge: 10 seconds by
default, configurable from 1 ms to 60 seconds. Refusal, resolver rejection,
offline state, or deadline expiry returns the original readable challenge
response. Caller cancellation and fetch network errors reject as native fetch
does. One cancelled caller does not cancel other callers waiting for the shared
flow; when none remain, the recovery signal aborts. Your flow must stop network
work and close its UI on that signal. If it ignores cancellation, later calls
remain attached to that stopped attempt until it settles, rather than opening
overlapping recovery dialogs. Browsers without `AbortSignal.any()` return the
challenge without attempting recovery.

Only version-1 responses with the exact WebDecoy challenge headers qualify.
Ordinary application 403s, unsupported/malformed contracts, revoked sessions,
crawler-policy refusals and agent impersonation do not trigger recovery.
Redirected responses, different origins and `credentials: 'omit'` also do not.
The helper preserves request options and leaves response bodies/streams unread.
It adds no request headers or CORS permission. Cross-origin calls need
`credentials: 'include'`, the protected host's cookie, and an application-approved
CORS policy that exposes the four challenge headers; otherwise the browser
cannot identify the response. A cookie set on the page's host does not clear a
different API host.

`onEvent` reports `challenge-served`, `recovery-attempted`,
`recovery-succeeded`/`recovery-failed`, and the retried `enforcement-outcome`
(status). Attempts are per waiting request; count callback invocations separately
for shared flows. Success means the retry returned an OK response, not a new
trust grade. These local observations send no telemetry automatically and must
remain distinct from the validator's verdict counts. Observation failures cannot
change the request. Interactive application flows must provide keyboard access,
focus restoration, an announced status and cancellation; the helper renders no UI.
2 changes: 1 addition & 1 deletion packages/client/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@webdecoy/client",
"version": "0.18.5",
"version": "0.19.0",
"description": "Web Decoy browser widget - signal collection, proof-of-work, and captcha UI",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
Expand Down
Loading
Loading