@chatpanel/bridge 0.10.11 → 0.10.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/LICENSE +168 -21
  2. package/README.md +16 -0
  3. package/package.json +2 -2
  4. package/src/server.js +82 -1
package/LICENSE CHANGED
@@ -1,21 +1,168 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 ChatPanel
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ # PolyForm Shield License 1.0.0
2
+
3
+ <https://polyformproject.org/licenses/shield/1.0.0>
4
+
5
+ Required Notice: Copyright © 2026 ChatPanel (https://chatpanel.net)
6
+
7
+ Licensor Line of Business: ChatPanel an AI browser side-panel, its local
8
+ bridge, and related developer tools and services (https://chatpanel.net)
9
+
10
+ ## Acceptance
11
+
12
+ In order to get any license under these terms, you must agree
13
+ to them as both strict obligations and conditions to all
14
+ your licenses.
15
+
16
+ ## Copyright License
17
+
18
+ The licensor grants you a copyright license for the
19
+ software to do everything you might do with the software
20
+ that would otherwise infringe the licensor's copyright
21
+ in it for any permitted purpose. However, you may
22
+ only distribute the software according to [Distribution
23
+ License](#distribution-license) and make changes or new works
24
+ based on the software according to [Changes and New Works
25
+ License](#changes-and-new-works-license).
26
+
27
+ ## Distribution License
28
+
29
+ The licensor grants you an additional copyright license to
30
+ distribute copies of the software. Your license to distribute
31
+ covers distributing the software with changes and new works
32
+ permitted by [Changes and New Works License](#changes-and-new-works-license).
33
+
34
+ ## Notices
35
+
36
+ You must ensure that anyone who gets a copy of any part of
37
+ the software from you also gets a copy of these terms or the
38
+ URL for them above, as well as copies of any plain-text lines
39
+ beginning with `Required Notice:` that the licensor provided
40
+ with the software. For example:
41
+
42
+ > Required Notice: Copyright © 2026 ChatPanel (https://chatpanel.net)
43
+
44
+ ## Changes and New Works License
45
+
46
+ The licensor grants you an additional copyright license to
47
+ make changes and new works based on the software for any
48
+ permitted purpose.
49
+
50
+ ## Patent License
51
+
52
+ The licensor grants you a patent license for the software that
53
+ covers patent claims the licensor can license, or becomes able
54
+ to license, that you would infringe by using the software.
55
+
56
+ ## Noncompete
57
+
58
+ Any purpose is a permitted purpose, except for providing any
59
+ product that competes with the software or any product the
60
+ licensor or any of its affiliates provides using the software.
61
+
62
+ ## Competition
63
+
64
+ Goods and services compete even when they provide functionality
65
+ through different kinds of interfaces or for different technical
66
+ platforms. Applications can compete with services, libraries
67
+ with plugins, frameworks with development tools, and so on,
68
+ even if they're written in different programming languages
69
+ or for different computer architectures. Goods and services
70
+ compete even when provided free of charge. If you market a
71
+ product as a practical substitute for the software or another
72
+ product, it definitely competes.
73
+
74
+ ## New Products
75
+
76
+ If you are using the software to provide a product that does
77
+ not compete, but the licensor or any of its affiliates brings
78
+ your product into competition by providing a new version of
79
+ the software or another product using the software, you may
80
+ continue using versions of the software available under these
81
+ terms beforehand to provide your competing product, but not
82
+ any later versions.
83
+
84
+ ## Discontinued Products
85
+
86
+ You may begin using the software to compete with a product
87
+ or service that the licensor or any of its affiliates has
88
+ stopped providing, unless the licensor includes a plain-text
89
+ line beginning with `Licensor Line of Business:` with the
90
+ software that mentions that line of business. For example:
91
+
92
+ > Licensor Line of Business: ChatPanel — an AI browser side-panel, its local
93
+ > bridge, and related developer tools and services (https://chatpanel.net)
94
+
95
+ ## Sales of Business
96
+
97
+ If the licensor or any of its affiliates sells a line of
98
+ business developing the software or using the software
99
+ to provide a product, the buyer can also enforce
100
+ Noncompete for that product.
101
+
102
+ ## Fair Use
103
+
104
+ You may have "fair use" rights for the software under the
105
+ law. These terms do not limit them.
106
+
107
+ ## No Other Rights
108
+
109
+ These terms do not allow you to sublicense or transfer any of
110
+ your licenses to anyone else, or prevent the licensor from
111
+ granting licenses to anyone else. These terms do not imply
112
+ any other licenses.
113
+
114
+ ## Patent Defense
115
+
116
+ If you make any written claim that the software infringes or
117
+ contributes to infringement of any patent, your patent license
118
+ for the software granted under these terms ends immediately. If
119
+ your company makes such a claim, your patent license ends
120
+ immediately for work on behalf of your company.
121
+
122
+ ## Violations
123
+
124
+ The first time you are notified in writing that you have
125
+ violated any of these terms, or done anything with the software
126
+ not covered by your licenses, your licenses can nonetheless
127
+ continue if you come into full compliance with these terms,
128
+ and take practical steps to correct past violations, within
129
+ 32 days of receiving notice. Otherwise, all your licenses
130
+ end immediately.
131
+
132
+ ## No Liability
133
+
134
+ ***As far as the law allows, the software comes as is, without
135
+ any warranty or condition, and the licensor will not be liable
136
+ to you for any damages arising out of these terms or the use
137
+ or nature of the software, under any kind of legal claim.***
138
+
139
+ ## Definitions
140
+
141
+ The **licensor** is the individual or entity offering these
142
+ terms, and the **software** is the software the licensor makes
143
+ available under these terms.
144
+
145
+ A **product** can be a good or service, or a combination
146
+ of them.
147
+
148
+ **You** refers to the individual or entity agreeing to these
149
+ terms.
150
+
151
+ **Your company** is any legal entity, sole proprietorship,
152
+ or other kind of organization that you work for, plus all
153
+ its affiliates.
154
+
155
+ **Affiliates** means the other organizations that an
156
+ organization has control over, is under the control of, or is
157
+ under common control with.
158
+
159
+ **Control** means ownership of substantially all the assets of
160
+ an entity, or the power to direct its management and policies
161
+ by vote, contract, or otherwise. Control can be direct or
162
+ indirect.
163
+
164
+ **Your licenses** are all the licenses granted to you for the
165
+ software under these terms.
166
+
167
+ **Use** means anything you do with the software requiring one
168
+ of your licenses.
package/README.md CHANGED
@@ -124,3 +124,19 @@ macOS (launchd) example — save to `~/Library/LaunchAgents/app.chatpanel.bridge
124
124
  ```
125
125
 
126
126
  Then `launchctl load ~/Library/LaunchAgents/app.chatpanel.bridge.plist`.
127
+
128
+ ## License
129
+
130
+ **Source-available**, under the [**PolyForm Shield License 1.0.0**](LICENSE).
131
+ Read, audit, run, and modify it for your own use — you just may not use it to
132
+ provide a product or service that **competes** with ChatPanel. The
133
+ [extension](https://github.com/chatpanel/chatpanel-extension) is under the same
134
+ license. This is *not* an OSI "open source" license; the source is published for
135
+ transparency and trust (this bridge runs on your machine with file access, so it
136
+ should be auditable), not for re-packaging or resale.
137
+
138
+ ## Trademarks & brand
139
+
140
+ The name **“ChatPanel”**, the logo, and brand assets are **trademarks of ChatPanel,
141
+ not licensed** under the terms above — the Shield license covers the code only.
142
+ Don't redistribute a fork (e.g. to npm) under the ChatPanel name or marks.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chatpanel/bridge",
3
- "version": "0.10.11",
3
+ "version": "0.10.13",
4
4
  "type": "module",
5
5
  "description": "Local bridge that exposes the AI coding agents installed on your machine — Claude Code (CLI), Codex (CLI), and Gemini CLI — to the ChatPanel Chrome extension over a localhost SSE endpoint. Bring your own agent.",
6
6
  "keywords": [
@@ -17,7 +17,7 @@
17
17
  "type": "git",
18
18
  "url": "git+https://github.com/chatpanel/chatpanel-bridge.git"
19
19
  },
20
- "license": "MIT",
20
+ "license": "SEE LICENSE IN LICENSE",
21
21
  "bin": {
22
22
  "chatpanel-bridge": "src/server.js"
23
23
  },
package/src/server.js CHANGED
@@ -36,7 +36,7 @@ import { callLocalMcp } from './mcp-local.js';
36
36
  // Hardcoded (not read from package.json) so it survives Bun's single-file
37
37
  // --compile, where package.json isn't on a readable FS. CI fails the publish if
38
38
  // this drifts from package.json, so the two can't silently diverge.
39
- const VERSION = '0.10.11';
39
+ const VERSION = '0.10.13';
40
40
  const HOST = process.env.CHATPANEL_BRIDGE_HOST || '127.0.0.1';
41
41
  const PORT = Number(process.env.CHATPANEL_BRIDGE_PORT) || 4319;
42
42
 
@@ -224,6 +224,7 @@ function tokenOk(req) {
224
224
  const PRIVILEGED_POST = new Set([
225
225
  '/chat',
226
226
  '/mcp-local',
227
+ '/mcp-remote',
227
228
  '/complete',
228
229
  '/list-models',
229
230
  '/agent-check',
@@ -232,6 +233,32 @@ const PRIVILEGED_POST = new Set([
232
233
  ]);
233
234
  const PRIVILEGED_GET = new Set(['/debug']);
234
235
 
236
+ // SSRF guard for /mcp-remote: the bridge must not become an open relay into the
237
+ // local network. Block non-http(s) schemes and private/loopback/link-local/
238
+ // metadata hosts — on the initial URL AND after any redirect.
239
+ function isBlockedHttpHost(hostname) {
240
+ const h = String(hostname || '').toLowerCase().replace(/^\[|\]$/g, '');
241
+ if (!h || h === 'localhost' || h.endsWith('.localhost') || h.endsWith('.local')) return true;
242
+ if (h === '::1' || h === '::' || h.startsWith('fc') || h.startsWith('fd') || h.startsWith('fe8') || h.startsWith('fe9') || h.startsWith('fea') || h.startsWith('feb')) return true;
243
+ const m = h.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
244
+ if (m) {
245
+ const a = Number(m[1]), b = Number(m[2]);
246
+ if (a === 0 || a === 127 || a === 10) return true; // this-host / loopback / RFC1918
247
+ if (a === 169 && b === 254) return true; // link-local + cloud metadata
248
+ if (a === 172 && b >= 16 && b <= 31) return true; // RFC1918
249
+ if (a === 192 && b === 168) return true; // RFC1918
250
+ if (a === 100 && b >= 64 && b <= 127) return true; // CGNAT
251
+ }
252
+ return false;
253
+ }
254
+ function assertPublicHttpUrl(u) {
255
+ let parsed;
256
+ try { parsed = new URL(u); } catch { throw new Error(`invalid URL: ${u}`); }
257
+ if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') throw new Error(`only http(s) URLs allowed (got "${parsed.protocol}")`);
258
+ if (isBlockedHttpHost(parsed.hostname)) throw new Error(`refusing to proxy a private/loopback/metadata address (${parsed.hostname})`);
259
+ return parsed;
260
+ }
261
+
235
262
  // Returns an error code if the request must be blocked, else null.
236
263
  function guard(req, pathname) {
237
264
  if (!hostAllowed(req)) return 'forbidden host';
@@ -441,6 +468,59 @@ async function handleMcpLocal(req, res) {
441
468
  }
442
469
  }
443
470
 
471
+ // POST /mcp-remote — proxy ONE JSON-RPC message to a remote Streamable-HTTP MCP
472
+ // server FROM the bridge (server-side, no browser Origin header), so the extension
473
+ // can reach servers that reject browser origins (their own DNS-rebinding/CORS
474
+ // protection). Body: { url, headers?, message }. Returns the upstream
475
+ // { status, sessionId, contentType, body } for the extension to parse. Privileged
476
+ // route + SSRF guard, so it can't be driven by a page or relay into the LAN.
477
+ async function handleMcpRemote(req, res) {
478
+ let body;
479
+ try {
480
+ body = await readBody(req);
481
+ } catch (e) {
482
+ return json(res, 400, { error: 'Bad JSON: ' + e.message });
483
+ }
484
+ const { url, headers: hdrs, message } = body || {};
485
+ if (!url || !message) return json(res, 400, { error: 'need url and message' });
486
+ let target;
487
+ try {
488
+ target = assertPublicHttpUrl(url);
489
+ } catch (e) {
490
+ return json(res, 400, { error: String(e?.message || e) });
491
+ }
492
+ // Forward only safe, MCP-relevant client headers (auth, session, protocol, x-*);
493
+ // never the Host/hop-by-hop headers. Content-Type/Accept are set by us.
494
+ const fwd = { 'Content-Type': 'application/json', Accept: 'application/json, text/event-stream' };
495
+ if (hdrs && typeof hdrs === 'object') {
496
+ for (const [k, v] of Object.entries(hdrs)) {
497
+ const lk = k.toLowerCase();
498
+ if (['authorization', 'mcp-session-id', 'mcp-protocol-version', 'x-api-key'].includes(lk) || lk.startsWith('x-')) {
499
+ fwd[k] = String(v);
500
+ }
501
+ }
502
+ }
503
+ const ac = new AbortController();
504
+ const t = setTimeout(() => ac.abort(), 30_000);
505
+ try {
506
+ const up = await fetch(target.href, { method: 'POST', headers: fwd, body: JSON.stringify(message), redirect: 'follow', signal: ac.signal });
507
+ // A redirect may have landed on an internal host — re-check the final URL.
508
+ try { if (up.url) assertPublicHttpUrl(up.url); } catch (e) { return json(res, 400, { error: String(e?.message || e) }); }
509
+ const text = await up.text().catch(() => '');
510
+ return json(res, 200, {
511
+ status: up.status,
512
+ sessionId: up.headers.get('Mcp-Session-Id') || null,
513
+ contentType: up.headers.get('Content-Type') || '',
514
+ body: text,
515
+ });
516
+ } catch (e) {
517
+ const why = e?.name === 'AbortError' ? 'timed out' : String(e?.message || e);
518
+ return json(res, 502, { error: `couldn't reach MCP server ${target.host}: ${why}` });
519
+ } finally {
520
+ clearTimeout(t);
521
+ }
522
+ }
523
+
444
524
  // POST /tool-result — the extension returns a relayed tool's result.
445
525
  async function handleToolResult(req, res) {
446
526
  let body;
@@ -582,6 +662,7 @@ const server = createServer(async (req, res) => {
582
662
  }
583
663
  if (req.method === 'POST' && url.pathname === '/tool-result') return handleToolResult(req, res);
584
664
  if (req.method === 'POST' && url.pathname === '/mcp-local') return handleMcpLocal(req, res);
665
+ if (req.method === 'POST' && url.pathname === '/mcp-remote') return handleMcpRemote(req, res);
585
666
  if (req.method === 'POST' && url.pathname === '/complete') return handleComplete(req, res);
586
667
  if (req.method === 'POST' && url.pathname === '/list-models') return handleListModels(req, res);
587
668
  if (req.method === 'POST' && url.pathname === '/agent-check') return handleAgentCheck(req, res);