Skip to content

Preserve browser Origin with authenticated proxy forwarding - #1251

Open
prk-Jr wants to merge 12 commits into
spec/mobile-ad-render-trace-endpointfrom
fix/dev-proxy-rewrite-origin
Open

prk-Jr wants to merge 12 commits into
spec/mobile-ad-render-trace-endpointfrom
fix/dev-proxy-rewrite-origin

Conversation

@prk-Jr

@prk-Jr prk-Jr commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Allow ts dev proxy to authenticate the browser-facing origin while preserving the browser's original Origin header. This lets trace Enable/End work when the proxy rewrites Host or sends plaintext to its upstream, without forging a trace-specific Origin.

Changes

  • Add --forwarder-secret-file. Validate a single secret of at least 32 ASCII graphic bytes before listening, redact diagnostics and mark the outgoing credential header sensitive. Credential injection requires a loopback listener.
  • On mapped intercepted requests, validate the actual browser Host or absolute-URI authority, retain its port, and stamp X-Forwarded-Host, X-Forwarded-Proto: https and X-TS-Forwarder-Auth after hop-by-hop sanitation. Conflicting or ambiguous authority is rejected; CONNECT fallback is never authenticated.
  • Remove incoming forwarding credentials from initial headers, trailer declarations and actual streaming trailer frames. Preserve benign trailers and streaming behavior.
  • Remove route-specific Origin rewriting. All Origin fields retain their original values on trace, publisher and vendor paths.
  • Document server provisioning and rollout. Add real wire tests and actual proxy-to-Axum trace/public-URL regressions alongside the existing proxy suite.

Dependency

Stacked on #1107 (spec/mobile-ad-render-trace-endpoint), which supplies optional authenticated-forwarder configuration, secret-store resolution and shared public-origin handling. The CLI includes that server branch so ts config push understands the new configuration and its secret metadata.

Configure the server's authentication header as x-ts-forwarder-auth; its shared_secret is a secret-store key, while the CLI file contains the actual matching secret. Deploy the server configuration before using the flag. Keep both PRs open: #1107 owns server trust and trace behavior; this PR owns the CLI companion.

Validation

All required local CI gates pass on the stacked branch: all eight lint gates, every adapter suite, parity, JS build/tests and formatting, native build-digest tests/lint, core documentation, native CLI suites and adapter builds including Fastly/Spin release artifacts. Final CLI results: 731 unit tests, 41 proxy wire tests and three actual proxy-to-server regressions pass. Explicit TLS provider setup makes the new shared fixture dependency deterministic; the isolated TLS wire test and serial 111-test proxy unit suite also pass. The actual ts config validate command accepts the forwarder secret-store key reference. The combined regressions use the production Axum HTTP listener behind the real proxy, covering encrypted/plaintext upstreams, rewritten/preserved Host, a public non-default port, Enable/End cookies, rejected Origins/queries/duplicates, missing credentials, server opt-out and transport fallback. Publisher tests check unchanged Origin, stripped credentials, generated public URLs and HTTPS protocol-relative signing.

Independent reviewers checked the CLI implementation, found and verified a streaming trailer credential leak fix, and reviewed the real-server fixture. Server ingress and configured identify CORS are covered by the companion PR.

Closes #1250

The new CodeQL alert #204 was independently reviewed and dismissed as a false positive. SHA-256 only creates temporary fixed-size bearer-token digests for comparison; neither digest is stored or exposed. Operator documentation requires cryptographically random token generation and distinguishes minimum length from entropy. This assessment follows CodeQL's guidance for the distinction between password storage and other hashing uses.

Standalone runtime verification: launched the actual trusted-server-axum server and ts dev proxy binaries, loading server configuration through the normal blob envelope and environment-backed secret store. All 56 live HTTPS requests passed the expected checks across plaintext/TLS upstreams with --rewrite-host on/off. Enable → separate active-state request → publisher document with active trace context → End → separate inactive-state request succeeded with unchanged Secure/HttpOnly/host-only/SameSite=Lax cookies. Public URLs retain browser HTTPS authority and port; protocol-relative signing uses HTTPS; publisher and custom Didomi routes preserve browser Origin and receive no forwarding credentials. Foreign hosts/Origins, duplicate Origin, action queries, nonempty bodies, missing/wrong credentials, and server opt-out reject without cookie mutation. Browser-to-proxy TLS uses the isolated generated CA; the self-signed local upstream TLS relay uses --insecure. An independent subagent audited the response and recorded upstream evidence. This does not establish physical mobile/browser UI or deployed-runtime acceptance.

The standalone run found a TLS-provider startup panic when workspace features enable both providers. The proxy now calls the existing idempotent provider initializer before TLS setup; a fresh-process regression requires a real PAC HTTP response. CI also exposed a redaction-test sentinel collision with a backtrace symbol; the distinct short invalid token retains the complete Report Debug redaction assertion and passes with short/full backtraces.

With --rewrite-host the proxy sends Host: TO but forwarded the browser's
Origin: https://FROM unchanged, so upstream endpoints that verify a
same-origin Origin against their own origin rejected proxied requests.
Replace a single same-origin Origin with the TO origin (scheme from
--upstream-plaintext, non-default port kept) so Origin and Host name the
same authority. Cross-site, null, plain-http and duplicated Origin values
pass through unchanged.
@prk-Jr prk-Jr self-assigned this Oct 7, 2026
Derive the first-party origin from the inbound Host so non-443 sessions
are rewritten too, and apply the rewrite only to Trusted Server's /_ts
endpoints, so publisher and integration requests keep the browser's real
Origin. Cover the behavior end to end through the proxy.
@prk-Jr
prk-Jr marked this pull request as ready for review October 7, 2026 13:28
@prk-Jr
prk-Jr requested review from ChristianPavilonis, aram356 and dhruv8sh and removed request for aram356 October 7, 2026 13:28

@ChristianPavilonis ChristianPavilonis left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Review summary

Reviewed 4ac34aaa05a36587b303c3bf9d4fc82ab5b6f4a4 against 7a0ecb4cbfa39af3d83f7e1ed2733d7eabdc4cb1. All five changed files and their affected callers and consumers were inspected. One P2 compatibility regression is posted inline.

Validation

  • cargo test --package trusted-server-cli --target x86_64-unknown-linux-gnu --lib commands::dev::proxy::: 113 passed.
  • cargo test --package trusted-server-cli --target x86_64-unknown-linux-gnu --test proxy_e2e: 33 passed.
  • cargo test --package trusted-server-cli --target x86_64-unknown-linux-gnu: passed, including 607 unit tests and the integration suites; 29 tests remained ignored.
  • cargo fmt --all -- --check, cargo clippy-cli, and cargo build-axum: passed.
  • Inline Python runtime fixtures: 24 proxy header exchanges passed; the custom Didomi route regression was reproduced through the real Axum adapter against a local Origin-validating vendor fixture.
  • All reported CI checks passed. Existing review feedback was empty.

Foreign, opaque, and duplicate Origins remained unchanged in the exercised cases. The trace handlers described in the PR are absent from this revision, so their actual Enable/End acceptance and cookie workflow were not independently verified. No repository files were modified.

Comment thread crates/trusted-server-cli/src/commands/dev/proxy/server.rs Outdated

@dhruv8sh dhruv8sh left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Summary

Scoped, well-tested fix: with --rewrite-host, same-origin Origin on /_ts requests is mapped to the TO origin so it agrees with Host. The scheme (http under --upstream-plaintext) and default-port omission line up with how the trace action check canonicalizes its expected origin from the request ingress, and keeping the rewrite out of publisher/integration paths preserves real Origin for vendors that forward it.

1 of the inline comments below carries a one-click GitHub suggestion.

Non-blocking

  • 🌱 Log when Origin is rewritten — see inline at crates/trusted-server-cli/src/commands/dev/proxy/server.rs:792
  • ⛏ Byte-exact Origin/Host comparison — see inline at crates/trusted-server-cli/src/commands/dev/proxy/server.rs:789

Cross-cutting / body-level findings

  • 📝 Depends on unmerged trace endpoints — the /_ts/trace/enable and /end routes (and their Origin check in trace/actions.rs) currently live on spec/mobile-ad-render-trace-endpoint, not main. Worth noting for anyone verifying this against main.
  • 👍 Scoping and tests — restricting the rewrite to the /_ts namespace is the right call, and the coverage (non-443 port, missing/duplicated Host, null/http/foreign/duplicate Origin, /_tsx and /_ts-foo look-alikes, plus end-to-end through the echo upstream) is thorough.

CI Status

All 22 checks PASS (cargo fmt, cargo test, cargo test (axum native), cargo test (ts CLI, native) on ubuntu/macos, cross-adapter parity, cloudflare/spin checks, vitest, format-typescript, format-docs, integration + browser integration tests, CodeQL / Analyze).

Comment thread crates/trusted-server-cli/src/commands/dev/proxy/server.rs Outdated
Comment thread crates/trusted-server-cli/src/commands/dev/proxy/server.rs Outdated
prk-Jr and others added 3 commits October 8, 2026 09:51
Match POST /_ts/trace/enable and /_ts/trace/end exactly instead of the
whole /_ts namespace, so integration routes an operator mounts under /_ts
(such as a Didomi proxy_path of _ts/consent) keep the browser's real
Origin. Log the rewrite at debug level.

@ChristianPavilonis ChristianPavilonis left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Review summary

Reviewed fbc7372bb79c8796b8cdbf956ca6408f889f4962 against 182fdf45c8a4e08fac68ea7b0b77f7f59a7284c1, from fix/dev-proxy-rewrite-origin into main. Inspected all five changed files and affected configuration, routing, forwarding, and test consumers.

No actionable new issues found.

Safety proof

  1. Only eligible trace actions receive the upstream Origin; publisher/vendor requests retain their browser Origin.

    • Highest evidence: Runtime.
    • Evidence: All 34 proxy end-to-end tests passed. An additional 36 CLI exchanges exercised plaintext upstreams, Enable/End, query handling, non-default ports, foreign/null/http/duplicate/missing Origins, missing Host, near-miss paths, publisher/vendor paths, and rewrite-host on/off.
    • Status: proven for the exercised inputs.
  2. Rewritten actions pass Trusted Server authorization without weakening its other controls.

    • Highest evidence: Path.
    • Evidence: Inspected the declared dependency #1107 at 285c75face3dc23853f053162b20fc7e18734586. Its trace actions compare Origin against trusted ingress metadata and independently require the action header, same-origin Fetch Metadata, and an empty body. Those handlers are absent from this reviewed revision.
    • Status: unproven.
    • Next check: After #1107 is integrated, exercise Enable/End and cookie observation through the proxy; verify foreign Origin and missing Fetch Metadata still return 403.

Validation and review context

  • cargo test --locked --package trusted-server-cli --target x86_64-unknown-linux-gnu --lib commands::dev::proxy::: 113 passed.
  • cargo test --locked --package trusted-server-cli --target x86_64-unknown-linux-gnu --test proxy_e2e: 34 passed.
  • cargo fmt --all -- --check and cargo clippy-cli: passed.
  • git diff 182fdf45c8a4e08fac68ea7b0b77f7f59a7284c1...fbc7372bb79c8796b8cdbf956ca6408f889f4962 --check: passed.
  • python3 - with an inline plaintext echo fixture: 36 exchanges passed. Initial certificate validation failed under Python's strict X.509 policy because the unchanged proxy certificate lacks an Authority Key Identifier. The rerun retained CA-chain and hostname verification with strict-policy enforcement disabled.
  • CI: all 22 checks successful; confirmed the test workflow ran against the locked head.
  • Existing feedback: inspected paginated reviews, inline comments, replies, issue comments, and thread resolution states. All three threads are resolved. The earlier custom Didomi-route regression is fixed and covered; no duplicate findings.
  • Residual risk: actual trace authorization and browser cookie lifecycle remain unverified pending #1107. Docs formatting was not rerun locally because Prettier is unavailable; its CI check passed.
  • No repository files were modified. Head/base revisions were rechecked before submission, and the worktree remained clean.

@prk-Jr prk-Jr added this to the 202610 milestone Oct 8, 2026

@aram356 aram356 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Summary

Translating Origin in the proxy is the right layer for this, and it works for --rewrite-host: Enable and End succeed against #1107's real handlers. The same mismatch still breaks the guide's plain-HTTP local setup, and the change depends on routes that aren't on main yet.

1 of the inline comments below carries a one-click GitHub suggestion; use Commit suggestion to apply it. The other comments describe the fix in prose because it spans several hunks or files.

Blocking

🔧 wrench

  • Plaintext upstream without --rewrite-host still gets 403: see inline at crates/trusted-server-cli/src/commands/dev/proxy/rewrite.rs:193

❓ question

  • Merge after #1107?: see Cross-cutting below

Non-blocking

  • 🤔 Hardcoded trace routes, and how far this can generalize: see inline at crates/trusted-server-cli/src/commands/dev/proxy/server.rs:764
  • ⛏ The trace actions reject query strings: see inline at docs/guide/ts-dev-proxy.md:338
  • 🏕 Origin is missing from the header tables: see Cross-cutting below

Cross-cutting / body-level findings

  • ❓ Merge after #1107? POST /_ts/trace/enable and /end don't exist on main, and #1107 is still open (head 117373e05). Merged first, the guide paragraph documents endpoints main doesn't serve, and nothing checks the hardcoded pair in requires_upstream_origin against the real routes. If #1107 renames or moves them during review, this fails closed (403) and no test notices. Can this wait for #1107, or land together with it?

  • 🏕 Origin is missing from the header tables. The spec's §8.3 table (docs/superpowers/specs/2026-06-22-ts-dev-proxy-design.md:361) and the guide's table (docs/guide/ts-dev-proxy.md:356) are where readers look for what the proxy changes, and neither lists Origin. A spec row could read:

    | `Origin` | `POST /_ts/trace/enable` and `/end` only: a single same-origin value becomes the upstream's origin (its scheme plus the `Host` sent) when that differs; otherwise unchanged | Trusted Server's trace actions require `Origin` to equal the origin they derive from their own scheme and `Host` |
  • 📝 Checked against #1107's handlers. Running #1107's Axum server (117373e05) behind this branch's proxy (c0982a5e2) with --upstream-plaintext --rewrite-host: Enable returns 200 and sets __Host-ts-console=1, End returns 200 and clears it, and a foreign Origin still returns 403.

CI Status

  • integration tests (Fastly EC lifecycle): PASS
  • browser integration tests: PASS
  • integration tests: PASS
  • prepare integration artifacts: PASS
  • cargo fmt: PASS (required)
  • cargo test: PASS (required)
  • cargo test (axum native): PASS
  • cargo test (cross-adapter parity): PASS
  • cargo test (ts CLI, native) (macos-latest): PASS
  • cargo test (ts CLI, native) (ubuntu-latest): PASS
  • cargo check (cloudflare native + wasm32-unknown-unknown): PASS
  • cargo check/build/test (spin native + wasm32-wasip1): PASS
  • vitest: PASS
  • format-typescript: PASS (required)
  • format-docs: PASS (required)
  • CLAUDE.md symlink guard: PASS
  • CodeQL: PASS
  • Analyze (rust): PASS
  • Analyze (actions): PASS
  • Analyze (python): PASS
  • Analyze (javascript-typescript): PASS (both runs)

Comment thread crates/trusted-server-cli/src/commands/dev/proxy/rewrite.rs Outdated
Comment thread crates/trusted-server-cli/src/commands/dev/proxy/server.rs Outdated
Comment thread docs/guide/ts-dev-proxy.md Outdated

@aram356 aram356 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Summary

Follow-up to my earlier review, after looking at where this fix belongs: the root cause should be fixed in Trusted Server rather than by forging Origin in the proxy. The matching change is requested on #1107.

Blocking

🔧 wrench

  • Send an authenticated forwarder header instead of rewriting Origin: see inline at crates/trusted-server-cli/src/commands/dev/proxy/server.rs:777

CI Status

  • integration tests (Fastly EC lifecycle): PASS
  • browser integration tests: PASS
  • integration tests: PASS
  • prepare integration artifacts: PASS
  • cargo fmt: PASS (required)
  • cargo test: PASS (required)
  • cargo test (axum native): PASS
  • cargo test (cross-adapter parity): PASS
  • cargo test (ts CLI, native) (macos-latest): PASS
  • cargo test (ts CLI, native) (ubuntu-latest): PASS
  • cargo check (cloudflare native + wasm32-unknown-unknown): PASS
  • cargo check/build/test (spin native + wasm32-wasip1): PASS
  • vitest: PASS
  • format-typescript: PASS (required)
  • format-docs: PASS (required)
  • CLAUDE.md symlink guard: PASS
  • CodeQL: PASS
  • Analyze (rust): PASS
  • Analyze (actions): PASS
  • Analyze (python): PASS
  • Analyze (javascript-typescript): PASS (both runs)

Comment thread crates/trusted-server-cli/src/commands/dev/proxy/server.rs Outdated
@prk-Jr prk-Jr changed the title Rewrite same-origin Origin to the upstream with --rewrite-host Preserve browser Origin with authenticated proxy forwarding Oct 9, 2026
@prk-Jr
prk-Jr changed the base branch from main to spec/mobile-ad-render-trace-endpoint October 9, 2026 09:11
@prk-Jr
prk-Jr requested a review from aram356 October 9, 2026 10:12

This branch has not been deployed

No deployments
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.

ts dev proxy --rewrite-host forwards a mismatched Origin, so same-origin POSTs are rejected

4 participants