Skip to content

fix: the session bearer on a plain-http read accelerator breaks every browser block read #1280

Description

@FSM1

Since #1243 the engine stamps the member's session JWT onto every read-accelerator request, and crates/engine/src/content/read.rs treats a loopback accelerator as able to keep it:

matches!(host, "localhost" | "127.0.0.1" | "[::1]")

No browser can deliver that header to such a gateway. Authorization makes the cross-origin block read non-simple, and a stock Kubo gateway answers the preflight with

Access-Control-Allow-Headers: Content-Type, Range, User-Agent, X-Requested-With

which omits it. Measured in the page against the local stack:

request result
Accept: application/vnd.ipld.raw 200, 1052 bytes
the same plus Authorization: Bearer … Failed to fetch — preflight rejected

What it breaks

VITE_PUBLIC_GATEWAYS is unset in apps/web/.env, so the accelerator is the only content source and there is nothing to rotate to. Every block read fails, so:

  • the root never adopts, and the resolve tick reports RefreshVerdict::Unreachable — the chrome shows refresh failed: no endpoint served a record this pass could adopt
  • Drain::load_scope_root (crates/engine/src/sync/drain.rs:838-852) cannot assemble the head envelope and returns Halt::UploadAttempt, so a queued write never publishes

The write stays pending with no dead letter, and the status indicator still reads synced. A cold start to an empty vault still works, which is why the smoke tier never saw it.

Reproduction

Against the local stack with VITE_READ_ACCELERATOR_URL=http://127.0.0.1:8080: log in, create a folder, force one refresh pass, wait. The child stays pending: "metadata" forever. Allowing Authorization in that Kubo's Gateway.HTTPHeaders makes the same build pass 11 of 11 web e2e specs.

Why no test caught it

crates/engine/tests/write_plane.rs builds engines on ApiBaseUrl::offline() and plants records, so "first-run mint then a queued write drains" is never composed. The gateway tests drive a scripted HTTP fake and assert only that the header is present. CORS exists only in a browser, so the web e2e write path is the first suite in the repo that can catch this class.

Fix

TLS is the whole rule: a plain-http accelerator falls to GatewaySource::public and still serves reads, unauthenticated. That also stops the session token reaching a gateway the host does not own.

Related, not covered here

Whatever accelerator staging points at must list Authorization in its CORS allow-list, or the deployed web app fails the same way.

Metadata

Metadata

Assignees

No one assigned

    Labels

    v2-buildv2 rewrite build slice

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions