Add ts dev proxy design spec and implementation plan#798
Draft
aram356 wants to merge 37 commits into
Draft
Conversation
- Compare r.from case-insensitively in RuleTable::first_match to enforce the lowercase invariant regardless of how Rule.from was built - Reject trailing-colon inputs (empty port string) as RuleError::Port in Authority::parse; add rejects_empty_or_missing_port test - Assert scheme_is_tls in rewrite_default_preserves_from_host_and_sets_sni_to_to and rewrite_host_uses_to_authority_with_port
Add ConfigError::BasicAuthFile variant with a path-carrying display message so file-not-found and permission errors are no longer reported as the misleading "--basic-auth must be USER:PASS" format error. The read failure now maps to BasicAuthFile; only parse failures map to BasicAuth. Includes a unit test that asserts the correct variant is returned for a missing file.
Replace the write-then-chmod pattern in CertAuthority::persist() with a single OpenOptions::create_new(true).mode(0o600) open, eliminating the window where the private key was briefly world/group-readable on disk.
…NNECT - Thread the raw buffered bytes through `RequestHead` so `blind_forward_http` can write the complete request head to the upstream before piping the rest of the socket bidirectionally (spec §8.4). Previously the head was discarded, sending a truncated/empty request. - Update `blind_forward_http` doc comment to reflect that it now replays the original head rather than falsely claiming it always did. - Add `unmatched_connect_off_loopback_is_refused_with_403` integration test. The proxy listener is bound on `127.0.0.1:0` (real socket) but `cfg.listen` is patched to `0.0.0.0:<port>` before being handed to `serve_on`, so `is_loopback` is computed as false while the test can still connect via loopback. Asserts that an unmatched `CONNECT` receives `403` and no tunnel is established.
…s-dev-proxy-spec # Conflicts: # Cargo.toml
Replace the dead `std::thread::park()` restore thread in `launch_safari`
with a file-based persist-and-recover scheme. Before applying the PAC
URL, write `<ca_dir>/safari-proxy-restore` capturing the network service
name and the prior auto-proxy URL (or an empty second line when
auto-proxy was off). Add `restore_system_proxy_if_pending(ca_dir)`,
which reads and deletes that file then runs the appropriate `networksetup`
command to put things back.
Wire it into `run()` in two places: at startup (crash recovery from a
previous hard-killed run) and in a `tokio::select!` on `ctrl_c()` (clean
exit). Also move the function-local `use std::time::{SystemTime,
UNIX_EPOCH}` out of `make_temp_dir` to the top-level imports per project
convention.
…-spec' into worktree-review-ts-dev-proxy-spec
…h, and service detection
…ell-quote restore URL
… trust revocation
…FROM validation - Abort `ca regenerate` when the old CA's keychain revocation can't be confirmed, so on-disk key material never outlives its OS trust. - Declare the CLI macOS-only via `compile_error!` on non-macOS targets (keychain, Safari, and networksetup are all macOS-specific), and gate the macOS-only helpers (`manual_restore_command`, `restore_auto_proxy`) while ungating `shell_quote` so the shared launch path compiles cleanly. - Shell-quote the keychain, cert, and profile paths in the `ca install` and Firefox `certutil` fallback instructions. - Validate rule FROM as a bare hostname before embedding it in the generated PAC, browser URL, and upstream Host header. - Ignore the workspace-excluded crate's `target/` directory. Spec, plan, and guide updated to match.
The macOS-only `compile_error!` lived inside the proxy module while all native deps stayed unconditional, so a build for the repo-default wasm32-wasip1 target failed first in tokio/ring/aws-lc-sys instead of with the intended "macOS only" error — an easy developer footgun (a plain `cargo check` in the crate inherits the wasm default from .cargo/config.toml). - Move every dependency (and dev-dependency) under `[target.'cfg(target_os = "macos")'.dependencies]` so unsupported targets build none of the native TLS/networking stack. - Lift the platform gate to the crate root: `compile_error!` in lib.rs and `#[cfg(target_os = "macos")]` on the command modules, the CLI types, and the binary entry point. The proxy module's own `compile_error!` is removed. - Gate the e2e test crate to macOS (its deps are now macOS-scoped). Now `cargo check --target wasm32-wasip1` emits exactly one error — the macOS-only message — with no dependency build attempts. Native build/test unchanged (31 unit + 6 e2e pass). Spec and plan updated to match.
Let `--to` target a bare IP and `--rewrite-host` carry the hostname that
endpoint expects. The flag now takes an optional value:
- omitted -> Host = FROM (default; unchanged)
- --rewrite-host -> Host = TO host (the prior bare-flag behavior)
- --rewrite-host <HOST> -> Host = <HOST> and TLS SNI = <HOST>
Connection still dials `--to`, so pointing at an IP works: the proxy presents
the explicit hostname for both SNI and Host while the socket goes to the IP.
Replaces `Rule.preserve_host: bool` with a `HostMode { PreserveFrom, UseTo,
Explicit }` enum threaded through `rewrite_for`; the explicit host is validated
as a hostname (new `ConfigError::InvalidRewriteHost`). Spec, plan, and guide
updated; adds tests for all three forms plus invalid-value rejection.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds the design spec and implementation plan for
ts dev proxy— a local TLS-terminating (MITM) developer proxy that serves a production publisher hostname from a dev/staging upstream by swapping the TLS SNI, so a real browser shows the production domain while a Compute/staging service answers.This PR is docs-only (no code yet): a reviewed spec plus a task-by-task TDD plan ready for implementation.
docs/superpowers/specs/2026-06-22-ts-dev-proxy-design.md— technical specdocs/superpowers/plans/2026-06-22-ts-dev-proxy.md— 8-phase implementation planSpec highlights
TO.Host = FROM(preserve the production host) because Trusted Server core anchors URL rewriting to the inboundHost;--rewrite-hostopts intoHost = TO.--allow-non-loopbackdisables blind tunnel/forward so it can't become an open proxy.Plan shape
Crate skeleton (excluded from the wasm workspace, native target) → rewrite core → local CA → CONNECT/MITM server → header/auth polish → browser/PAC orchestration → config inference → docs. Each task ends in an independently testable deliverable; pure-logic tasks carry full TDD code.
Notes