Skip to content

Add protected Cloudflare Quick Tunnel previews#135

Open
alex-vance wants to merge 2 commits into
EvanBacon:mainfrom
alex-vance:agent/cloudflare-quick-tunnels-upstream
Open

Add protected Cloudflare Quick Tunnel previews#135
alex-vance wants to merge 2 commits into
EvanBacon:mainfrom
alex-vance:agent/cloudflare-quick-tunnels-upstream

Conversation

@alex-vance

@alex-vance alex-vance commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Summary

This adds an opt-in Cloudflare Quick Tunnel mode to serve-sim so the full simulator preview can be opened from a remote computer or mobile browser without exposing a LAN listener or using a remote-desktop session.

The new workflow is available through:

serve-sim --tunnel [device...]
serve-sim --tunnel --detach [device...]

--public is retained as a hidden alias for --tunnel. Quick Tunnel mode requires an existing cloudflared installation, but does not require a Cloudflare account or login.

The implementation includes an application-level access gate because a Cloudflare Quick Tunnel provides transport and a random hostname, not identity authentication. It also makes the tunnel lifecycle part of the existing serve-sim process lifecycle, including detached startup, diagnostics, ownership validation, and cleanup.

Motivation

serve-sim already exposes a complete browser-based simulator control surface, but its normal preview URL is local to the Mac running Simulator. Reaching that preview from another device otherwise requires LAN connectivity, a VPN, manual reverse-proxy setup, or an RDP-style session.

This change makes temporary remote access a deliberate CLI choice while preserving the existing local default:

  • Normal serve-sim behavior is unchanged.
  • Tunnel mode binds the preview origin to 127.0.0.1.
  • cloudflared publishes that loopback origin through a temporary trycloudflare.com URL.
  • The complete printed URL acts as a private launch credential.
  • Attached and detached modes have explicit, observable cleanup behavior.

User-facing behavior

Attached mode

serve-sim --tunnel

The CLI prints both the local URL and a tokenized public URL. The process stays attached to the terminal, and Ctrl+C, SIGTERM, or SIGHUP stops the preview and its owned cloudflared child.

Detached mode

serve-sim --tunnel --detach -q

The launcher waits until the simulator preview and Quick Tunnel are ready, then returns JSON containing publicUrl, localUrl, process IDs, selected devices, the bound port, and the normalized launch configuration. serve-sim --kill stops the detached preview owner and its cloudflared child.

If an identical detached tunnel is already running, the command returns that state. If devices or preview options differ, it fails with an explicit instruction to stop the existing tunnel first instead of silently returning the wrong preview.

Missing dependency

If cloudflared is absent, tunnel mode exits before booting or publishing the preview and prints the Homebrew install command:

brew install cloudflared

The command passes --config /dev/null to cloudflared so a user-level named-tunnel configuration cannot interfere with Quick Tunnel startup.

Access protection

Quick Tunnels do not add Cloudflare Access or an identity login, so serve-sim protects the public control surface itself:

  • A cryptographically random 256-bit URL-safe token is generated for every tunnel owner.
  • The complete public URL includes that token as a query parameter.
  • The browser's first request exchanges the token for an HTTP-only, host-only, SameSite=Strict cookie; public HTTPS requests also receive the Secure attribute.
  • The redirect removes the token from the browser address bar.
  • Public HTTP routes and WebSocket upgrades require the cookie.
  • Browser control requests with an Origin header must match the exact tunnel origin, preventing a sibling trycloudflare.com page from driving the control surface with an existing cookie.
  • The preview origin remains bound to 127.0.0.1; tunnel mode rejects a conflicting --host value.

Local CLI commands continue to use the credential-free loopback URLs stored in the existing per-device state files. The access gate allows those requests only when the request host is loopback and no proxy forwarding protocol is present. Forwarded requests remain protected even if they present a loopback Host header.

The complete URL is still a credential: anyone who receives it can view and control the simulator and use preview features that execute commands on the host. The README and bundled AI skill call this out directly and recommend a managed Cloudflare Tunnel with Access for stable hostnames or identity policies.

Process ownership and lifecycle

The tunnel owner writes owner-only state and diagnostics under the existing runtime-state directory:

$TMPDIR/serve-sim/tunnel.json
$TMPDIR/serve-sim/tunnel.log

Both files are mode 0600. The log follows the project's existing persistent runtime diagnostics convention and records lifecycle events plus cloudflared output without writing the private access token.

Lifecycle handling includes:

  • Owner-verified cleanup before signalling a persisted PID.
  • A second ownership check before escalating from SIGTERM to SIGKILL, avoiding stale PID reuse.
  • Complete multi-device ownership validation rather than accepting a partially matching state set.
  • Automatic cloudflared termination when the preview owner exits through normal signal paths.
  • Detached launcher signal handlers that terminate the detached process group if startup is interrupted.
  • Process-group signalling with a direct-PID fallback for platforms or test environments where the group is unavailable.
  • A macOS caffeinate -w companion for deliberately detached public previews; it exits with the preview owner.
  • State removal only by the process that still owns it.

Detached launch identity

Detached state persists a normalized launch signature containing:

  • resolved device UDIDs and order
  • requested port and whether it was explicitly supplied
  • codec
  • initial panes
  • fit mode
  • theme

An existing tunnel is reused only when this signature matches exactly. Legacy state without a launch signature is not silently reused.

Runtime re-execution fix

Both regular --detach and tunnel detachment re-execute the CLI. npm/npx commonly invokes the package through an extensionless .bin/serve-sim symlink, which cannot be classified by filename extension.

Re-execution now classifies the runtime through process.execPath:

  • Node and Bun script runtimes include process.argv[1], even when the entrypoint is extensionless.
  • A Bun-compiled binary re-executes itself without appending its virtual /$bunfs/... entrypoint.

This preserves compiled-binary behavior while fixing both the existing npm/npx --detach path and the documented npx serve-sim --tunnel --detach path.

Startup timing and diagnostics

The detached launcher previously used a fixed deadline that could be shorter than the child process's supported startup behavior. Its deadline now includes:

  • 60 seconds for each simulator's simctl bootstatus
  • 30 seconds for Quick Tunnel readiness
  • 15 seconds of launcher/scheduling grace

If startup still fails, the launcher terminates the detached process group and surfaces the tail of the persistent tunnel log instead of leaving an orphaned preview or tunnel.

Review findings addressed

This version includes regression coverage and fixes for the blocking review findings found during local testing:

  1. Extensionless npm/npx entrypoints now retain the CLI script when re-executing under Node.
  2. Local CLI HTTP and WebSocket commands remain usable while the public access gate is active.
  3. Interrupting the detached launcher during readiness tears down the detached process group.
  4. Existing tunnels are reused only for an exact device and preview-option match.
  5. The launcher startup budget covers all simulator boot waits plus Cloudflare readiness.
  6. Persisted process ownership is revalidated before signalling and again before escalation.
  7. Tunnel-only access/proxy behavior remains internal to the standalone CLI instead of adding a partially implemented public middleware mode.
  8. Generated .playwright-mcp/ logs and snapshots are ignored and are not part of the published change set.

Documentation and AI skill

The package README now documents:

  • --tunnel and detached usage
  • remote PC/mobile access
  • cloudflared installation and failure behavior
  • the token/cookie security model
  • attached and detached cleanup
  • diagnostics locations
  • Quick Tunnel limitations and the managed Tunnel + Access alternative

The bundled serve-sim skill now includes:

  • Node 20+ and tunnel-specific prerequisite checks
  • scripts/check-prereqs.sh --tunnel
  • public preview commands and URL handoff guidance
  • lifecycle and security anti-patterns
  • a dedicated references/tunnels.md workflow
  • an additional tunnel-focused evaluation prompt

Validation

Automated checks:

bun run typecheck
bun run lint
bun run packages/serve-sim/build.ts
bun test packages/serve-sim/src/__tests__/bundle-portability.test.ts \
  packages/serve-sim/src/__tests__/runtime-reexec.test.ts \
  packages/serve-sim/src/__tests__/quick-tunnel.test.ts
jq empty skills/serve-sim/evals/evals.json
bash -n skills/serve-sim/scripts/check-prereqs.sh

Results:

  • TypeScript passed.
  • oxlint reported 0 warnings and 0 errors.
  • The complete JS, compiled CLI, camera dylib/helper, AX helper, and N-API native build passed.
  • 31 focused portability, re-execution, access-control, ownership, launch-identity, timeout, and process-group tests passed.
  • The skill eval JSON and prerequisite script syntax validated.

Manual CLI validation covered:

  • Regular detached startup through an extensionless npm-style serve-sim entrypoint.
  • Detached tunnel startup through that same extensionless entrypoint.
  • Identical launch reuse without spawning a replacement owner.
  • Rejection of a codec/configuration mismatch while preserving the existing owner.
  • Credential-free local event-log HTTP access during tunnel mode.
  • Credential-free local tap WebSocket access during tunnel mode.
  • Successful direct-loopback preview access and 401 for unauthenticated forwarded traffic.
  • Ctrl+C during detached startup leaving no preview process, cloudflared process, state file, or listening port.
  • serve-sim --kill and direct owner termination cleaning up the owned cloudflared process and runtime state.
  • Real Quick Tunnel creation, token-to-cookie exchange, preview configuration, helper streaming, and control WebSocket access with cloudflared 2026.7.1.

The broader real-Simulator suite produced 375 passes and 3 skips. Two teardown-time failures were investigated separately: the camera-helper shutdown test passed on isolated rerun, while all 10 UI-settings assertions pass but its serial afterAll reset exceeds Bun's default hook timeout on this machine.

Scope and compatibility

  • Existing local and LAN preview behavior remains the default.
  • The public simMiddleware package API is unchanged by tunnel authentication.
  • Node support follows the repository's maintained-LTS policy (>=20).
  • cloudflared is required only when the user explicitly selects tunnel mode.
  • Quick Tunnels remain a temporary development/testing feature, not a replacement for Cloudflare Access or production tunnel management.

Summary by CodeRabbit

  • New Features

    • Added protected Cloudflare Quick Tunnel support to serve-sim, including --tunnel and --public tokenized URL sharing.
    • Introduced detached tunnel preview lifecycle handling, including safe cleanup and --kill.
    • Updated runtime behavior to support re-execution across Node/Bun/bin entrypoints.
  • Documentation

    • Expanded CLI, skill, and reference docs with tunnel prerequisites (Node 20+ and cloudflared), security notes, and updated tunnel examples/evals.
  • Tests

    • Added Bun test coverage for tunnel access protection, URL parsing, ownership matching, detached startup/shutdown behavior, and runtime re-exec.
  • Chores

    • Updated ignore rules to exclude Playwright tunnel state artifacts.

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b08af643-5cfb-4a65-adac-157a572c34e5

📥 Commits

Reviewing files that changed from the base of the PR and between d10fdc5 and e35dc4b.

📒 Files selected for processing (3)
  • packages/serve-sim/src/index.ts
  • packages/serve-sim/src/quick-tunnel.ts
  • skills/serve-sim/scripts/check-prereqs.sh
🚧 Files skipped from review as they are similar to previous changes (3)
  • skills/serve-sim/scripts/check-prereqs.sh
  • packages/serve-sim/src/quick-tunnel.ts
  • packages/serve-sim/src/index.ts

📝 Walkthrough

Walkthrough

This change adds protected Cloudflare Quick Tunnel support to serve-sim, including token-based access control, detached lifecycle management, process ownership validation, cross-runtime re-execution, prerequisite checks, documentation, and evaluation coverage.

Changes

Quick Tunnel publishing

Layer / File(s) Summary
Tunnel runtime and access control
packages/serve-sim/src/quick-tunnel.ts, packages/serve-sim/src/__tests__/quick-tunnel.test.ts
Adds tunnel state contracts, token-cookie authorization, WebSocket protection, Cloudflare process startup, state persistence, ownership matching, timeout calculation, and process-group cleanup with tests.
Cross-runtime child execution
packages/serve-sim/src/runtime.ts, packages/serve-sim/src/__tests__/runtime-reexec.test.ts
Adds runtime-aware child command construction for Node, Bun source execution, and compiled Bun binaries.
CLI wiring and detached lifecycle
packages/serve-sim/src/index.ts
Adds tunnel options and validation, detached tunnel startup, tunnel-child re-execution, persisted readiness state, cleanup handlers, ownership revalidation, and --kill integration.
Prerequisites and workflow guidance
packages/serve-sim/README.md, skills/serve-sim/*, skills/serve-sim/scripts/check-prereqs.sh, .gitignore
Documents Quick Tunnel usage, token handling, lifecycle, diagnostics, Node 20 and cloudflared prerequisites, evaluation cases, and ignores .playwright-mcp/.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant Preview as serve-sim preview
  participant Tunnel as cloudflared
  participant Remote as Browser
  CLI->>Preview: Start with --tunnel
  Preview->>Tunnel: Spawn Quick Tunnel
  Tunnel-->>Preview: Return public trycloudflare URL
  Preview-->>CLI: Print publicUrl and persist tunnel state
  Remote->>Preview: Request tokenized URL
  Preview-->>Remote: Set access cookie and redirect
  Remote->>Preview: Send cookie-authenticated request
Loading

Possibly related PRs

  • EvanBacon/serve-sim#57: Introduced the skill scaffolding that these Quick Tunnel documentation and evaluation updates extend.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding protected Cloudflare Quick Tunnel previews.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot 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.

🧹 Nitpick comments (1)
skills/serve-sim/scripts/check-prereqs.sh (1)

8-15: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Make argument parsing strictly reject unexpected arguments.

Currently, if the script is invoked as scripts/check-prereqs.sh --tunnel extra_arg, it accepts the --tunnel flag and silently ignores the extra argument. Using shift ensures that any trailing unexpected arguments are correctly flagged as an error.

♻️ Proposed refactor
 TUNNEL_MODE=false
 if [[ "${1:-}" == "--tunnel" ]]; then
   TUNNEL_MODE=true
-elif [[ "$#" -gt 0 ]]; then
+  shift
+fi
+
+if [[ "$#" -gt 0 ]]; then
   echo "usage: $0 [--tunnel]" >&2
   exit 2
 fi
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@skills/serve-sim/scripts/check-prereqs.sh` around lines 8 - 15, Update the
argument parsing around TUNNEL_MODE so that after recognizing --tunnel, the
consumed argument is removed before validating remaining arguments. Ensure any
trailing arguments cause the existing usage error and exit status, while
preserving no-argument and --tunnel behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@skills/serve-sim/scripts/check-prereqs.sh`:
- Around line 8-15: Update the argument parsing around TUNNEL_MODE so that after
recognizing --tunnel, the consumed argument is removed before validating
remaining arguments. Ensure any trailing arguments cause the existing usage
error and exit status, while preserving no-argument and --tunnel behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 57efd1d7-c16e-46e1-bd2f-a2d0a0d682b2

📥 Commits

Reviewing files that changed from the base of the PR and between 14ad57f and d10fdc5.

📒 Files selected for processing (12)
  • .gitignore
  • packages/serve-sim/README.md
  • packages/serve-sim/src/__tests__/quick-tunnel.test.ts
  • packages/serve-sim/src/__tests__/runtime-reexec.test.ts
  • packages/serve-sim/src/index.ts
  • packages/serve-sim/src/quick-tunnel.ts
  • packages/serve-sim/src/runtime.ts
  • skills/serve-sim/README.md
  • skills/serve-sim/SKILL.md
  • skills/serve-sim/evals/evals.json
  • skills/serve-sim/references/tunnels.md
  • skills/serve-sim/scripts/check-prereqs.sh

@alex-vance

Copy link
Copy Markdown
Contributor Author

Here's a preview of serve-sim running over the internet on my iPhone.

trim.CF10B2B2-ECAB-41D4-A07B-68B3954735AD.MOV

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