Add hosted captun.sh safety controls#20
Conversation
…s' into mmkal/26/05/23/hosted-rate-limits
commit: |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 4377903. Configure here.
| () => | ||
| settle(() => { | ||
| void webSocketConnectionFailedError(options).then(reject); | ||
| }), |
There was a problem hiding this comment.
Close handler skips rejection probe
Medium Severity
In waitUntilOpen, the close listener rejects immediately with a generic close message, while the diagnostic HTTP probe that surfaces 409/rate-limit bodies runs only from the error listener. If close fires first (common when the upgrade handshake fails), CaptunTunnelConnectError and CLI active-name conflict hints are skipped.
Reviewed by Cursor Bugbot for commit 4377903. Configure here.
| windowSeconds: config.windowSeconds, | ||
| }); | ||
| if (!result.ok) return hostedRateLimitedResponse(result); | ||
| } |
There was a problem hiding this comment.
Rate limit counts before rejection
Medium Severity
Hosted forwarded-request limiting runs separate HostedRateLimiter.check calls for the client IP and tunnel name in sequence. Each successful check increments its bucket before the next runs, so a request rejected on the tunnel limit still consumes IP quota, and vice versa, without the request being forwarded.
Reviewed by Cursor Bugbot for commit 4377903. Configure here.
|
Superseded by the gateway-owned addressing refactor now pushed to #16. The hosted safety/rate-limit work should be rebuilt on top of the new gateway/token/connect-query protocol instead of carrying forward ownerToken/serverUrl-era API shape. |


Summary
Adds the first hosted
captun.shsafety layer on top of the initial public tunnel deployment.This collapses the earlier stacked safety PRs into one review surface. It supersedes #17, #18, and #19.
Behavior
Hosted
captun.shnow gets:HOSTED_RATE_LIMIT_DISABLED=1is explicitly setownerToken409 Conflictinstead of anonymous clients evicting each other from an active tunnel nameSelf-hosted and secret-protected deployments keep the existing replacement/auth behavior.
User-Facing API
createCaptunTunnelnow returns the anonymous hosted owner token and accepts it for intentional same-owner replacement:A different active anonymous owner gets a conflict instead of evicting the current tunnel.
Runtime Configuration
Defaults:
HOSTED_RATE_LIMIT_WINDOW_SECONDS=60HOSTED_CONNECTS_PER_IP_PER_WINDOW=30HOSTED_REQUESTS_PER_IP_PER_WINDOW=600HOSTED_REQUESTS_PER_TUNNEL_PER_WINDOW=1200The hosted rate limiter is wired through a new
HostedRateLimiterDurable Object binding and migration.Follow-Up Left Out Intentionally
This is still intentionally lightweight. It does not add persistent reservations, auth/accounts, paid custom names, active tunnel caps, byte/response caps, observability dashboards, or Cloudflare-native edge throttles. Anonymous ownership is active-session only: once a tunnel disconnects, the name is free for another anonymous token to claim.
Verification
pnpm exec vitest run test/hosted-admission.test.ts test/worker.test.ts test/cli.test.ts --testNamePattern 'diagnostic|retries reuse|secret|rate limit|ownership|owner token'pnpm run checkpnpm testpnpm run buildCAPTUN_PUBLIC_E2E=1 pnpm vitest run test/public-hosted.test.tswas run on the earlier safety stack before the Bugbot-only fixesNote
High Risk
Adds enforcement and throttling to the hosted tunnel connect/request path (new Durable Object rate limiter, ownership-token admission, and diagnostic probing), which can affect availability and connection behavior for production traffic. Also changes the public client API surface (
ownerToken), so regressions could break existing consumers if assumptions differ.Overview
Introduces hosted-only safety controls for
captun.sh: anonymous tunnel connects now require an ownership token and reject conflicting active owners with409instead of evicting the current session, while self-hosted/secret-protected deployments keep prior replace/auth behavior.Adds Durable Object–backed fixed-window rate limiting for hosted connect attempts (per IP) and forwarded requests (per IP and per tunnel), including fail-closed behavior when the limiter binding is missing (with an explicit
HOSTED_RATE_LIMIT_DISABLED=1escape hatch).Improves client-side ergonomics and diagnostics:
createCaptunTunnelnow generates/returns/acceptsownerToken, performs a short read-only HTTP probe to surface WebSocket upgrade rejection details (CaptunTunnelConnectError), and the CLI reuses one token across retries and shows a clearer message for the known hosted name-in-use conflict.Reviewed by Cursor Bugbot for commit 4377903. Bugbot is set up for automated code reviews on this repo. Configure here.