Skip to content

fix(security/web) P0: gate handler StopIteration on apex + 404 on portals

isidro requested to merge fix/preview-prefixes-stopiteration-and-rewrite into main

Incident — 2026-05-21 (cont. of MR !33 (merged))

After MR !33 (merged) (the original `sovereignstrike.com/plan` leak fix) was deployed, two regressions surfaced when the handler ran behind Cloudflare Tunnel:

  1. `/` and `/index.html` crashed with `StopIteration` → connection closed → cloudflared logs `EOF` → user sees 502 Bad Gateway. Affects every visitor hitting the apex (which was every mobile user since CF caches the apex differently than internal paths).
  2. `/cto`, `/art`, `/plan`, `/studio`, … returned 404 because the gate's serve-direct flow tried to read `/cto/index.html` literally instead of `/cto_portal/index.html` (which is what the rewrite chain does).

Symptoms in the cloudflared log: ``` ERR error="Unable to reach the origin service ... EOF" connIndex=2 originService=http://127.0.0.1:8788 ```

Root cause

MR !33 (merged) added the 11 internal portal prefixes to `_PREVIEW_PREFIXES` to enforce authentication on them. But that tuple controlled two unrelated concerns:

Concern Should drive
"must require authentication" _PREVIEW_PREFIXES
"is served from disk at /index.html" (was implicit, conflated)

For `/press`, both are true. For `/cto`, only the first is — the actual file lives at `/cto_portal/index.html` via the elif chain farther down `do_GET`. The post-gate code returned BEFORE reaching that rewrite block.

For `/` and `/index.html`, neither serving strategy matches; the prefix lookup raised `StopIteration`.

Fix

  1. New `_PREVIEW_SERVE_DIRECT_PREFIXES = {"/press"}` — the only path that genuinely lives as a directory in `docs/`. Drives the serving strategy only.
  2. Apex roots (`/`, `/index.html`) and internal portals (`/cto`, `/art`, `/plan`, `/studio`, …) fall through to the standard rewrite chain after auth passes.
  3. New `_gate_passed_for_path` instance flag prevents double-prompting with `CMO_WEB_AUTH` on top of `LANDING_AUTH`.

Test plan

  • `uv run pytest -q` → 473 passed (+1 regression test enforcing the serve-direct split)
  • Local curls (against the natively-served origin):
    • `/, /cto, /cto/, /plan, /plan/, /art, /art/, /studio` → 200 (with auth)
    • `/press` → 301 → `/press/` → 200 (with auth)
    • All without auth → 401
  • Playwright navigated `/cto` end-to-end via Cloudflare Tunnel and got `ERR_INVALID_AUTH_CREDENTIALS` (= origin served basic-auth challenge, NOT 502)
  • Post-merge: confirm mobile flow (the original reproducer)

Severity

P0 — the production cockpit was unusable from any client that hit the apex first (i.e. every mobile + every fresh browser session).

🤖 Generated with Claude Code

Merge request reports