Securely connect the browser to a local model (0289) - #439
Merged
crs48 merged 12 commits intoJul 9, 2026
Conversation
added 11 commits
July 9, 2026 07:21
… model Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: xNet Test <test@xnet.dev>
The loopback chat daemon (:31416) now requires a per-launch pairing token (Authorization: Bearer, constant-time compared) on /v1/chat/completions and /run, and validates the Host header to reject DNS-rebinding requests before origin/token checks. /health stays unauthenticated so detection works before pairing. Exploration 0289. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: xNet Test <test@xnet.dev>
Mirror the agent-bridge anti-DNS-rebinding gate: reject any request whose Host isn't the exact loopback authority before origin/token checks. Exploration 0289. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: xNet Test <test@xnet.dev>
…very Electron starts the bridge with allowedOrigins (app.xnet.fyi + XNET_BRIDGE_ALLOWED_ORIGINS) and forwards the pairing token to its renderer over IPC status (never HTTP). `xnet bridge serve` prints the pairing code and gains --token to pin it. Exploration 0289. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: xNet Test <test@xnet.dev>
…ver tier detectConnectors accepts appOrigin and bakes the precise `OLLAMA_ORIGINS=<origin> ollama serve` line (never a wildcard) into the local-server setup hint. Exploration 0289. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: xNet Test <test@xnet.dev>
Thread the bridge pairing code through the connector (Authorization: Bearer), add a pairing-code field (auto-filled over IPC under Electron), query the Chrome loopback-network permission and hint on denial, pass appOrigin so the local-server hint names the exact OLLAMA_ORIGINS line, and add 127.0.0.1 to the CSP connect-src. Exploration 0289. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: xNet Test <test@xnet.dev>
openAiChatAgent forwards the conversation to an upstream OpenAI-compatible server (Ollama/LM Studio/vLLM); `xnet bridge serve --upstream <url>` routes a raw local model through the authenticated, origin-locked, Host-validated bridge instead of the user weakening the model server's own CORS. Exploration 0289. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: xNet Test <test@xnet.dev>
… minor) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: xNet Test <test@xnet.dev>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: xNet Test <test@xnet.dev>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: xNet Test <test@xnet.dev>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: xNet Test <test@xnet.dev>
Contributor
🖼️ UI changes in this PRNo visual differences detected in the changed UI. |
Contributor
|
Preview removed for PR #439. |
crs48
deleted the
claude/0289-securely-connecting-the-browser-to-a-local-model
branch
July 9, 2026 15:40
crs48
added a commit
that referenced
this pull request
Jul 10, 2026
This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated. # Releases ## @xnetjs/devkit@1.0.0 ### Major Changes - [#439](#439) [`677856e`](677856e) Thanks [@crs48](https://github.com/crs48)! - Secure the browser↔local-model bridge (exploration 0289). - **`@xnetjs/devkit` (breaking):** the agent bridge daemon now **requires a per-launch pairing token** (`Authorization: Bearer <token>`, constant-time compared) on its data endpoints (`/v1/chat/completions`, `/run`) and validates the `Host` header to reject DNS-rebinding requests. `BridgeServerConfig` gains `pairingToken?`, `BridgeServerHandle` exposes `pairingToken`, and a token is auto-generated when none is supplied — so a client that previously called the data endpoints with no auth now gets `401`. `/health` stays unauthenticated so detection still works before pairing. New `openAiChatAgent` lets the bridge front a raw OpenAI-compatible model server (Ollama/LM Studio) through the same authenticated door. - **`@xnetjs/plugins`:** `ConnectorEnv` gains `appOrigin` and the local-server setup hint now names the exact `OLLAMA_ORIGINS=<origin>` line (never a wildcard); new `localServerSetupHint` export; the MCP HTTP transport now validates the `Host` header (defense-in-depth, no change for legitimate callers). Additive. - **`@xnetjs/cli`:** `xnet bridge serve` prints the pairing code and gains `--token` (pin the code) and `--upstream` / `--upstream-model` (front a raw local model). Additive. ### Patch Changes - [#446](#446) [`10c9f87`](10c9f87) Thanks [@crs48](https://github.com/crs48)! - Isolate git subprocesses from inherited repo-location env. When the dev loop (or its tests) ran while a git hook was active — e.g. husky `pre-push` running `pnpm test` — the hook's exported `GIT_DIR`/`GIT_WORK_TREE`/`GIT_INDEX_FILE` leaked into `git` children and overrode the explicit `cwd`, so operations (`config`, `commit`, even `push`) targeted the hook's repo instead of the requested worktree. `NodeCommandRunner` now scrubs git's repo-location env vars for `git` invocations so `cwd` is always authoritative; an explicit `options.env` entry still wins. ## @xnetjs/cli@0.1.0 ### Minor Changes - [#439](#439) [`677856e`](677856e) Thanks [@crs48](https://github.com/crs48)! - Secure the browser↔local-model bridge (exploration 0289). - **`@xnetjs/devkit` (breaking):** the agent bridge daemon now **requires a per-launch pairing token** (`Authorization: Bearer <token>`, constant-time compared) on its data endpoints (`/v1/chat/completions`, `/run`) and validates the `Host` header to reject DNS-rebinding requests. `BridgeServerConfig` gains `pairingToken?`, `BridgeServerHandle` exposes `pairingToken`, and a token is auto-generated when none is supplied — so a client that previously called the data endpoints with no auth now gets `401`. `/health` stays unauthenticated so detection still works before pairing. New `openAiChatAgent` lets the bridge front a raw OpenAI-compatible model server (Ollama/LM Studio) through the same authenticated door. - **`@xnetjs/plugins`:** `ConnectorEnv` gains `appOrigin` and the local-server setup hint now names the exact `OLLAMA_ORIGINS=<origin>` line (never a wildcard); new `localServerSetupHint` export; the MCP HTTP transport now validates the `Host` header (defense-in-depth, no change for legitimate callers). Additive. - **`@xnetjs/cli`:** `xnet bridge serve` prints the pairing code and gains `--token` (pin the code) and `--upstream` / `--upstream-model` (front a raw local model). Additive. ### Patch Changes - Updated dependencies [[`dd3b1cb`](dd3b1cb), [`853d849`](853d849), [`10c9f87`](10c9f87), [`677856e`](677856e)]: - @xnetjs/plugins@0.8.0 - @xnetjs/runtime@0.2.0 - @xnetjs/devkit@1.0.0 - @xnetjs/data@0.8.0 - @xnetjs/sqlite@0.8.0 - @xnetjs/sync@0.8.0 - @xnetjs/identity@0.8.0 - @xnetjs/crypto@0.8.0 - @xnetjs/core@0.8.0 ## @xnetjs/plugins@0.8.0 ### Minor Changes - [#420](#420) [`dd3b1cb`](dd3b1cb) Thanks [@crs48](https://github.com/crs48)! - Single-shell layout primitives (exploration 0284): `createDefaultTree()` and `DEFAULT_WORKSPACE_ID` join the workspace layout API — the one canonical tree (a sectioned sidebar in the rail, the full left dock, tabs on) that replaces the quiet/calm/bench preset trichotomy. Purely additive: `createPresetTree` and the preset ids remain for the devtools seed and portable-workspace round-trips. - [#439](#439) [`677856e`](677856e) Thanks [@crs48](https://github.com/crs48)! - Secure the browser↔local-model bridge (exploration 0289). - **`@xnetjs/devkit` (breaking):** the agent bridge daemon now **requires a per-launch pairing token** (`Authorization: Bearer <token>`, constant-time compared) on its data endpoints (`/v1/chat/completions`, `/run`) and validates the `Host` header to reject DNS-rebinding requests. `BridgeServerConfig` gains `pairingToken?`, `BridgeServerHandle` exposes `pairingToken`, and a token is auto-generated when none is supplied — so a client that previously called the data endpoints with no auth now gets `401`. `/health` stays unauthenticated so detection still works before pairing. New `openAiChatAgent` lets the bridge front a raw OpenAI-compatible model server (Ollama/LM Studio) through the same authenticated door. - **`@xnetjs/plugins`:** `ConnectorEnv` gains `appOrigin` and the local-server setup hint now names the exact `OLLAMA_ORIGINS=<origin>` line (never a wildcard); new `localServerSetupHint` export; the MCP HTTP transport now validates the `Host` header (defense-in-depth, no change for legitimate callers). Additive. - **`@xnetjs/cli`:** `xnet bridge serve` prints the pairing code and gains `--token` (pin the code) and `--upstream` / `--upstream-model` (front a raw local model). Additive. ### Patch Changes - Updated dependencies []: - @xnetjs/data@0.8.0 - @xnetjs/abuse@0.8.0 - @xnetjs/core@0.8.0 ## @xnetjs/runtime@0.2.0 ### Minor Changes - [#448](#448) [`853d849`](853d849) Thanks [@crs48](https://github.com/crs48)! - `NodeStoreSyncProvider` now handles hub capacity rejections gracefully: on the first `QUOTA_EXCEEDED` (over the hub's per-user cap) or `STORAGE_FULL` (hub disk full) rejection it pauses outbound sync instead of re-flooding the hub, keeps local data intact, and resumes on the next reconnect. Subscribe to the new `onSyncBlocked(listener)` API (with `SyncBlockedReason`/`SyncBlockedListener` types) to surface a "storage full" notice in your app. ### Patch Changes - Updated dependencies [[`dd3b1cb`](dd3b1cb), [`677856e`](677856e)]: - @xnetjs/plugins@0.8.0 - @xnetjs/history@0.8.0 - @xnetjs/data-bridge@0.8.0 - @xnetjs/data@0.8.0 - @xnetjs/storage@0.8.0 - @xnetjs/sync@0.8.0 - @xnetjs/identity@0.8.0 - @xnetjs/crypto@0.8.0 - @xnetjs/core@0.8.0 ## @xnetjs/abuse@0.8.0 ### Patch Changes - Updated dependencies []: - @xnetjs/identity@0.8.0 - @xnetjs/crypto@0.8.0 ## @xnetjs/crypto@0.8.0 ### Patch Changes - Updated dependencies []: - @xnetjs/core@0.8.0 ## @xnetjs/data@0.8.0 ### Patch Changes - Updated dependencies []: - @xnetjs/storage@0.8.0 - @xnetjs/sqlite@0.8.0 - @xnetjs/sync@0.8.0 - @xnetjs/identity@0.8.0 - @xnetjs/crypto@0.8.0 - @xnetjs/core@0.8.0 ## @xnetjs/data-bridge@0.8.0 ### Patch Changes - Updated dependencies []: - @xnetjs/data@0.8.0 - @xnetjs/sqlite@0.8.0 - @xnetjs/sync@0.8.0 - @xnetjs/core@0.8.0 ## @xnetjs/history@0.8.0 ### Patch Changes - Updated dependencies []: - @xnetjs/data@0.8.0 - @xnetjs/sync@0.8.0 - @xnetjs/core@0.8.0 ## @xnetjs/identity@0.8.0 ### Patch Changes - Updated dependencies []: - @xnetjs/crypto@0.8.0 - @xnetjs/core@0.8.0 ## @xnetjs/react@0.8.0 ### Patch Changes - Updated dependencies [[`dd3b1cb`](dd3b1cb), [`853d849`](853d849), [`677856e`](677856e)]: - @xnetjs/plugins@0.8.0 - @xnetjs/runtime@0.2.0 - @xnetjs/history@0.8.0 - @xnetjs/data-bridge@0.8.0 - @xnetjs/data@0.8.0 - @xnetjs/sync@0.8.0 - @xnetjs/identity@0.8.0 - @xnetjs/crypto@0.8.0 - @xnetjs/core@0.8.0 ## @xnetjs/storage@0.8.0 ### Patch Changes - Updated dependencies []: - @xnetjs/sqlite@0.8.0 - @xnetjs/crypto@0.8.0 - @xnetjs/core@0.8.0 ## @xnetjs/sync@0.8.0 ### Patch Changes - Updated dependencies []: - @xnetjs/identity@0.8.0 - @xnetjs/crypto@0.8.0 - @xnetjs/core@0.8.0 ## @xnetjs/core@0.8.0 ## @xnetjs/sqlite@0.8.0 ## xnet-cloud@0.0.11 ### Patch Changes - Updated dependencies []: - @xnetjs/crypto@0.8.0 - @xnetjs/cloud@0.0.1 ## @xnetjs/brain@0.0.12 ### Patch Changes - Updated dependencies []: - @xnetjs/data@0.8.0 - @xnetjs/vectors@0.0.1 ## @xnetjs/comms@0.0.12 ### Patch Changes - Updated dependencies []: - @xnetjs/data@0.8.0 - @xnetjs/crypto@0.8.0 ## @xnetjs/dashboard@0.0.12 ### Patch Changes - Updated dependencies [[`dd3b1cb`](dd3b1cb), [`677856e`](677856e)]: - @xnetjs/plugins@0.8.0 - @xnetjs/react@0.8.0 - @xnetjs/data@0.8.0 - @xnetjs/social@0.0.12 ## @xnetjs/labs@0.0.12 ### Patch Changes - Updated dependencies [[`dd3b1cb`](dd3b1cb), [`677856e`](677856e)]: - @xnetjs/plugins@0.8.0 - @xnetjs/data@0.8.0 ## @xnetjs/licenses@0.0.12 ### Patch Changes - Updated dependencies []: - @xnetjs/crypto@0.8.0 ## @xnetjs/maps@0.0.12 ### Patch Changes - Updated dependencies []: - @xnetjs/data@0.8.0 ## @xnetjs/meetings@0.0.5 ### Patch Changes - Updated dependencies [[`dd3b1cb`](dd3b1cb), [`677856e`](677856e)]: - @xnetjs/plugins@0.8.0 - @xnetjs/data@0.8.0 ## @xnetjs/server@0.0.11 ### Patch Changes - Updated dependencies []: - @xnetjs/data-bridge@0.8.0 - @xnetjs/data@0.8.0 - @xnetjs/identity@0.8.0 - @xnetjs/crypto@0.8.0 ## @xnetjs/social@0.0.12 ### Patch Changes - Updated dependencies []: - @xnetjs/data@0.8.0 - @xnetjs/crypto@0.8.0 ## @xnetjs/unreal@0.0.12 ### Patch Changes - Updated dependencies []: - @xnetjs/data@0.8.0 ## xnet-desktop@0.8.0 Desktop shell release riding the @xnetjs/core 0.8.0 train. Desktop-specific changes are not tracked here; see the core packages' changelogs for what shipped.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements exploration 0289 — harden the loopback agent bridge into the secure spine the web app talks to local agents (Claude Code / Codex) and raw local models (Ollama / LM Studio) through.
What changed
Bridge daemon hardening (
@xnetjs/devkit, breaking → major)/v1/chat/completionsand/runnow require a per-launch pairing token (Authorization: Bearer, constant-time compared)./healthstays unauthenticated so detection still works before pairing.Host-header validation rejects DNS-rebinding requests before origin/token checks (the Ollama CVE-2024-28224 fix). Mirrored onto the MCP HTTP transport (@xnetjs/plugins).openAiChatAgentlets the bridge front a raw local model (Ollama/LM Studio) through the same authenticated door —xnet bridge serve --upstream http://localhost:11434.Token delivery (out-of-band, never over HTTP)
allowedOrigins(app.xnet.fyi+XNET_BRIDGE_ALLOWED_ORIGINS) and forwards the token to its renderer over IPC → auto-pairs.xnet bridge serveprints a pairing code (and--tokento pin it); the web app gets a pairing-code field to paste it into.Web app (
xnet-web)loopback-networkpermission and shows an "allow local network access" hint on denial instead of failing silently.connect-srcnow includeshttp://127.0.0.1:*/ws://127.0.0.1:*(the bridge's default address).local-servertier now shows the exactOLLAMA_ORIGINS=<origin>line (never a wildcard).Trust boundary
Hostgate → origin allowlist → bearer token, all server-side (the only layer that holds on Safari/older Firefox, which don't yet enforce Local Network Access).Tests
New/updated coverage in
bridge-server.test.ts(token + Host + rebind),mcp-http.test.ts(rebind),chat-agent.test.ts(upstream proxy),detect.test.ts(exact-origin hint),bridge.test.ts(--token),ai-chat-connector.test.ts(bridge token). All affected suites green; full typecheck (45 tasks) passes.Scope note
11/12 implementation items shipped. The 12th — a native-messaging browser-extension bridge (Option C) for the pure-web PWA — is explicitly a follow-up in the exploration and is tracked as separate work; everything else (the complete secure bridge) is here. Exploration doc left at
[_]pending that follow-up.🤖 Generated with Claude Code