Skip to content

fix: harden remote relay credentials and lifecycle - #136

Merged
dzianisv merged 3 commits into
mainfrom
fix/135-relay-lifecycle
Aug 14, 2026
Merged

fix: harden remote relay credentials and lifecycle#136
dzianisv merged 3 commits into
mainfrom
fix/135-relay-lifecycle

Conversation

@dzianisv

Copy link
Copy Markdown
Member

Summary

  • redact remote relay credentials from CLI, debug, and set_remote output
  • require WSS outside loopback, validate URL UUIDs, and bound handshakes
  • clear stale relay state and surface close diagnostics without retrying permanent rejection
  • serialize remote switches and safely terminate CONNECTING sockets
  • add comprehensive relay lifecycle and regression coverage

Verification

  • npm run build
  • npx tsc --noEmit
  • npm run test:e2e:remote-lifecycle
  • npm test

Fixes #135
Parent: #130

@dzianisv

Copy link
Copy Markdown
Member Author

Addressed all independent review findings in 40a01d6:

  • explicit permanent WS close allowlist includes relay 4004 unknown and 4009 revoked; HTTP policy keeps 408/429 retryable
  • lifecycle/config epochs prevent stale reconnect attempts from owning or replacing the final socket
  • relay and transport errors redact full targets and UUIDs case-insensitively at connection, CLI, and MCP boundaries
  • stop() invalidates active and queued set_remote operations
  • HTTP handshake diagnostics survive the synthetic 1006 close

Regression coverage includes policy matrices, active CONNECTING reconnect vs set_remote, queued cancellation, mixed-case untrusted relay errors through CLI/MCP, and HTTP diagnostic preservation.

Local verification: npm run build, npx tsc --noEmit, npm run test:e2e:remote-lifecycle, and full npm test pass.

@dzianisv

Copy link
Copy Markdown
Member Author

Follow-up review fixes are pushed in 848f1c8191248e59c450023ca30bdbae1b98bdb0.

  • redact relay-originated tool_result error payloads recursively before CLI/MCP output
  • reject malformed non-string relay errors deterministically instead of orphaning pending requests
  • make repeated/concurrent start() calls idempotent so they cannot invalidate the live socket lifecycle
  • make browser CLI error-valued tool results fail closed with nonzero exit

Focused regressions now exercise malformed relay errors, mixed-case UUID leakage through tool_result, repeated/concurrent start plus reconnect, browser CLI output, and MCP HTTP output.

Post-commit verification passed: npm run build, npx tsc --noEmit, npm run test:e2e:remote-lifecycle, npm run test:e2e:browser-cli, npm run test:e2e:http, and full npm test. CI is queued. No merge performed.

@dzianisv
dzianisv force-pushed the fix/135-relay-lifecycle branch from 848f1c8 to 77f87aa Compare August 14, 2026 17:48
dzianisv added a commit that referenced this pull request Aug 14, 2026
Users are shown one canonical connector credential in the extension
(https://relay.api.vibebrowser.app/mcp/<uuid>). That string could be pasted
into Claude/ChatGPT connectors but not into the CLI, which forced users to
learn a second transport URL for the same bearer capability.

`--remote` (start, openclaw, browser CLI) and the MCP `set_remote` tool now
accept it, deriving the relay target deterministically through the single
shared normalizer `parseRemoteTarget`:

  https://host[/prefix]/mcp/<uuid>      -> wss://host[/prefix] + <uuid>
  http://<loopback>[/prefix]/mcp/<uuid> -> ws://<loopback>[/prefix] + <uuid>

Bare UUID and ws(s) relay URL forms are unchanged.

All #136 hardening is preserved: valid UUID only, no credentials/query/
fragment, no non-loopback plaintext relay transport, and no bearer value in
errors, logs, help text, or CLI output. Generic HTTP(S) URLs without the
exact /mcp/<uuid> suffix are rejected.

Tests: connector normalization matrix (self-hosted path prefixes, ports,
loopback forms), malformed/hostile URL rejections with leak assertions, a
live set_remote connect over the connector form, a browser-CLI status run
through an http connector URL, and a packaged-CLI help gate.

Refs #145, #130

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@dzianisv
dzianisv merged commit 1730d2e into main Aug 14, 2026
1 check passed
@dzianisv
dzianisv deleted the fix/135-relay-lifecycle branch August 14, 2026 18:29
dzianisv added a commit that referenced this pull request Aug 14, 2026
Users are shown one canonical connector credential in the extension
(https://relay.api.vibebrowser.app/mcp/<uuid>). That string could be pasted
into Claude/ChatGPT connectors but not into the CLI, which forced users to
learn a second transport URL for the same bearer capability.

`--remote` (start, openclaw, browser CLI) and the MCP `set_remote` tool now
accept it, deriving the relay target deterministically through the single
shared normalizer `parseRemoteTarget`:

  https://host[/prefix]/mcp/<uuid>      -> wss://host[/prefix] + <uuid>
  http://<loopback>[/prefix]/mcp/<uuid> -> ws://<loopback>[/prefix] + <uuid>

Bare UUID and ws(s) relay URL forms are unchanged.

All #136 hardening is preserved: valid UUID only, no credentials/query/
fragment, no non-loopback plaintext relay transport, and no bearer value in
errors, logs, help text, or CLI output. Generic HTTP(S) URLs without the
exact /mcp/<uuid> suffix are rejected.

Tests: connector normalization matrix (self-hosted path prefixes, ports,
loopback forms), malformed/hostile URL rejections with leak assertions, a
live set_remote connect over the connector form, a browser-CLI status run
through an http connector URL, and a packaged-CLI help gate.

Refs #145, #130

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
dzianisv added a commit that referenced this pull request Aug 14, 2026
…147)

* feat: accept the HTTPS /mcp/<uuid> connector URL in --remote (#145)

Users are shown one canonical connector credential in the extension
(https://relay.api.vibebrowser.app/mcp/<uuid>). That string could be pasted
into Claude/ChatGPT connectors but not into the CLI, which forced users to
learn a second transport URL for the same bearer capability.

`--remote` (start, openclaw, browser CLI) and the MCP `set_remote` tool now
accept it, deriving the relay target deterministically through the single
shared normalizer `parseRemoteTarget`:

  https://host[/prefix]/mcp/<uuid>      -> wss://host[/prefix] + <uuid>
  http://<loopback>[/prefix]/mcp/<uuid> -> ws://<loopback>[/prefix] + <uuid>

Bare UUID and ws(s) relay URL forms are unchanged.

All #136 hardening is preserved: valid UUID only, no credentials/query/
fragment, no non-loopback plaintext relay transport, and no bearer value in
errors, logs, help text, or CLI output. Generic HTTP(S) URLs without the
exact /mcp/<uuid> suffix are rejected.

Tests: connector normalization matrix (self-hosted path prefixes, ports,
loopback forms), malformed/hostile URL rejections with leak assertions, a
live set_remote connect over the connector form, a browser-CLI status run
through an http connector URL, and a packaged-CLI help gate.

Refs #145, #130

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore(release): bump @vibebrowser/mcp to 0.3.4 and @vibebrowser/cli to 0.3.2

npm already serves @vibebrowser/mcp@0.3.3 and @vibebrowser/cli@0.3.1, so
merging #146 as-is would be a no-op for the publish workflow. Bump both
packages and sync every mirrored version reference the plugin-bundle
contract test asserts against package.json (server.json, mcpb/manifest.json,
plugin.json, .mcp.json pin, marketplace.json). package-lock.json was
regenerated via `npm version --no-git-tag-version`; no dependency changes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(remote): redact every UUID path segment from remote targets

A malformed-but-parseable connector URL such as
https://host/mcp/<u1>/mcp/<u2> parses to relay base wss://host/mcp/<u1>
and uuid <u2>. redactRemoteTarget only masked the full target and the
final path segment, so <u1> leaked through any message that echoed the
derived relay base.

Redact every UUID-shaped path segment (raw and percent-decoded), while
preserving full-target redaction, verbatim final-segment redaction and
case-insensitive matching.

Adds deterministic e2e-remote-lifecycle regressions for double-/mcp/
redaction, loopback-bypass hostnames rejecting plaintext, numeric
loopback canonicalization, and encoded/path-confusion accept/reject
outcomes. All UUIDs used are synthetic.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

Harden remote relay credentials and WebSocket lifecycle

1 participant