From 625ca5c12fdca4ea68ebe9bd162f77d1a0005962 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Fri, 10 Jul 2026 12:13:52 -0700 Subject: [PATCH 1/2] Streamline the dev proxy guide to match the ts CLI install MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the verbose `cargo run --manifest-path … --target … -- dev proxy` invocations with the installed `ts` binary (via `cargo install-cli`), so the guide reads the same way as the CLI guide. Note that `ts dev proxy` is macOS-only and phrase the install step as "install or update". Make the quick start a complete first run (`ca install`, then run with `--rewrite-host` and `--launch chrome`), and make `--rewrite-host` the recommended default for dev and staging upstreams. The Host-header guidance now turns on whether the upstream accepts `Host: ` rather than on the upstream being Trusted Server Compute. Correct several stale or wrong examples, verified against the CLI source: - Firefox `certutil` now uses `-d "sql:…"` and the full CA nickname, matching the auto-import the tool performs. - Add the missing `--connect-timeout` option and note that `[COMMAND]` is `ca`. - Prefer `--basic-auth-file` over inline `--basic-auth` in the staging example. - Clarify the CA key is stored outside the repository only by default, and rename "First run: CA setup" to "The development CA". --- docs/guide/ts-dev-proxy.md | 87 ++++++++++++++++++++++++-------------- 1 file changed, 56 insertions(+), 31 deletions(-) diff --git a/docs/guide/ts-dev-proxy.md b/docs/guide/ts-dev-proxy.md index ee28fde9..6d289b7a 100644 --- a/docs/guide/ts-dev-proxy.md +++ b/docs/guide/ts-dev-proxy.md @@ -33,18 +33,38 @@ logic, CMP/consent flows, first-party context — before any DNS cutover. **Non-goals.** Not a production proxy or load-testing tool. Does not modify any Fastly service. Local only, developer-facing. -## Build and run +## Install and run -`ts dev proxy` is part of `crates/trusted-server-cli`, a workspace member that -builds for the host. Because the workspace default target is `wasm32-wasip1` -(where the CLI is an empty shell), build and run it with an explicit native -target: +`ts dev proxy` is a subcommand of the `ts` CLI, and is **macOS-only** — its +dependencies are scoped to macOS, so the command is not present in the CLI on +other platforms. On macOS, install (or update) the `ts` binary from the +repository root (see [the CLI guide](./cli.md#install-from-source) for details): ```bash -cargo run --manifest-path crates/trusted-server-cli/Cargo.toml \ - --target "$(rustc -vV | sed -n 's/host: //p')" -- dev proxy --help +cargo install-cli ``` +Rerun `cargo install-cli` after pulling CLI changes to refresh the installed +binary. Then trust the dev CA once (see +[the development CA](#the-development-ca)), map the production hostname to your +upstream, and launch a browser: + +```bash +ts dev proxy ca install +ts dev proxy \ + --map www.example-publisher.com=trusted-server-example.edgecompute.app \ + --rewrite-host \ + --launch chrome +``` + +`--rewrite-host` sends the upstream `Host: `, which most dev and staging +upstreams require — they don't have the production hostname configured and would +reject the default `Host: `. See +[Host header behavior](#host-header-behavior) for the one case where you can drop +it. + +Run `ts dev proxy --help` to list every flag. + ### Passing the rewrite rule The upstream is always passed explicitly — there is no inference from @@ -52,9 +72,7 @@ The upstream is always passed explicitly — there is no inference from shorthand, or one or more `--map FROM=TO` rules: ```bash -cargo run --manifest-path crates/trusted-server-cli/Cargo.toml \ - --target "$(rustc -vV | sed -n 's/host: //p')" -- dev proxy \ - -f www.example-publisher.com -t trusted-server-example.edgecompute.app +ts dev proxy -f www.example-publisher.com -t trusted-server-example.edgecompute.app ``` With no `--map`/`-f`/`-t`, the proxy exits with @@ -63,8 +81,7 @@ With no `--map`/`-f`/`-t`, the proxy exits with ### Explicit rule and browser launch ```bash -cargo run --manifest-path crates/trusted-server-cli/Cargo.toml \ - --target "$(rustc -vV | sed -n 's/host: //p')" -- dev proxy \ +ts dev proxy \ --map www.example-publisher.com=trusted-server-example.edgecompute.app \ --launch chrome,firefox,safari ``` @@ -79,7 +96,8 @@ omitted the proxy runs without opening any browser. ts dev proxy \ -f www.example-publisher.com \ -t staging.example.net \ - --basic-auth dev:secret \ + --rewrite-host \ + --basic-auth-file ./staging-creds.txt \ --launch firefox # Local instance over plain HTTP, no browser: @@ -89,23 +107,25 @@ ts dev proxy \ --upstream-plaintext ``` -## First run: CA setup +## The development CA -On first run the proxy generates a per-machine Certificate Authority and prints: +The proxy relies on a per-machine Certificate Authority that your browser must +trust. `ts dev proxy ca install` (in the quick start above) generates it if +needed and trusts it in one step, so you don't have to run the proxy first. +Running the proxy also generates the CA on first use, printing: ``` generated dev CA at ~/Library/Application Support/trusted-server/dev-proxy/ca-cert.pem — run `ts dev proxy ca install` to trust it ``` -The CA key is stored with mode `0600` outside the repository and is never -committed. +The CA key is written with mode `0600` and, by default, is stored outside the +repository; it is never committed. ### Trust the CA on macOS (Chrome and Safari) ```bash -cargo run --manifest-path crates/trusted-server-cli/Cargo.toml \ - --target "$(rustc -vV | sed -n 's/host: //p')" -- dev proxy ca install +ts dev proxy ca install ``` This adds the CA to the macOS login keychain (no `sudo` required; prompts for @@ -120,18 +140,16 @@ Firefox profile's NSS database using `certutil`. If you are pointing an existing Firefox profile at the proxy manually, run: ```bash -certutil -A -n "Trusted Server DEV-ONLY Proxy CA" \ +certutil -A -n "Trusted Server DEV-ONLY Proxy CA — DO NOT TRUST IN PRODUCTION" \ -t "CT,," \ - -i "$(cargo run --manifest-path crates/trusted-server-cli/Cargo.toml \ - --target "$(rustc -vV | sed -n 's/host: //p')" -- dev proxy ca path)" \ - -d "$HOME/Library/Application Support/Firefox/Profiles/" + -i "$(ts dev proxy ca path)" \ + -d "sql:$HOME/Library/Application Support/Firefox/Profiles/" ``` ### Revoking trust when done ```bash -cargo run --manifest-path crates/trusted-server-cli/Cargo.toml \ - --target "$(rustc -vV | sed -n 's/host: //p')" -- dev proxy ca uninstall +ts dev proxy ca uninstall ``` This removes the CA from the macOS keychain. Run it when you are finished — @@ -175,8 +193,9 @@ all HTML/URL rewriting to it (it prefers `X-Forwarded-Host`, then `Host`), so **first-party URLs always stay on the production domain regardless of the `Host` header**. That decouples routing (`Host`) from the first-party host. -By default `Host: ` too, which works against a Trusted Server Compute -upstream because Fastly routes by SNI (`= TO`) and passes `Host` through unchanged. +By default `Host: ` too. Fastly routes by SNI (`= TO`) and passes `Host` +through unchanged, so `Host: ` reaches the upstream — but the upstream still +has to be configured to accept it, which a dev or staging service usually isn't. **Targeting a specific server by IP.** To point at a particular server or load balancer — for example when the `TO` hostname isn't in DNS yet — keep `--to` a @@ -208,9 +227,12 @@ hostname (e.g. a Fastly Deliver service that rejects an unconfigured `Host`), pa > **Caveat with real Trusted Server adapters.** The Fastly and Spin adapter > request paths strip inbound `X-Forwarded-Host` before routing, so with > `--rewrite-host` a real Trusted Server upstream falls back to `Host` (`TO`) and -> emits first-party URLs on `TO`, not `FROM`. Prefer the default (no -> `--rewrite-host`) with Trusted Server upstreams; reach for `--rewrite-host` -> only when the upstream rejects an unconfigured `Host`. +> emits first-party URLs on `TO`, not `FROM`. Even so, `--rewrite-host` is the +> right choice for most upstreams — dev and staging services rarely have the +> production hostname configured and would reject the plain `Host: `. Drop +> it only when the upstream is configured to accept `Host: ` and you +> specifically need first-party URLs anchored to `FROM` — the `--resolve` example +> above is exactly that case. The TLS SNI is always the `TO` host either way: @@ -257,11 +279,14 @@ Options: --basic-auth-file Read USER:PASS from a file --insecure Skip upstream TLS certificate verification --upstream-plaintext Connect to upstream over plain HTTP + --connect-timeout Upstream connect timeout in seconds [default: 10] --ca-dir CA cert/key directory [default: ~/Library/Application Support/ trusted-server/dev-proxy on macOS] ``` -The tool is flags-only; there are no environment variable overrides. +The tool is flags-only; there are no environment variable overrides. The +`[COMMAND]` slot is the `ca` subcommand — see +[CA companion commands](#ca-companion-commands). ## Browser details From 7cbb2b14d27cec93168f24c842dade06c77b06c5 Mon Sep 17 00:00:00 2001 From: Aram Grigoryan <132480+aram356@users.noreply.github.com> Date: Fri, 10 Jul 2026 12:28:12 -0700 Subject: [PATCH 2/2] Address deep-review findings on the dev proxy guide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Keep `--rewrite-host` in the quick start (it is required for upstreams that reject `Host: `), but state its trade-off honestly: against a real Trusted Server adapter, first-party URLs then render on the upstream host, not the production domain. Qualify the "first-party URLs always stay on the production domain" claim accordingly — it holds only when the upstream preserves `X-Forwarded-Host`. Also: - Note that Firefox CA auto-import needs `certutil` (`brew install nss`) and qualify the troubleshooting row that assumed it is always present. - State that `--launch` opens only the first mapped hostname while every mapping is still proxied. - Note that connection options (`--rewrite-host`, auth, `--insecure`, `--upstream-plaintext`) apply to every mapping, not per-rule. - Scope the Safari "only while the proxy runs" claim to a clean exit. --- docs/guide/ts-dev-proxy.md | 51 +++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/docs/guide/ts-dev-proxy.md b/docs/guide/ts-dev-proxy.md index 6d289b7a..48f76e31 100644 --- a/docs/guide/ts-dev-proxy.md +++ b/docs/guide/ts-dev-proxy.md @@ -57,11 +57,13 @@ ts dev proxy \ --launch chrome ``` -`--rewrite-host` sends the upstream `Host: `, which most dev and staging -upstreams require — they don't have the production hostname configured and would -reject the default `Host: `. See -[Host header behavior](#host-header-behavior) for the one case where you can drop -it. +`--rewrite-host` sends the upstream `Host: ` so upstreams that reject the +default `Host: ` — most dev and staging services, which aren't configured +for the production hostname — still serve a page. Trade-off: against a real +Trusted Server adapter, first-party URLs then render on the upstream host, not the +production domain. To keep them on the production domain, point at an upstream that +accepts `Host: ` and omit `--rewrite-host` — see +[Host header behavior](#host-header-behavior). Run `ts dev proxy --help` to list every flag. @@ -78,6 +80,9 @@ ts dev proxy -f www.example-publisher.com -t trusted-server-example.edgecompute. With no `--map`/`-f`/`-t`, the proxy exits with `no rewrite rule: pass --map FROM=TO (or -f/--from with -t/--to)`. +Connection options — `--rewrite-host`, `--basic-auth`/`--basic-auth-file`, +`--insecure`, and `--upstream-plaintext` — apply to every mapping, not per-rule. + ### Explicit rule and browser launch ```bash @@ -87,7 +92,9 @@ ts dev proxy \ ``` `--launch` takes a comma list (`chrome`, `firefox`, `safari`) or `all`. When -omitted the proxy runs without opening any browser. +omitted the proxy runs without opening any browser. With multiple `--map` rules, +every mapping is proxied, but `--launch` opens only the first rule's `FROM` +hostname — navigate to the others manually. ### Other examples @@ -135,9 +142,11 @@ will trust the proxy's certificates immediately. ### Trust the CA in Firefox Firefox does not reliably consult the macOS login keychain. When you use -`--launch firefox`, the proxy automatically imports the CA into the temporary -Firefox profile's NSS database using `certutil`. If you are pointing an existing -Firefox profile at the proxy manually, run: +`--launch firefox`, the proxy imports the CA into the temporary Firefox profile's +NSS database using `certutil`. `certutil` is not built into macOS — install it +with `brew install nss`; without it the proxy prints a warning and Firefox opens +with certificate errors. If you are pointing an existing Firefox profile at the +proxy manually, run: ```bash certutil -A -n "Trusted Server DEV-ONLY Proxy CA — DO NOT TRUST IN PRODUCTION" \ @@ -190,8 +199,10 @@ trust the new CA. The proxy always sends `X-Forwarded-Host: ` (the production hostname) — the standard "original host" header for a forward proxy. Trusted Server core anchors all HTML/URL rewriting to it (it prefers `X-Forwarded-Host`, then `Host`), so -**first-party URLs always stay on the production domain regardless of the `Host` -header**. That decouples routing (`Host`) from the first-party host. +**first-party URLs stay on the production domain regardless of the `Host` header — +as long as the upstream preserves `X-Forwarded-Host`**. That decouples routing +(`Host`) from the first-party host. (Real Trusted Server adapters strip inbound +`X-Forwarded-Host`; the caveat below covers what that means with `--rewrite-host`.) By default `Host: ` too. Fastly routes by SNI (`= TO`) and passes `Host` through unchanged, so `Host: ` reaches the upstream — but the upstream still @@ -306,7 +317,7 @@ the terminal (only that command is elevated; the proxy keeps running as you). If `sudo` is declined or there is no terminal (e.g. the proxy is backgrounded), it prints the exact `networksetup` command and the System Settings path so you can set the PAC manually. The change is system-wide (all apps) but PAC-scoped to the -`FROM` hosts, and only while the proxy runs. The prior setting — including +`FROM` hosts, and — on a clean exit — only while the proxy runs. The prior setting — including whether auto-proxy was enabled or disabled — is saved and restored. On a clean exit (Ctrl-C) the restore uses `sudo` and may prompt once more if a long run outlived the cached credential. After a hard kill (`SIGKILL`) the next @@ -316,11 +327,11 @@ command. ## Troubleshooting -| Symptom | Cause | Fix | -| ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| "unknown domain" or `404` from upstream | The upstream service does not accept `Host: ` (the default). A domain can be active on only one Fastly service at a time, so you cannot add the production hostname to a dev service. | Use a Trusted Server Compute upstream (routes by SNI, not `Host`), or pass `--rewrite-host` to send `Host: `. | -| Upstream returns `401` | Upstream is behind Basic auth. | Pass `--basic-auth user:pass` or `--basic-auth-file ./creds.txt`. | -| Upstream unreachable (`502` / `503`) | Upstream service is down or the domain is not provisioned. | Verify the upstream URL and its Fastly service health. | -| Browser shows an untrusted-certificate warning | The dev CA is not trusted in the browser. | Run `ts dev proxy ca install` for Chrome and Safari. For Firefox, use `--launch firefox` (auto-imports) or run `certutil` manually (see above). After `ca regenerate`, re-trust with `ca install`. | -| Listen address already in use | Another process holds port 18080. | Pass `--listen 127.0.0.1:18081` (or another free port). | -| `--listen` rejected as non-loopback | A non-loopback address was given without the required flag. | Add `--allow-non-loopback`. | +| Symptom | Cause | Fix | +| ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| "unknown domain" or `404` from upstream | The upstream service does not accept `Host: ` (the default). A domain can be active on only one Fastly service at a time, so you cannot add the production hostname to a dev service. | Use a Trusted Server Compute upstream (routes by SNI, not `Host`), or pass `--rewrite-host` to send `Host: `. | +| Upstream returns `401` | Upstream is behind Basic auth. | Pass `--basic-auth user:pass` or `--basic-auth-file ./creds.txt`. | +| Upstream unreachable (`502` / `503`) | Upstream service is down or the domain is not provisioned. | Verify the upstream URL and its Fastly service health. | +| Browser shows an untrusted-certificate warning | The dev CA is not trusted in the browser. | Run `ts dev proxy ca install` for Chrome and Safari. For Firefox, use `--launch firefox` (auto-imports when `certutil` is installed — `brew install nss`) or run `certutil` manually (see above). After `ca regenerate`, re-trust with `ca install`. | +| Listen address already in use | Another process holds port 18080. | Pass `--listen 127.0.0.1:18081` (or another free port). | +| `--listen` rejected as non-loopback | A non-loopback address was given without the required flag. | Add `--allow-non-loopback`. |