Skip to content

feat(access): reach the engines from any PC through the Cloudflare tunnel - #44

Merged
MSD21091969 merged 2 commits into
mainfrom
feat/t283-cf-access
Aug 11, 2026
Merged

feat(access): reach the engines from any PC through the Cloudflare tunnel#44
MSD21091969 merged 2 commits into
mainfrom
feat/t283-cf-access

Conversation

@MSD21091969

Copy link
Copy Markdown
Contributor

The pilot could only talk to localhost and Tailscale: the manifest listed no
cloud hostname (so MV3 blocked the request before any code ran) and CF-Access
appeared in zero files under src/. The tunnel has worked since t283 — this is
the client half.

  • src/state/cf-access.ts: the service-token store on its own storage key.
    Deliberately NOT in access-config.ts, whose contract says it never stores a
    secret; widening that quietly would have been the wrong move.
  • streamable-http-client.js: accessHeaders merged into mcpHeaders() and all
    three REST sites, with the JSDoc typedef updated so TS checks it.
  • adapter-factory: one injection point at resolveAdapterConfig's single exit,
    so every caller is covered and no site can forget.
  • manifest: + kernel./api.my-tiny-data-collider.nl.

SCOPING IS THE SAFETY PROPERTY, and it is tested: cfAccessHeadersFor()
returns {} unless the URL is https AND the host ends in the collider domain.
9 cases pass — cloud kernel/api get the pair; localhost, Tailscale, plain
http, a disabled token, no token, a garbage URL, and a LOOKALIKE domain
(kernel.my-tiny-data-collider.nl.evil.com) all get nothing. Local operation
is byte-identical to before.

The token is edge passage only. Writes stay gated by the kernel bearer; the
two must never be interchanged.

Verified: typecheck clean, build green, smoke:live PASS.

authored-by: agent:claude-cowork.hp-z440 / session:sam.z440-cowork-workspace / t283-cf-access

Co-Authored-By: Claude Fable 5 noreply@anthropic.com

…nnel

The pilot could only talk to localhost and Tailscale: the manifest listed no
cloud hostname (so MV3 blocked the request before any code ran) and CF-Access
appeared in zero files under src/. The tunnel has worked since t283 — this is
the client half.

- src/state/cf-access.ts: the service-token store on its own storage key.
  Deliberately NOT in access-config.ts, whose contract says it never stores a
  secret; widening that quietly would have been the wrong move.
- streamable-http-client.js: accessHeaders merged into mcpHeaders() and all
  three REST sites, with the JSDoc typedef updated so TS checks it.
- adapter-factory: one injection point at resolveAdapterConfig's single exit,
  so every caller is covered and no site can forget.
- manifest: + kernel./api.my-tiny-data-collider.nl.

SCOPING IS THE SAFETY PROPERTY, and it is tested: cfAccessHeadersFor()
returns {} unless the URL is https AND the host ends in the collider domain.
9 cases pass — cloud kernel/api get the pair; localhost, Tailscale, plain
http, a disabled token, no token, a garbage URL, and a LOOKALIKE domain
(kernel.my-tiny-data-collider.nl.evil.com) all get nothing. Local operation
is byte-identical to before.

The token is edge passage only. Writes stay gated by the kernel bearer; the
two must never be interchanged.

Verified: typecheck clean, build green, smoke:live PASS.

authored-by: agent:claude-cowork.hp-z440 / session:sam.z440-cowork-workspace / t283-cf-access

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 11, 2026 16:33

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds Cloudflare Access service-token support to the Pilot’s HTTP/MCP client path so the extension can reach cloud-hosted engines (via the Cloudflare tunnel) from any machine, while keeping local/Tailscale behavior unchanged.

Changes:

  • Introduces a dedicated Cloudflare Access token store (chrome.storage.local) and URL scoping helper (cfAccessHeadersFor).
  • Threads accessHeaders through the streamable HTTP adapter/client so MCP + REST requests can include the Cloudflare Access headers when appropriate.
  • Expands MV3 host_permissions to allow requests to kernel.my-tiny-data-collider.nl and api.my-tiny-data-collider.nl.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/state/cf-access.ts Adds Cloudflare Access token persistence and scoped header construction.
src/mcp/streamable-http-client.js Merges Access headers into MCP and REST requests.
src/mcp/streamable-http-adapter.ts Adds accessHeaders to adapter config and forwards into the shared client.
src/mcp/adapter-factory.ts Injects Access headers during adapter config resolution based on engine URL.
public/manifest.json Adds cloud host permissions and reformats permissions/resources arrays.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/state/cf-access.ts Outdated
Comment on lines +20 to +23
* STORAGE TIER. `chrome.storage.local` is extension-local and not synced. It is readable by
* anything with access to this profile — same trust tier as the browser's own password store,
* and strictly better than the alternative of embedding the pair in a committed config. The
* pair is only ever sent to the two configured Cloudflare hostnames (see `isCloudHost`).
Comment on lines +202 to +205
const cf = await loadCfAccess();
const headers = cfAccessHeadersFor(config.engineUrl ?? "", cf);
if (Object.keys(headers).length > 0) config.accessHeaders = headers;
} catch {
Comment thread src/state/cf-access.ts Outdated
The middle one was a real defect, not a nit: accessHeaders are baked into an
adapter at build time, but the worker only dropped its memo on pilot.engine
changes — so ROTATING OR DISABLING the token left live adapters sending the
old credential until the service worker happened to restart. The memo now
invalidates on pilot.cfaccess too, which is what makes the enabled:false off
switch actually mean something.

- isCfAccessSet is a type predicate, removing the cfg! assertions.
- The doc no longer claims chrome.storage.local is the browser password
  store (it is not encrypted at rest) and no longer says 'two hostnames'
  when isCloudHost is a suffix match over the whole zone. Both said plainly,
  with the rotation note that the Access policy binds the token by name.

Verified: typecheck + build clean, smoke:live PASS, scoping still 9/9
(cloud yes; localhost, Tailscale, http, disabled, absent, garbage, and a
lookalike domain all no).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@MSD21091969

Copy link
Copy Markdown
Contributor Author

All three addressed in 62d1466 — and the middle one was a real defect, so thank you for it.

Stale token (the bug). You're right that accessHeaders are baked in at adapter-construction time while the worker only cleared its memo on pilot.engine. Consequence: rotating or disabling the token left live adapters sending the old credential until the service worker happened to restart — which quietly made the enabled: false off switch not an off switch. src/worker.ts now invalidates on PILOT_CF_ACCESS_KEY as well.

Type predicate. isCfAccessSet now narrows (cfg is CfAccessConfig); the cfg! assertions are gone.

Overstated doc. Both claims corrected rather than softened: chrome.storage.local is not encrypted at rest and is not the password store — anything that can read the profile can read the pair; and isCloudHost is a suffix match over the whole zone, so it is not "two hostnames" — today it is kernel./api., but any future subdomain of that zone would also receive the pair. Added the rotation note, since the Access policy binds the token by name and rotation therefore needs no dashboard rework.

Verified: typecheck + build clean, smoke:live PASS, and the scoping suite still 9/9 — cloud hosts yes; localhost, Tailscale, plain http, disabled, absent, garbage URL and a lookalike domain all get nothing.

@MSD21091969
MSD21091969 merged commit f1c192b into main Aug 11, 2026
2 checks passed
@MSD21091969
MSD21091969 deleted the feat/t283-cf-access branch August 11, 2026 16:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants