Skip to content

fix(web): let the browser extension WS handshake past the Origin gate - #151

Merged
cnjack merged 1 commit into
mainfrom
fix/browser-extension-ws-origin-gate
Jul 18, 2026
Merged

fix(web): let the browser extension WS handshake past the Origin gate#151
cnjack merged 1 commit into
mainfrom
fix/browser-extension-ws-origin-gate

Conversation

@cnjack

@cnjack cnjack commented Jul 18, 2026

Copy link
Copy Markdown
Owner

Problem

The browser plugin (jcode Browser Bridge extension) could no longer connect.

PR #141 (commit f71f5a8) hardened corsMiddleware to return 403 for any untrusted Origin before the request reaches a handler — closing a drive-by-POST vector. But a Chrome extension always sends Origin: chrome-extension://<id> on its WebSocket handshake, which is never same-origin. As a result GET /api/browser/ext/ws was 403'd at the Origin gate before the bridge's own pairing-token auth could run, so the extension could never connect.

Fix

Exempt the extension WS endpoint (GET only, exact path) from the Origin gate, mirroring the existing isAuthExempt carve-out for the same route. Added isBrowserExtensionWS() and a comment explaining the trade-off.

Security is unchanged: the bridge still authenticates via its pairing token (bridge.go:83). The exemption is scoped to a GET upgrade endpoint that performs no cross-origin simple-request side effects, so the drive-by-POST vector PR #141 closed stays closed.

Verification (live server, real WS handshakes)

Started a local jcode web server and drove real handshakes from both the plugin's and an attacker's perspective:

Case Result
extension Origin handshake 101 (was 403 before fix) ✅
loopback (web UI) handshake 101
evil Origin → normal API (/api/config) 403
extension Origin → wrong path 403 ✅ (path-scoped)
extension Origin POST → WS path 403 ✅ (GET-only)
evil Origin → WS, no token upgrade 101, then bridge rejects with authentication required
evil Origin → WS, wrong token same rejection ✅

The last two rows confirm the pairing token remains the real authorization boundary — an untrusted origin gains nothing from the exemption.

Tests

  • New TestCORSMiddlewareExtensionWSHandshake (5 subcases, pins the regression)
  • go test ./internal/web/ ./internal/browser/ green
  • go vet + golangci-lint 0 issues; full pre-push suite green

Summary by CodeRabbit

  • Bug Fixes
    • Fixed browser extension WebSocket connections being incorrectly blocked by cross-origin security checks.
    • Extension connections to the supported WebSocket endpoint now establish successfully while other request types and endpoints remain protected.
    • Added coverage to verify that only the intended WebSocket handshake is allowed.

PR #141 hardened corsMiddleware to 403 any untrusted Origin before it
reaches a handler. But a Chrome extension always sends
'Origin: chrome-extension://<id>' on the WebSocket handshake, which is
never same-origin, so /api/browser/ext/ws was being rejected before the
bridge's own pairing-token auth could run — the extension could no longer
connect.

Exempt the extension WS endpoint (GET only, exact path) from the Origin
gate, mirroring the existing isAuthExempt carve-out. The bridge still
authenticates via its pairing token (verified: an evil-origin socket that
completes the upgrade is rejected with 'authentication required'), so the
drive-by-POST vector PR #141 closed stays closed.

Adds TestCORSMiddlewareExtensionWSHandshake to pin the regression.
@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 77169d9b-0812-4114-9d20-4c5eb921d18f

📥 Commits

Reviewing files that changed from the base of the PR and between e7ab226 and 75a0ba8.

📒 Files selected for processing (2)
  • internal/web/cors_test.go
  • internal/web/server.go

📝 Walkthrough

Walkthrough

The CORS middleware now exempts only GET requests to the browser extension WebSocket handshake endpoint from origin rejection. Tests cover extension and untrusted origins on the WebSocket path, plus blocked methods and non-WebSocket endpoints.

Changes

Browser extension WebSocket CORS exception

Layer / File(s) Summary
WebSocket origin gate and regression coverage
internal/web/server.go, internal/web/cors_test.go
corsMiddleware allows the exact GET /api/browser/ext/ws handshake to proceed past origin validation, while tests confirm other methods, paths, and blocked-origin cases remain rejected.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 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 clearly and concisely describes the main change: exempting the browser extension WebSocket handshake from the Origin gate.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ 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 fix/browser-extension-ws-origin-gate

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.

@cnjack
cnjack merged commit d933d79 into main Jul 18, 2026
4 checks passed
@cnjack
cnjack deleted the fix/browser-extension-ws-origin-gate branch July 18, 2026 04:37
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