Skip to content

Add hosted captun.sh safety controls#22

Merged
mmkal merged 24 commits into
mainfrom
mmkal/26/05/26/hosted-safety-on-gateway
May 26, 2026
Merged

Add hosted captun.sh safety controls#22
mmkal merged 24 commits into
mainfrom
mmkal/26/05/26/hosted-safety-on-gateway

Conversation

@mmkal
Copy link
Copy Markdown
Contributor

@mmkal mmkal commented May 26, 2026

Summary

Adds the first public-hosted safety layer for the now-merged hosted captun.sh gateway.

Hosted captun.sh tunnels now get a generated captun-token; the active tunnel keeps that token, same-token reconnects can replace it, and different-token reconnects get 409 instead of evicting someone else. Forwarded responses also drop Set-Cookie headers whose Domain escapes the active tunnel hostname while captun.sh waits for enough usage to justify Public Suffix List submission.

const tunnel = await createCaptunTunnel({
  fetch: (request) => new Response("ok"),
});

console.log(tunnel.url);   // https://<random>.captun.sh
console.log(tunnel.token); // generated hosted reconnect token

Structure

  • src/worker.ts owns the shared Cloudflare Tunnel Gateway and CaptunServerShard tunnel lifecycle; its default decideTunnelAdmission behavior keeps self-hosted deployments trusted and token-gated only when CAPTUN_TOKEN is set.
  • src/hosted/worker.ts is the Iterate-operated hosted gateway entrypoint for captun.sh; it subclasses the shared shard to override public hosted admission, wraps requests with hosted rate limits, and strips broad response cookie domains.
  • src/hosted/rate-limit.ts owns hosted-only Durable Object rate-limit buckets.
  • src/hosted/site.ts owns the www.captun.sh docs/demo surface and browser module.

Notes for reviewers

  • Self-hosted deployments without CAPTUN_TOKEN keep trusted replacement behavior; deployments with CAPTUN_TOKEN still require the configured token.
  • The hosted Wrangler config is the only deployment config with the HostedRateLimiter binding.
  • The browser demo module now generates the same hosted token as the package and CLI.
  • WebSocket connection failures do a short diagnostic HTTP probe so CLI errors can show 409/429 bodies instead of generic WebSocket failures.
  • Hosted response hardening keeps host-only cookies and cookies scoped to the active tunnel hostname, but strips Domain=captun.sh, sibling tunnel domains, and other domains outside that tunnel namespace.

Verification

  • pnpm exec vitest run test/hosted-worker.test.ts
  • pnpm run check
  • pnpm test

Note

High Risk
Changes public tunnel admission, rate limiting, and cookie handling on the untrusted hosted gateway—security-sensitive paths with broad test coverage but production impact on all captun.sh users.

Overview
This PR adds the first public hosted safety layer for captun.sh, keeping public gateway policy in src/hosted/ while src/worker.ts stays the shared self-hosted gateway core.

Tunnel ownership: Hosted clients (CLI, library, browser demo) now get a generated captun-token when targeting captun.sh. The hosted shard overrides admission so anonymous connects require that token, same-token reconnects can replace the session, and a different token gets 409 instead of evicting the active tunnel. Self-hosted behavior is unchanged unless CAPTUN_TOKEN is set.

Rate limiting: New HostedRateLimiter Durable Object buckets throttle connect attempts and forwarded traffic per cf-connecting-ip and per tunnel name, with a diagnostic path that does not open WebSockets or replace active tunnels. Missing the limiter binding returns 503 unless explicitly disabled.

Client UX: createCaptunTunnel probes failed WebSocket upgrades via x-captun-connect-diagnostic and surfaces CaptunTunnelConnectError with HTTP status/body; the CLI skips DNS hints for the known name-conflict case and reuses the same hosted token across retries.

Response hardening: Hosted forwards strip Set-Cookie headers whose Domain escapes the active tunnel hostname (e.g. apex or sibling subdomains).

Reviewed by Cursor Bugbot for commit 133ffbf. Bugbot is set up for automated code reviews on this repo. Configure here.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 26, 2026

Open in StackBlitz

npx https://pkg.pr.new/captun@22

commit: 133ffbf

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit d41f629. Configure here.

Comment thread src/hosted/rate-limit.ts
Comment thread src/index.ts
mmkal added a commit that referenced this pull request May 26, 2026
## Summary

Adds the hosted `captun.sh` path so a first-time user can get a public
tunnel without deploying anything first.

```bash
# with something listening on localhost:3000
npx captun 3000
```

The CLI now defaults to the hosted `https://captun.sh` gateway when
there is no local config, generates a random tunnel name, and prints a
public URL like:

```text
https://abc123.captun.sh -> localhost:3000
```

No Cloudflare account, config file, token, or deploy wizard is needed
for that first run. Users can still run `npx captun deploy` later if
they want their own self-hosted gateway.

## What Changed

- defaults missing local config to the hosted `https://captun.sh`
gateway
- changes `createCaptunTunnel` to connect with `{ gateway, name, token,
fetch }` and wait for the gateway to return `{ url, token }`
- renames the low-level Cap'n Web accept APIs to
`acceptFetcherCapability` and `acceptFetcherCapabilityFromSocket`
- updates the CLI, deploy wizard, config file, smoke scripts,
benchmarks, docs, and hosted browser demo to use `gateway`/`token`
- keeps the self-hosted Cloudflare Tunnel Gateway readable in
`src/worker.ts`, with the hosted `captun.sh` product surface isolated
under `src/hosted/`
- reserves product/control-plane tunnel names, serves `www.captun.sh`,
and redirects the apex host to `www`
- adds `CONTEXT.md` and ADR-0001 to keep the Fetcher Capability / Tunnel
/ Gateway language clear

#20 is superseded by this shape; hosted rate limiting and ownership
controls are rebuilt in #22 on top of the gateway-owned protocol.

## Library Example

```ts
import { createCaptunTunnel } from "captun";

const tunnel = await createCaptunTunnel({
  fetch: (request) => Response.json({ path: new URL(request.url).pathname }),
});

console.log(tunnel.url); // https://abc123.captun.sh
```

Self-hosted use now passes the gateway URL, not a tunnel URL template:

```bash
npx captun 3000 --gateway 'https://captun.youraccount.workers.dev' --token abc123
```

## Structure

- `src/index.ts` contains the client API and low-level Fetcher
Capability helpers.
- `src/worker.ts` is the deployable self-hosted Cloudflare Tunnel
Gateway.
- `src/hosted/site.ts` and `src/hosted/worker.ts` are the
Iterate-operated hosted surface for `captun.sh`.
- `wrangler.jsonc` is for self-hosted deployment;
`wrangler.hosted.jsonc` is for the hosted service.

## Verification

- `pnpm run check`
- `pnpm test`
- `pnpm run build`
- `pnpm exec vitest run test/worker.test.ts test/e2e.test.ts
examples/weather-reporter/e2e.test.ts`
- `CAPTUN_PUBLIC_E2E=1 pnpm exec vitest run test/public-hosted.test.ts`
- deployed `captun-public` to Iterate prd with `captun.sh/*` and
`*.captun.sh/*`
@mmkal mmkal changed the base branch from hosted-captun-sh to main May 26, 2026 11:43
@mmkal mmkal merged commit d3c0510 into main May 26, 2026
5 checks passed
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.

1 participant