Skip to content

feat(server): add --public-url for pairing behind an external proxy - #7

Merged
Fryuni merged 8 commits into
mainfrom
t3code/configurable-public-url-pairing
Sep 14, 2026
Merged

Fryuni merged 8 commits into
mainfrom
t3code/configurable-public-url-pairing

Conversation

@Fryuni

@Fryuni Fryuni commented Sep 13, 2026 •

Copy link
Copy Markdown
Owner

What Changed

Adds a --public-url flag (and T3CODE_PUBLIC_URL env var) to the server that advertises an external HTTP(S) origin for pairing without changing the bind address. When set, the server:

  • uses the public URL for the startup pairing link and QR code, later npx t3 pair calls, and serverGetConfig's advertised config
  • treats itself as remote-reachable for auth policy, even when bound to loopback
  • names session cookies from its persisted environment identity instead of the internal port, so a restart that lands on a different internal port behind an unchanged proxy origin does not silently log paired browsers out
  • persists the public URL in server runtime state
  • surfaces a "Public URL" endpoint as the default shareable QR endpoint in Settings → Connections

The value is validated as an HTTP(S) origin — no path, query, fragment, or credentials. Documented in docs/user/remote-access.md under a new "External reverse proxy" section.

Why

Running T3 Code behind an external reverse proxy previously had no first-class story: pairing links were built from the bind address, so a loopback-bound server behind a proxy produced unreachable pairing URLs and a misleading "only this machine can connect" auth policy. An explicit advertised origin keeps the security posture of a loopback listener while emitting correct, reachable pairing addresses. Scope stays tight: no tunnel is started, and --tailscale pairing is unchanged.

Development servers keep their port-scoped cookie names. A dev server stays reachable at localhost:<port>, and parallel worktree servers on one machine would otherwise clobber each other's session cookie on that shared hostname.

UI Changes

Settings → Connections lists a "Public URL" endpoint, marked as the default and QR-shareable when reachable. The Network access row names that endpoint rather than reading "Limited to this machine.", which is the state a proxied server sits in while local network exposure stays off. The disabled-toggle description now suggests --public-url for proxy setups.

Before/after captures use the actual Settings components in Chromium against an isolated development server configured with T3CODE_PUBLIC_URL=https://t3.example.com. Desktop bridge fixtures supply the local-network exposure state and local/LAN endpoint inventory. The before captures use ConnectionsSettings.tsx from merge-base 665746a; after captures use ac2dbd6. Both use the same current backend, authenticated session, viewport, and fixture data. These demonstrate the rendered desktop Settings behavior, not a live external proxy or native Electron/WSL integration test.

With local network access off, the before state says “Limited to this machine.” The after state names the public URL and shows authorized clients.

Before: local network off After: local network off
Before: local-only Settings After: public proxy shown with local network off

With local network access on and the endpoint list expanded, the after state adds Public URL and selects it as the default.

Before: expanded endpoints After: expanded endpoints
Before: local and LAN endpoints After: Public URL is the default endpoint

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Original implementation worked on by GLM (glm-5.3-flash) via opencode. Review follow-ups worked on by claude-opus-5 via Codex in T3 Code.

Review fixes and screenshot verification completed with GPT-6 via Codex.

- Servers can advertise an HTTP(S) origin (--public-url / T3CODE_PUBLIC_URL) while staying bound to loopback
- Startup links, QR codes, `npx t3 pair`, and Settings → Connections prefer the public URL
- Auth policy treats a public URL as remote-reachable
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 13, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-14T16:46:36.443077Z ac2dbd6 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7b31aeaece

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/server/src/auth/EnvironmentAuthPolicy.ts Outdated
A server bound to loopback and advertised through --public-url is
remote-reachable for auth policy, but cookie naming still looked only at
the bind address, so it kept the loopback form containing the internal
port. Web mode resolves its port with findAvailablePort, so a restart can
land on a different internal port behind an unchanged proxy origin and
every paired browser silently appears logged out.

Resolve reachability once in isRemoteReachableServer and use it for both
the auth descriptor and the session cookie names, so proxied servers get
the stable environment-identity name.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9613345112

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/server/src/auth/utils.ts Outdated
Comment thread apps/web/src/components/settings/ConnectionsSettings.tsx
A server behind an external proxy keeps local network exposure off, so
renderNetworkAccessRow branched to "Limited to this machine." and dropped
NetworkAccessDescription even though visibleDesktopNetworkAdvertisedEndpoints
now carries the advertised Public URL endpoint. That contradicted the
Authorized clients section, which already treats the same server as remotely
reachable, and left no way to expand the endpoint list.

Render the description whenever the default advertised endpoint is
non-loopback. Tailscale-only and loopback public URLs keep the local-only
wording.

Fryuni commented Sep 13, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a029d9ae53

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/server/src/auth/utils.ts
The desktop branch returned the port-scoped cookie name before reachability
was considered, so a desktop backend advertised through --public-url kept a
name tied to whichever port DesktopApp's upward scan from 3773 happened to
pick. A restart onto a different port behind an unchanged proxy origin signed
remote browsers out.

Proxied desktop backends now use the environment-identity name, which
separates local instances just as well as the port did. Ordinary desktop
access is untouched, including network-exposed backends, whose cookie names
predate --public-url and must not change.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: efe2b8e834

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/web/src/components/settings/pairingUrls.ts
withPublicUrlEndpoint labeled every non-loopback --public-url "public", so a
proxy on a private address such as http://192.168.1.42:8080 got the
"Reachable from anywhere" share hint while only being reachable on the LAN.

Classify private IPv4 ranges, IPv6 unique-local and link-local addresses, and
mDNS .local names as "lan". They stay QR-shareable, since isQrShareableEndpoint
only excludes loopback endpoints.

Fryuni commented Sep 13, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 616c3a1f4d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/server/src/cli/config.ts
Comment thread apps/web/src/components/settings/pairingUrls.ts Outdated
A --public-url on a Tailscale address fell through to "public", so the share
hint claimed "Reachable from anywhere" for an endpoint that only resolves
inside the tailnet.

Classify 100.64.0.0/10 as private-network, matching how tailscaleEndpointProvider
already labels the same addresses.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3530914388

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/web/src/components/settings/pairingUrls.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e192909eca

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/desktop/src/backend/DesktopBackendConfiguration.ts Outdated
Comment thread apps/web/src/components/settings/pairingUrls.ts Outdated
@Fryuni

Fryuni commented Sep 14, 2026

Copy link
Copy Markdown
Owner Author

[from Codex]: @codex review — retrying the failed review on ac2dbd6. All published threads are resolved; focused tests, desktop/web typechecks, lint, and formatting pass.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ac2dbd6e91

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/web/src/components/settings/ConnectionsSettings.tsx
@Fryuni
Fryuni merged commit 3bd5d51 into main Sep 14, 2026
@Fryuni
Fryuni deleted the t3code/configurable-public-url-pairing branch September 14, 2026 16:57
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