Skip to content

fix(tlsnotary): hostname-based scheme, default /tlsn path for public …#819

Open
Shitikyan wants to merge 1 commit into
stabilisationfrom
feat/tlsn-reverse-proxy-url
Open

fix(tlsnotary): hostname-based scheme, default /tlsn path for public …#819
Shitikyan wants to merge 1 commit into
stabilisationfrom
feat/tlsn-reverse-proxy-url

Conversation

@Shitikyan
Copy link
Copy Markdown
Contributor

@Shitikyan Shitikyan commented May 12, 2026

…bases

EXPOSED_URL doubles as the public RPC endpoint advertised to peers for omniprotocol port discovery, so it must stay as http://host:53550 — peers fail otherwise. The previous protocol-based scheme switch forced ws://host:7047 for that base, which can't be reached through the reverse proxy.

Switch buildWsUrl to a hostname-based rule:

  • localhost / 127.0.0.1 → ws://: (dev, raw port)
  • anything else → wss:////, path defaulting
    to /tlsn when not supplied

Behaviour:
http://localhost:53550 → ws://localhost:7047
http://127.0.0.1:53550 → ws://127.0.0.1:7047
http://node2.demos.sh:53550 → wss://node2.demos.sh/tlsn/7047/
https://node2.demos.sh/tlsn → wss://node2.demos.sh/tlsn/7047/

Public bases now always route through TLS — assumes the reverse proxy terminates HTTPS at hostname:443 (standard for the production deploy).

Summary by CodeRabbit

  • Bug Fixes
    • Improved proxy connection handling to better support both local and remote server configurations.

Review Change Stack

…bases

EXPOSED_URL doubles as the public RPC endpoint advertised to peers for
omniprotocol port discovery, so it must stay as http://host:53550 — peers
fail otherwise. The previous protocol-based scheme switch forced
ws://host:7047 for that base, which can't be reached through the reverse
proxy.

Switch buildWsUrl to a hostname-based rule:
- localhost / 127.0.0.1  → ws://<host>:<port>      (dev, raw port)
- anything else          → wss://<host><path>/<port>/, path defaulting
                          to /tlsn when not supplied

Behaviour:
  http://localhost:53550        → ws://localhost:7047
  http://127.0.0.1:53550        → ws://127.0.0.1:7047
  http://node2.demos.sh:53550   → wss://node2.demos.sh/tlsn/7047/
  https://node2.demos.sh/tlsn   → wss://node2.demos.sh/tlsn/7047/

Public bases now always route through TLS — assumes the reverse proxy
terminates HTTPS at hostname:443 (standard for the production deploy).
@qodo-code-review
Copy link
Copy Markdown
Contributor

ⓘ You've reached your Qodo monthly free-tier limit. Reviews pause until next month — upgrade your plan to continue now, or link your paid account if you already have one.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 12, 2026

Walkthrough

The PR modifies WebSocket URL construction in the TLS Notary proxy manager. The buildWsUrl function now distinguishes between local bases (localhost/127.0.0.1) and remote bases, applying direct ws:// connections for local targets and secure wss:// reverse-proxy routing with pathname-derived paths for remote targets.

Changes

WebSocket URL Construction

Layer / File(s) Summary
Local vs. remote proxy URL routing
src/features/tlsnotary/proxyManager.ts
buildWsUrl updated to detect local bases (localhost, 127.0.0.1) and return direct ws:// URLs; for remote bases, always use wss:// with reverse-proxy path routing derived from the base pathname, defaulting to /tlsn when pathname is empty.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Suggested labels

Possible security concern, Review effort 2/5

Suggested reviewers

  • cwilvx

Poem

🐰 A whisker-twitch through local lanes,
Direct as rabbit warren chains—
But distant hosts through tunnels roam,
Secure paths leading safely home.
With /tlsn the default way,
Your WebSocket will safely play! 🔐

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 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: switching to hostname-based scheme detection and introducing a default /tlsn path for public endpoints in the buildWsUrl function.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/tlsn-reverse-proxy-url

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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
src/features/tlsnotary/proxyManager.ts (2)

177-188: 💤 Low value

Consider treating IPv6 loopback [::1] as local too.

When a developer points EXPOSED_URL (or a request origin) at http://[::1]:53550, url.hostname resolves to [::1] (brackets included per WHATWG URL), so the function falls through to the public branch and produces wss://[::1]/tlsn/<port>/ — not reachable in local dev. Trivial to include alongside localhost/127.0.0.1.

♻️ Suggested adjustment
-    const isLocal =
-        url.hostname === "localhost" || url.hostname === "127.0.0.1"
+    const isLocal =
+        url.hostname === "localhost" ||
+        url.hostname === "127.0.0.1" ||
+        url.hostname === "[::1]"
🤖 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 `@src/features/tlsnotary/proxyManager.ts` around lines 177 - 188, The
buildWsUrl function treats only "localhost" and "127.0.0.1" as local; update its
local-check to also recognize the IPv6 loopback by either testing for
url.hostname === "[::1]" or by normalizing brackets (e.g. strip leading/trailing
'[' ']' and compare to "::1") so addresses like http://[::1]:53550 are treated
as local and return a ws:// URL; modify the isLocal condition in buildWsUrl
accordingly.

190-195: 💤 Low value

Stale JSDoc example for getPublicUrl.

The @returns example still reads "ws://node.demos.sh:55123", but for any non-local base this function now returns a wss://<host>/tlsn/<port>/ form. Worth updating so the doc reflects the new public-base shape (the local example is still accurate).

📝 Suggested doc tweak
 /**
  * Build the public WebSocket URL for the proxy
  * `@param` localPort - Local port the proxy is listening on
  * `@param` requestOrigin - Optional request origin for auto-detection
- * `@returns` WebSocket URL like "ws://node.demos.sh:55123"
+ * `@returns` WebSocket URL — `ws://localhost:<port>` for local bases,
+ *          `wss://<host>/tlsn/<port>/` (or `/<basePath>/<port>/`) for public bases
  */
🤖 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 `@src/features/tlsnotary/proxyManager.ts` around lines 190 - 195, Update the
stale JSDoc for getPublicUrl to reflect the new public-base URL shape: keep the
local example (ws://<host>:<port>) but change the non-local example to show the
current form returned for non-local bases, e.g. wss://<host>/tlsn/<port>/, so
the `@returns` accurately documents both local and non-local outputs from
getPublicUrl.
🤖 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 `@src/features/tlsnotary/proxyManager.ts`:
- Around line 177-188: The buildWsUrl function treats only "localhost" and
"127.0.0.1" as local; update its local-check to also recognize the IPv6 loopback
by either testing for url.hostname === "[::1]" or by normalizing brackets (e.g.
strip leading/trailing '[' ']' and compare to "::1") so addresses like
http://[::1]:53550 are treated as local and return a ws:// URL; modify the
isLocal condition in buildWsUrl accordingly.
- Around line 190-195: Update the stale JSDoc for getPublicUrl to reflect the
new public-base URL shape: keep the local example (ws://<host>:<port>) but
change the non-local example to show the current form returned for non-local
bases, e.g. wss://<host>/tlsn/<port>/, so the `@returns` accurately documents both
local and non-local outputs from getPublicUrl.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1ca21d65-ee12-4728-a3b1-896c0307fb18

📥 Commits

Reviewing files that changed from the base of the PR and between 82b5841 and bf9f6ff.

📒 Files selected for processing (1)
  • src/features/tlsnotary/proxyManager.ts

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 12, 2026

Greptile Summary

Switches buildWsUrl from a protocol-based scheme selector to a hostname-based one, so that EXPOSED_URL (used for peer discovery) stays as http://host:port while still producing correct wss:// reverse-proxy URLs for public nodes.

  • localhost / 127.0.0.1 continue to get direct ws://<host>:<port> URLs; all other hostnames now unconditionally produce wss://<hostname><path>/<port>/, defaulting the path to /tlsn when the base URL carries no explicit path.
  • The old scheme relied on the incoming URL's protocol (https:wss:), which broke when EXPOSED_URL was http://host:53550 (peers need that form) but the TLSNotary WebSocket still needed to go through the TLS reverse proxy.

Confidence Score: 4/5

Safe to merge for the described production topology; the edge cases (IPv6 loopback, private-network IPs) only affect non-standard dev environments.

The hostname-based switch correctly solves the EXPOSED_URL problem for the documented production setup. The two gaps — IPv6 ::1 generating a structurally invalid URL, and RFC-1918 addresses unexpectedly routing through wss:// — are real but narrow. No tests exist for buildWsUrl, so regressions on those paths would be silent.

src/features/tlsnotary/proxyManager.ts — specifically the isLocal guard in buildWsUrl and the absence of any unit tests for the URL-building logic.

Important Files Changed

Filename Overview
src/features/tlsnotary/proxyManager.ts Replaces protocol-based ws/wss scheme selection in buildWsUrl with a hostname-based rule: localhost/127.0.0.1 → direct ws://, everything else → path-routed wss:// with /tlsn default path. IPv6 loopback (::1) and private RFC-1918 addresses are not covered by the isLocal guard.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["buildWsUrl(base, port)"] --> B["new URL(base)"]
    B --> C{"hostname ==\n'localhost' or\n'127.0.0.1'?"}
    C -- Yes --> D["ws://hostname:port\n(direct, dev)"]
    C -- No --> E["path = pathname || '/tlsn'"]
    E --> F["wss://hostname + path + '/' + port + '/'"]

    G["getPublicUrl(localPort, requestOrigin?)"] --> H{requestOrigin\nprovided?}
    H -- Yes --> I["buildWsUrl(requestOrigin, localPort)"]
    H -- No --> J{EXPOSED_URL set?}
    J -- Yes --> K["buildWsUrl(EXPOSED_URL, localPort)"]
    J -- No --> L["buildWsUrl(sharedState.exposedUrl, localPort)"]
    L --> M["Fallback: ws://localhost:port"]
Loading

Comments Outside Diff (1)

  1. src/features/tlsnotary/proxyManager.ts, line 69 (link)

    P2 Stale JSDoc example in ProxyInfo

    The field comment still shows "ws://node.demos.sh:55123" (the old direct-port format), but public hosts now get path-routed wss:// URLs like wss://node.demos.sh/tlsn/55123/. Minor doc drift, but it can mislead callers inspecting the interface.

Reviews (1): Last reviewed commit: "fix(tlsnotary): hostname-based scheme, d..." | Re-trigger Greptile

Comment on lines +179 to +180
const isLocal =
url.hostname === "localhost" || url.hostname === "127.0.0.1"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 The isLocal check omits the IPv6 loopback address ::1. When a base URL like http://[::1]:53550 is passed, url.hostname is ::1, which falls through to the public branch and produces wss://::1/tlsn/7047/. That string is an invalid URL (IPv6 literals in URLs require brackets), so any downstream new URL(...) call or WebSocket constructor on the result will throw.

Suggested change
const isLocal =
url.hostname === "localhost" || url.hostname === "127.0.0.1"
const isLocal =
url.hostname === "localhost" ||
url.hostname === "127.0.0.1" ||
url.hostname === "::1"

Comment on lines +179 to 188
const isLocal =
url.hostname === "localhost" || url.hostname === "127.0.0.1"

if (isLocal) {
return `ws://${url.hostname}:${port}`
}

const path = url.pathname.replace(/\/+$/, "") || "/tlsn"
return `wss://${url.hostname}${path}/${port}/`
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Private RFC-1918 IPs routed through wss://

Any base URL whose hostname is a private-network address (e.g. 192.168.x.x, 10.x.x.x, 172.16–31.x.x) is not localhost/127.0.0.1, so the new code treats it as a public host and emits a wss:// path-routed URL. Without a TLS-terminating reverse proxy on those hosts the resulting WebSocket URL is unreachable, breaking LAN / on-premise development setups. Consider extending isLocal (or a separate isPrivate guard) for RFC-1918 ranges, or documenting that those deployments must also set up a reverse proxy.

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