From 63ca1d519dd4f0ac536e527144a7e4ecbf8c2563 Mon Sep 17 00:00:00 2001 From: Chris Taylor Date: Tue, 4 Aug 2026 15:44:47 +0100 Subject: [PATCH 1/2] docs(protect-edge): explain HTML-vs-JSON path types in the overview MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a "Protecting HTML pages vs JSON APIs" section to the edge integration overview. Explains: - HTML paths: cookie-less first-visit passes through so the Protect script can load and create a session; subsequent requests hit the verdict path. - JSON paths: cookie-less request 401s with X-Prosopo-Status: no-session; the Protect script intercepts, creates a session, and replays. Never passes through — no HTML shell to bootstrap from. - How to configure via defaultPathType + per-endpoint rules on the Prosopo dashboard, and why this closes the Accept-header spoof bypass. - Which mode fits which kind of site (server-rendered, SPA, pure API), with a table. Verified: astro check 0 errors. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/content/docs/en/protect-edge/index.mdx | 37 ++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/content/docs/en/protect-edge/index.mdx b/src/content/docs/en/protect-edge/index.mdx index 7ffa797cb268c..241cade9b8847 100644 --- a/src/content/docs/en/protect-edge/index.mdx +++ b/src/content/docs/en/protect-edge/index.mdx @@ -20,6 +20,43 @@ On every request that hits your CDN: The blocked-response HTML and challenge interstitial are branded per site via your Prosopo dashboard: logo, colour palette, typography, and message strings all sanitised and rendered server-side by the edge. +## Protecting HTML pages vs JSON APIs + +Protect handles browser page loads and API calls differently. You tell it which paths on your site are which, and the edge enforces accordingly. + +### HTML paths — pages users load in a browser + +- **First visit (no cookie):** the edge passes the request through so the Protect script embedded in your HTML can load and create a session. +- **Subsequent requests:** the edge fetches the verdict and either forwards (`allow`), serves a captcha interstitial (`challenge`), or serves a branded "Access Denied" page (`block`). + +Session creation is transparent — the Protect script calls `/api/protect/init`, receives a `prosopo_session` cookie, and every request from that browser carries it automatically. + +### JSON paths — API endpoints called by scripts / XHR / `fetch` + +- **No cookie:** the edge returns **401 Unauthorized** with `X-Prosopo-Status: no-session`. The Protect script (loaded on your app shell) intercepts the 401, creates a session, and replays the request. +- **With cookie:** the edge fetches the verdict. `allow` forwards to your origin; `block` returns a JSON error the Protect script uses to render an in-place block interstitial; `challenge` returns a JSON `challenge_config` the script uses to render a captcha modal without a full-page reload. + +JSON paths never pass through on a cookie-less request — an API endpoint has no HTML shell to bootstrap a session from, so a first-hit is always either a rule-based decision or a hard 401. + +### Configuring path types + +Tell Protect which paths on your site are HTML vs JSON on your [Prosopo dashboard](https://portal.prosopo.io/): + +- **Site-level default (`defaultPathType`)** — applies to every path unless overridden. +- **Per-endpoint rules** — override the default for specific paths (e.g. `/api/*` → json, `/` → html). + +This closes the "spoof `Accept: text/html` on an API endpoint" bypass. Bots can lie about their `Accept` header, but they can't change what content type your site actually serves at a given path — the declared type is what the edge enforces. + +### Which mode fits your site + +| Your site | Recommended default | Endpoint overrides | +|--|--|--| +| Server-rendered pages with occasional API endpoints | `html` | `/api/*` → `json` | +| Single-page app with a heavy API surface | `json` | `/`, `/index.html` → `html` | +| Pure JSON API service (no HTML at all) | `json` | (none) | + +For a pure-API deployment, consumers must obtain a session through a separate app that loads the Protect script, or through your own headless-friendly flow — the edge won't let a cookie-less browser reach a JSON endpoint. + ## Correlation Every edge request is stamped with an `X-Prosopo-Request-Id` header that's echoed on the response and written to the verdict audit log. From a support ticket you can grep straight to the verdict that fired. From fe08ee9b8e105e850ee9d857a88ea1f08b2caf97 Mon Sep 17 00:00:00 2001 From: Chris Taylor Date: Tue, 4 Aug 2026 15:48:26 +0100 Subject: [PATCH 2/2] docs(protect-edge): drop "HTML SPA shell" jargon for plain "HTML page" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "HTML SPA shell" was internal terminology and confusing for readers who aren't building a single-page app. Reword to "HTML page" and "HTML pages" throughout. The "Single-page app" row in the mode-fit table stays — it's how most devs describe their own project. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/content/docs/en/protect-edge/index.mdx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/content/docs/en/protect-edge/index.mdx b/src/content/docs/en/protect-edge/index.mdx index 241cade9b8847..50931a5680a13 100644 --- a/src/content/docs/en/protect-edge/index.mdx +++ b/src/content/docs/en/protect-edge/index.mdx @@ -16,7 +16,7 @@ Both integrations enforce the same policy: they consult the Prosopo Protect API On every request that hits your CDN: 1. **If the request has a `prosopo_session` cookie** the edge fetches the verdict for the session's token from the Prosopo Protect API. `allow` passes through to your origin; `block` returns a branded interstitial (or JSON error for API calls) with no origin fetch; `challenge` serves a captcha interstitial. -2. **If the request has no cookie** the edge asks Protect what to do. Protect evaluates your access-rule set (IP CIDR, ASN, IP category, country, User-Agent, JA4 TLS fingerprint) against the request. Rule matches return `block` or `challenge`; if no rule matches and the path is an HTML SPA shell, the edge lets it through so the Protect script can create a session. +2. **If the request has no cookie** the edge asks Protect what to do. Protect evaluates your access-rule set (IP CIDR, ASN, IP category, country, User-Agent, JA4 TLS fingerprint) against the request. Rule matches return `block` or `challenge`; if no rule matches and the path is an HTML page, the edge lets it through so the Protect script can create a session. The blocked-response HTML and challenge interstitial are branded per site via your Prosopo dashboard: logo, colour palette, typography, and message strings all sanitised and rendered server-side by the edge. @@ -33,10 +33,10 @@ Session creation is transparent — the Protect script calls `/api/protect/init` ### JSON paths — API endpoints called by scripts / XHR / `fetch` -- **No cookie:** the edge returns **401 Unauthorized** with `X-Prosopo-Status: no-session`. The Protect script (loaded on your app shell) intercepts the 401, creates a session, and replays the request. +- **No cookie:** the edge returns **401 Unauthorized** with `X-Prosopo-Status: no-session`. The Protect script (loaded on your site's HTML pages) intercepts the 401, creates a session, and replays the request. - **With cookie:** the edge fetches the verdict. `allow` forwards to your origin; `block` returns a JSON error the Protect script uses to render an in-place block interstitial; `challenge` returns a JSON `challenge_config` the script uses to render a captcha modal without a full-page reload. -JSON paths never pass through on a cookie-less request — an API endpoint has no HTML shell to bootstrap a session from, so a first-hit is always either a rule-based decision or a hard 401. +JSON paths never pass through on a cookie-less request — an API endpoint isn't an HTML page, so there's nowhere for the Protect script to load from and no session can be bootstrapped on the fly. A first-hit is always either a rule-based decision or a hard 401. ### Configuring path types