Skip to content

Add hosted captun.sh rate limits#17

Closed
mmkal wants to merge 5 commits into
hosted-captun-shfrom
mmkal/26/05/23/hosted-rate-limits
Closed

Add hosted captun.sh rate limits#17
mmkal wants to merge 5 commits into
hosted-captun-shfrom
mmkal/26/05/23/hosted-rate-limits

Conversation

@mmkal
Copy link
Copy Markdown
Contributor

@mmkal mmkal commented May 23, 2026

Summary

Adds the first hosted-service abuse guardrail for captun.sh: a hosted-only Durable Object rate limiter for anonymous public tunnels.

Behavior:

  • throttles tunnel connect attempts per client IP before shard dispatch
  • throttles forwarded HTTP requests per client IP and per tunnel name
  • distributes limiter Durable Objects by hashed bucket key instead of one global hot object
  • returns plain 429 responses with Retry-After, cache-control: no-store, and x-captun-rate-limit
  • fails closed on hosted captun.sh if the limiter binding is missing, unless explicitly disabled with HOSTED_RATE_LIMIT_DISABLED=1
  • only trusts Cloudflare's cf-connecting-ip for hosted client identity; spoofable forwarded-IP headers collapse to unknown
  • keeps self-hosted/folder-routing deployments unaffected by default
  • exposes Worker vars for the window and bucket limits so tests and deployment can tune them

The first public defaults are intentionally coarse, not billing-grade quota accounting:

HOSTED_RATE_LIMIT_WINDOW_SECONDS=60
HOSTED_CONNECTS_PER_IP_PER_WINDOW=30
HOSTED_REQUESTS_PER_IP_PER_WINDOW=600
HOSTED_REQUESTS_PER_TUNNEL_PER_WINDOW=1200

Follow-up left out intentionally

Ownership tokens, active tunnel caps, byte/stream limits, Cloudflare-native edge rate limiting bindings, and observability are listed in the task file as follow-up work.

Verification

  • pnpm run check
  • pnpm test
  • pnpm run build
  • CAPTUN_PUBLIC_E2E=1 pnpm vitest run test/public-hosted.test.ts

Note

Medium Risk
Changes hosted captun.sh throttling behavior and Durable Object keying, which can impact availability (503/429) and effectiveness of abuse protection if misconfigured. Scoped to hosted mode but touches request admission paths and rate-limit enforcement.

Overview
Refactors hosted captun.sh rate limiting to scale and harden defaults. The HostedRateLimiter Durable Object is simplified to track a single fixed-window bucket, and rate limiting is now performed by multiple DO instances named via a hash of each bucket key (per-IP / per-tunnel) instead of a single global DO with an in-memory map.

Tightens safety and trust boundaries. Hosted mode now fails closed with a 503 when the HostedRateLimiter binding is missing (unless HOSTED_RATE_LIMIT_DISABLED=1 is explicitly set), and client IP detection for rate limiting is restricted to cf-connecting-ip (no longer trusting spoofable forwarded-IP headers). Tests and the hosted rate-limit task notes are updated to cover/describe these behaviors.

Reviewed by Cursor Bugbot for commit 256fd37. 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 23, 2026

Open in StackBlitz

npm i https://pkg.pr.new/captun@17

commit: 256fd37

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 1 potential issue.

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 256fd37. Configure here.

Comment thread src/worker.ts
limit: check.limit,
windowSeconds: config.windowSeconds,
});
if (!result.ok) return hostedRateLimitedResponse(result);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Split checks consume quota early

Medium Severity

For hosted HTTP requests, rate limiting runs per-IP then per-tunnel in separate HostedRateLimiter calls, and each successful check increments its bucket before the next check. If the later per-tunnel check returns 429, the per-IP counter is already increased even though the request never passed all limits, so clients can hit IP limits faster than the configured allowance.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 256fd37. Configure here.

@mmkal
Copy link
Copy Markdown
Contributor Author

mmkal commented May 24, 2026

Superseded by #20, which now uses this branch history as part of one combined hosted safety review.

@mmkal mmkal closed this May 24, 2026
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