@jobshimo/browser-link 0.1.0 → 0.4.0
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.
- package/README.md +58 -21
- package/dist/bridge/client.d.ts +101 -0
- package/dist/bridge/client.js +435 -0
- package/dist/bridge/client.js.map +1 -0
- package/dist/bridge/dispatch.d.ts +29 -0
- package/dist/bridge/dispatch.js +39 -0
- package/dist/bridge/dispatch.js.map +1 -0
- package/dist/bridge/events.d.ts +39 -0
- package/dist/bridge/events.js +47 -0
- package/dist/bridge/events.js.map +1 -0
- package/dist/bridge/protocol.d.ts +80 -0
- package/dist/bridge/protocol.js +79 -0
- package/dist/bridge/protocol.js.map +1 -0
- package/dist/bridge/server.d.ts +42 -0
- package/dist/bridge/server.js +336 -0
- package/dist/bridge/server.js.map +1 -0
- package/dist/bridge/token.d.ts +17 -0
- package/dist/bridge/token.js +79 -0
- package/dist/bridge/token.js.map +1 -0
- package/dist/cli.js +132 -39
- package/dist/cli.js.map +1 -1
- package/dist/commands/about.d.ts +3 -6
- package/dist/commands/about.js +2 -18
- package/dist/commands/about.js.map +1 -1
- package/dist/commands/doctor.d.ts +12 -1
- package/dist/commands/doctor.js +90 -20
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/extension.d.ts +3 -2
- package/dist/commands/extension.js +53 -28
- package/dist/commands/extension.js.map +1 -1
- package/dist/commands/multi-agent.d.ts +7 -0
- package/dist/commands/multi-agent.js +109 -0
- package/dist/commands/multi-agent.js.map +1 -0
- package/dist/commands/tools.d.ts +11 -0
- package/dist/commands/tools.js +168 -0
- package/dist/commands/tools.js.map +1 -0
- package/dist/commands/updates.d.ts +20 -0
- package/dist/commands/updates.js +100 -0
- package/dist/commands/updates.js.map +1 -0
- package/dist/commands/welcome.d.ts +2 -1
- package/dist/commands/welcome.js +11 -47
- package/dist/commands/welcome.js.map +1 -1
- package/dist/config.d.ts +25 -3
- package/dist/config.js +35 -2
- package/dist/config.js.map +1 -1
- package/dist/installers/copilot.d.ts +2 -0
- package/dist/installers/copilot.js +72 -0
- package/dist/installers/copilot.js.map +1 -0
- package/dist/installers/index.js +2 -1
- package/dist/installers/index.js.map +1 -1
- package/dist/installers/types.d.ts +1 -1
- package/dist/messages.d.ts +7 -0
- package/dist/permissions.d.ts +37 -0
- package/dist/permissions.js +156 -0
- package/dist/permissions.js.map +1 -0
- package/dist/server.d.ts +7 -3
- package/dist/server.js +160 -32
- package/dist/server.js.map +1 -1
- package/dist/tools/browser-definitions.js +18 -0
- package/dist/tools/browser-definitions.js.map +1 -1
- package/dist/tools/browser-dispatch.d.ts +7 -0
- package/dist/tools/browser-dispatch.js +11 -0
- package/dist/tools/browser-dispatch.js.map +1 -1
- package/dist/tools/server-instructions.d.ts +1 -1
- package/dist/tools/server-instructions.js +4 -0
- package/dist/tools/server-instructions.js.map +1 -1
- package/dist/ui/app.d.ts +7 -0
- package/dist/ui/app.js +77 -0
- package/dist/ui/app.js.map +1 -0
- package/dist/ui/components.d.ts +18 -0
- package/dist/ui/components.js +27 -0
- package/dist/ui/components.js.map +1 -0
- package/dist/ui/screens.d.ts +61 -0
- package/dist/ui/screens.js +603 -0
- package/dist/ui/screens.js.map +1 -0
- package/dist/ui/start.d.ts +6 -0
- package/dist/ui/start.js +19 -0
- package/dist/ui/start.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +15 -0
- package/dist/version.js.map +1 -0
- package/package.json +10 -4
- package/dist/commands/menu.d.ts +0 -25
- package/dist/commands/menu.js +0 -167
- package/dist/commands/menu.js.map +0 -1
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import { createServer } from 'node:net';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { lookupPeerProcess } from '../auth/process-identity.js';
|
|
4
|
+
import { SERVER_INSTRUCTIONS } from '../tools/server-instructions.js';
|
|
5
|
+
import { VERSION } from '../version.js';
|
|
6
|
+
import { IPC_HOST, IPC_PING_INTERVAL_MS, IPC_PONG_TIMEOUT_MS, IPC_PORT, IPC_PROTOCOL_VERSION, encodeFrame, isCompatibleVersion, parseFrame, } from './protocol.js';
|
|
7
|
+
import { handleToolCall, handleToolsList } from './dispatch.js';
|
|
8
|
+
import { rotateToken } from './token.js';
|
|
9
|
+
/**
|
|
10
|
+
* Primary's side of the IPC bridge. Listens on 127.0.0.1:17530, validates
|
|
11
|
+
* incoming peer processes via the same kernel-level process binding that
|
|
12
|
+
* protects the WS bridge, then runs the hello/token handshake. After auth,
|
|
13
|
+
* forwards MCP JSON-RPC frames through the shared dispatch handlers.
|
|
14
|
+
*
|
|
15
|
+
* Construction does NOT bind the port. Call `start()` and await.
|
|
16
|
+
*/
|
|
17
|
+
function log(msg) {
|
|
18
|
+
// stderr — stdout belongs to the MCP transport.
|
|
19
|
+
console.error(`[browser-link ipc] ${msg}`);
|
|
20
|
+
}
|
|
21
|
+
/** Binaries we'll accept as legitimate peers. The token is the real auth;
|
|
22
|
+
* this is defence in depth. Names normalised to lowercase before comparison. */
|
|
23
|
+
const NODE_PROCESS_NAMES = new Set([
|
|
24
|
+
'node',
|
|
25
|
+
'node.exe',
|
|
26
|
+
'nodejs',
|
|
27
|
+
'tsx',
|
|
28
|
+
'tsx.exe',
|
|
29
|
+
'browser-link',
|
|
30
|
+
'browser-link.exe',
|
|
31
|
+
]);
|
|
32
|
+
/** Same loopback normalisation as server.ts uses for the WS bridge. */
|
|
33
|
+
function normaliseLoopback(addr) {
|
|
34
|
+
if (addr === '::1' || addr === '::ffff:127.0.0.1')
|
|
35
|
+
return '127.0.0.1';
|
|
36
|
+
if (addr.startsWith('::ffff:'))
|
|
37
|
+
return addr.slice('::ffff:'.length);
|
|
38
|
+
return addr;
|
|
39
|
+
}
|
|
40
|
+
export class IpcServer {
|
|
41
|
+
deps;
|
|
42
|
+
options;
|
|
43
|
+
server = null;
|
|
44
|
+
sessions = new Map();
|
|
45
|
+
token;
|
|
46
|
+
boundHost = IPC_HOST;
|
|
47
|
+
boundPortValue = IPC_PORT;
|
|
48
|
+
constructor(deps, options = {}) {
|
|
49
|
+
this.deps = deps;
|
|
50
|
+
this.options = options;
|
|
51
|
+
// Rotate the token on every primary startup. Any stale token left by a
|
|
52
|
+
// crashed previous primary is invalidated immediately.
|
|
53
|
+
this.token = rotateToken();
|
|
54
|
+
}
|
|
55
|
+
/** Bind the IPC port. Rejects on EADDRINUSE so the caller (server.ts)
|
|
56
|
+
* can log and continue without multi-agent — never throws after listen. */
|
|
57
|
+
start() {
|
|
58
|
+
return new Promise((resolve, reject) => {
|
|
59
|
+
const host = this.options.host ?? IPC_HOST;
|
|
60
|
+
const port = this.options.port ?? IPC_PORT;
|
|
61
|
+
const server = createServer((socket) => {
|
|
62
|
+
this.handleConnection(socket).catch((err) => {
|
|
63
|
+
log(`Connection handler error: ${err instanceof Error ? err.message : String(err)}`);
|
|
64
|
+
socket.destroy();
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
let settled = false;
|
|
68
|
+
server.on('error', (err) => {
|
|
69
|
+
if (!settled) {
|
|
70
|
+
settled = true;
|
|
71
|
+
if (err.code === 'EADDRINUSE') {
|
|
72
|
+
reject(new Error(`IPC port ${host}:${port} already in use — another browser-link primary may already be running.`));
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
reject(err);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
log(`Server error after listening: ${err.message}`);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
server.listen(port, host, () => {
|
|
83
|
+
settled = true;
|
|
84
|
+
this.server = server;
|
|
85
|
+
const addr = server.address();
|
|
86
|
+
if (addr && typeof addr === 'object') {
|
|
87
|
+
this.boundHost = addr.address;
|
|
88
|
+
this.boundPortValue = addr.port;
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
this.boundHost = host;
|
|
92
|
+
this.boundPortValue = port;
|
|
93
|
+
}
|
|
94
|
+
log(`IPC server listening on ${this.boundHost}:${this.boundPortValue}`);
|
|
95
|
+
resolve();
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
/** The actual host+port the server is listening on. Set after start()
|
|
100
|
+
* resolves; meaningful even when port:0 was passed. */
|
|
101
|
+
boundAddress() {
|
|
102
|
+
return { host: this.boundHost, port: this.boundPortValue };
|
|
103
|
+
}
|
|
104
|
+
/** Tell every connected proxy we're going down (so auto-reelect can fire),
|
|
105
|
+
* then close the listening socket. Safe to call when start() never ran. */
|
|
106
|
+
async stop() {
|
|
107
|
+
for (const session of this.sessions.values()) {
|
|
108
|
+
try {
|
|
109
|
+
session.socket.write(encodeFrame({ kind: 'primary-closing', reason: 'shutdown' }));
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
/* socket already gone */
|
|
113
|
+
}
|
|
114
|
+
clearInterval(session.pingTimer);
|
|
115
|
+
if (session.pongDeadline)
|
|
116
|
+
clearTimeout(session.pongDeadline);
|
|
117
|
+
session.socket.end();
|
|
118
|
+
}
|
|
119
|
+
this.sessions.clear();
|
|
120
|
+
if (this.server) {
|
|
121
|
+
await new Promise((res) => this.server.close(() => res()));
|
|
122
|
+
this.server = null;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/** Visible to tests + doctor. */
|
|
126
|
+
sessionCount() {
|
|
127
|
+
return this.sessions.size;
|
|
128
|
+
}
|
|
129
|
+
/** Exposed for tests only. Production callers never need this. */
|
|
130
|
+
currentToken() {
|
|
131
|
+
return this.token;
|
|
132
|
+
}
|
|
133
|
+
async handleConnection(socket) {
|
|
134
|
+
const remoteAddr = normaliseLoopback(socket.remoteAddress ?? '');
|
|
135
|
+
const remotePort = socket.remotePort;
|
|
136
|
+
if (!remoteAddr || remotePort == null) {
|
|
137
|
+
log('Rejected IPC connection: peer address/port not exposed by socket.');
|
|
138
|
+
socket.destroy();
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
// Kernel-level process binding. Same trick as the WS bridge: we ask the
|
|
142
|
+
// OS which process owns the peer's TCP port, and reject anything that
|
|
143
|
+
// does not look like a Node-family binary.
|
|
144
|
+
const peer = await lookupPeerProcess(remoteAddr, remotePort).catch(() => null);
|
|
145
|
+
if (!peer || !NODE_PROCESS_NAMES.has(peer.binaryName.toLowerCase())) {
|
|
146
|
+
log(`Rejected IPC connection from ${remoteAddr}:${remotePort}: not a known Node process (${peer?.binaryName ?? 'unknown'}).`);
|
|
147
|
+
socket.destroy();
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
let buffer = '';
|
|
151
|
+
let authenticated = false;
|
|
152
|
+
let sessionId = null;
|
|
153
|
+
const handleLine = (line) => {
|
|
154
|
+
if (line.length === 0)
|
|
155
|
+
return;
|
|
156
|
+
const frame = parseFrame(line);
|
|
157
|
+
if (!frame) {
|
|
158
|
+
log('Invalid frame received; closing connection.');
|
|
159
|
+
socket.destroy();
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
if (!authenticated) {
|
|
163
|
+
if (frame.kind !== 'hello') {
|
|
164
|
+
log(`First frame was not "hello" (got ${frame.kind}); closing.`);
|
|
165
|
+
socket.destroy();
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (!isCompatibleVersion(frame.version)) {
|
|
169
|
+
socket.write(encodeFrame({
|
|
170
|
+
kind: 'hello-reject',
|
|
171
|
+
reason: `version mismatch: primary ${IPC_PROTOCOL_VERSION}, proxy ${frame.version}`,
|
|
172
|
+
}));
|
|
173
|
+
socket.end();
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (frame.token !== this.token) {
|
|
177
|
+
socket.write(encodeFrame({ kind: 'hello-reject', reason: 'invalid token' }));
|
|
178
|
+
socket.end();
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
// Auth ok — register session and start the heartbeat.
|
|
182
|
+
authenticated = true;
|
|
183
|
+
sessionId = randomUUID();
|
|
184
|
+
socket.write(encodeFrame({ kind: 'hello-ack', version: IPC_PROTOCOL_VERSION, sessionId }));
|
|
185
|
+
const sid = sessionId;
|
|
186
|
+
const session = {
|
|
187
|
+
id: sid,
|
|
188
|
+
socket,
|
|
189
|
+
pingTimer: setInterval(() => this.heartbeat(sid), IPC_PING_INTERVAL_MS),
|
|
190
|
+
pongDeadline: null,
|
|
191
|
+
};
|
|
192
|
+
this.sessions.set(sid, session);
|
|
193
|
+
log(`Proxy connected: session=${sid} pid=${peer.pid}`);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
void this.handleFrame(frame, sessionId, socket);
|
|
197
|
+
};
|
|
198
|
+
socket.on('data', (chunk) => {
|
|
199
|
+
buffer += chunk.toString('utf8');
|
|
200
|
+
let nl;
|
|
201
|
+
while ((nl = buffer.indexOf('\n')) >= 0) {
|
|
202
|
+
const line = buffer.slice(0, nl);
|
|
203
|
+
buffer = buffer.slice(nl + 1);
|
|
204
|
+
handleLine(line);
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
socket.on('close', () => {
|
|
208
|
+
if (sessionId) {
|
|
209
|
+
const session = this.sessions.get(sessionId);
|
|
210
|
+
if (session) {
|
|
211
|
+
clearInterval(session.pingTimer);
|
|
212
|
+
if (session.pongDeadline)
|
|
213
|
+
clearTimeout(session.pongDeadline);
|
|
214
|
+
this.sessions.delete(sessionId);
|
|
215
|
+
log(`Proxy disconnected: session=${sessionId}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
socket.on('error', (err) => {
|
|
220
|
+
log(`Proxy socket error: ${err.message}`);
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
heartbeat(sid) {
|
|
224
|
+
const session = this.sessions.get(sid);
|
|
225
|
+
if (!session)
|
|
226
|
+
return;
|
|
227
|
+
try {
|
|
228
|
+
session.socket.write(encodeFrame({ kind: 'ping' }));
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
// socket already closed; the 'close' handler will clean up
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
if (!session.pongDeadline) {
|
|
235
|
+
session.pongDeadline = setTimeout(() => {
|
|
236
|
+
log(`Proxy ${sid} did not pong in time; dropping.`);
|
|
237
|
+
session.socket.destroy();
|
|
238
|
+
}, IPC_PONG_TIMEOUT_MS);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
async handleFrame(frame, sessionId, socket) {
|
|
242
|
+
switch (frame.kind) {
|
|
243
|
+
case 'pong': {
|
|
244
|
+
const session = this.sessions.get(sessionId);
|
|
245
|
+
if (session?.pongDeadline) {
|
|
246
|
+
clearTimeout(session.pongDeadline);
|
|
247
|
+
session.pongDeadline = null;
|
|
248
|
+
}
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
case 'mcp.request': {
|
|
252
|
+
const response = await this.dispatchMcpRequest(frame.payload);
|
|
253
|
+
try {
|
|
254
|
+
socket.write(encodeFrame({ kind: 'mcp.response', requestId: frame.requestId, payload: response }));
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
/* socket gone */
|
|
258
|
+
}
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
case 'mcp.notification': {
|
|
262
|
+
// Proxies forward notifications like notifications/initialized. We
|
|
263
|
+
// don't reply (notifications have no id). Future work: route to map
|
|
264
|
+
// event log when we add the traceability layer.
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
case 'ping': {
|
|
268
|
+
try {
|
|
269
|
+
socket.write(encodeFrame({ kind: 'pong' }));
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
/* socket gone */
|
|
273
|
+
}
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
default:
|
|
277
|
+
// hello/hello-ack/hello-reject/primary-closing should not arrive
|
|
278
|
+
// post-handshake from a proxy. Ignore defensively.
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
/** Dispatch a JSON-RPC 2.0 MCP request to the shared handlers and return
|
|
283
|
+
* a JSON-RPC 2.0 response object. Errors are converted to JSON-RPC error
|
|
284
|
+
* envelopes — never thrown back to the socket layer. */
|
|
285
|
+
async dispatchMcpRequest(payload) {
|
|
286
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
287
|
+
return { jsonrpc: '2.0', id: null, error: { code: -32600, message: 'Invalid request' } };
|
|
288
|
+
}
|
|
289
|
+
const req = payload;
|
|
290
|
+
const id = req.id ?? null;
|
|
291
|
+
const method = req.method ?? '';
|
|
292
|
+
try {
|
|
293
|
+
switch (method) {
|
|
294
|
+
case 'initialize': {
|
|
295
|
+
const result = {
|
|
296
|
+
protocolVersion: '2024-11-05',
|
|
297
|
+
capabilities: { tools: {} },
|
|
298
|
+
serverInfo: { name: 'browser-link', version: VERSION },
|
|
299
|
+
instructions: SERVER_INSTRUCTIONS,
|
|
300
|
+
};
|
|
301
|
+
return { jsonrpc: '2.0', id, result };
|
|
302
|
+
}
|
|
303
|
+
case 'tools/list': {
|
|
304
|
+
const result = handleToolsList(this.deps);
|
|
305
|
+
return { jsonrpc: '2.0', id, result };
|
|
306
|
+
}
|
|
307
|
+
case 'tools/call': {
|
|
308
|
+
const params = req.params;
|
|
309
|
+
if (!params || typeof params.name !== 'string') {
|
|
310
|
+
return {
|
|
311
|
+
jsonrpc: '2.0',
|
|
312
|
+
id,
|
|
313
|
+
error: { code: -32602, message: 'Missing tool name in params' },
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
const result = await handleToolCall({ name: params.name, arguments: params.arguments }, this.deps);
|
|
317
|
+
return { jsonrpc: '2.0', id, result };
|
|
318
|
+
}
|
|
319
|
+
case 'ping': {
|
|
320
|
+
return { jsonrpc: '2.0', id, result: {} };
|
|
321
|
+
}
|
|
322
|
+
default:
|
|
323
|
+
return {
|
|
324
|
+
jsonrpc: '2.0',
|
|
325
|
+
id,
|
|
326
|
+
error: { code: -32601, message: `Method not found: ${method}` },
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
catch (err) {
|
|
331
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
332
|
+
return { jsonrpc: '2.0', id, error: { code: -32603, message } };
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/bridge/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Where the per-session token file lives. */
|
|
2
|
+
export declare function tokenPath(): string;
|
|
3
|
+
/** Generate a fresh 32-byte hex token. */
|
|
4
|
+
export declare function generateToken(): string;
|
|
5
|
+
/** Write the given token to the data dir, replacing any previous value.
|
|
6
|
+
* Tries to set 0600 permissions on POSIX; on Windows the file ACL
|
|
7
|
+
* inherits from the user's profile, which is already user-only. */
|
|
8
|
+
export declare function writeToken(token: string): string;
|
|
9
|
+
/** Read the current token. Returns null if no token file exists or if
|
|
10
|
+
* it does not look like a valid 32-byte hex string. */
|
|
11
|
+
export declare function readToken(): string | null;
|
|
12
|
+
/** Generate + write a fresh token. Returns it. Called by every primary
|
|
13
|
+
* on startup, so any zombie token from a previous primary is invalidated. */
|
|
14
|
+
export declare function rotateToken(): string;
|
|
15
|
+
/** Best-effort cleanup. Used when the primary shuts down cleanly so the
|
|
16
|
+
* file doesn't linger as misleading state. */
|
|
17
|
+
export declare function clearToken(): void;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { randomBytes } from 'node:crypto';
|
|
4
|
+
import { getDataDir } from '../map/paths.js';
|
|
5
|
+
/**
|
|
6
|
+
* Per-session authentication token for the IPC channel between primary and
|
|
7
|
+
* proxy browser-link processes. The primary writes a fresh random token at
|
|
8
|
+
* startup; proxies read it just before sending their hello frame.
|
|
9
|
+
*
|
|
10
|
+
* Security model: the file lives in the user's data dir. On POSIX we set
|
|
11
|
+
* 0600 so only the user can read it. On Windows the file inherits the
|
|
12
|
+
* user-profile ACL, which is already restricted to the same user. A
|
|
13
|
+
* malicious process running as the same user can read the file — at that
|
|
14
|
+
* point the user has already lost — but a different local user, a remote
|
|
15
|
+
* peer, or a sandboxed process cannot.
|
|
16
|
+
*/
|
|
17
|
+
const TOKEN_FILE_NAME = 'multi-agent-token';
|
|
18
|
+
const TOKEN_HEX_LENGTH = 64; // 32 bytes hex = 64 chars
|
|
19
|
+
/** Where the per-session token file lives. */
|
|
20
|
+
export function tokenPath() {
|
|
21
|
+
return join(getDataDir(), TOKEN_FILE_NAME);
|
|
22
|
+
}
|
|
23
|
+
/** Generate a fresh 32-byte hex token. */
|
|
24
|
+
export function generateToken() {
|
|
25
|
+
return randomBytes(32).toString('hex');
|
|
26
|
+
}
|
|
27
|
+
/** Write the given token to the data dir, replacing any previous value.
|
|
28
|
+
* Tries to set 0600 permissions on POSIX; on Windows the file ACL
|
|
29
|
+
* inherits from the user's profile, which is already user-only. */
|
|
30
|
+
export function writeToken(token) {
|
|
31
|
+
const path = tokenPath();
|
|
32
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
33
|
+
writeFileSync(path, token, 'utf8');
|
|
34
|
+
try {
|
|
35
|
+
chmodSync(path, 0o600);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// chmod is a no-op on Windows; ignore.
|
|
39
|
+
}
|
|
40
|
+
return path;
|
|
41
|
+
}
|
|
42
|
+
/** Read the current token. Returns null if no token file exists or if
|
|
43
|
+
* it does not look like a valid 32-byte hex string. */
|
|
44
|
+
export function readToken() {
|
|
45
|
+
const path = tokenPath();
|
|
46
|
+
if (!existsSync(path))
|
|
47
|
+
return null;
|
|
48
|
+
try {
|
|
49
|
+
const raw = readFileSync(path, 'utf8').trim();
|
|
50
|
+
if (raw.length !== TOKEN_HEX_LENGTH)
|
|
51
|
+
return null;
|
|
52
|
+
if (!/^[0-9a-f]+$/i.test(raw))
|
|
53
|
+
return null;
|
|
54
|
+
return raw;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/** Generate + write a fresh token. Returns it. Called by every primary
|
|
61
|
+
* on startup, so any zombie token from a previous primary is invalidated. */
|
|
62
|
+
export function rotateToken() {
|
|
63
|
+
const token = generateToken();
|
|
64
|
+
writeToken(token);
|
|
65
|
+
return token;
|
|
66
|
+
}
|
|
67
|
+
/** Best-effort cleanup. Used when the primary shuts down cleanly so the
|
|
68
|
+
* file doesn't linger as misleading state. */
|
|
69
|
+
export function clearToken() {
|
|
70
|
+
const path = tokenPath();
|
|
71
|
+
try {
|
|
72
|
+
if (existsSync(path))
|
|
73
|
+
unlinkSync(path);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// ignore
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=token.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token.js","sourceRoot":"","sources":["../../src/bridge/token.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACpG,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C;;;;;;;;;;;GAWG;AAEH,MAAM,eAAe,GAAG,mBAAmB,CAAC;AAC5C,MAAM,gBAAgB,GAAG,EAAE,CAAC,CAAC,0BAA0B;AAEvD,8CAA8C;AAC9C,MAAM,UAAU,SAAS;IACvB,OAAO,IAAI,CAAC,UAAU,EAAE,EAAE,eAAe,CAAC,CAAC;AAC7C,CAAC;AAED,0CAA0C;AAC1C,MAAM,UAAU,aAAa;IAC3B,OAAO,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACzC,CAAC;AAED;;mEAEmE;AACnE,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,MAAM,IAAI,GAAG,SAAS,EAAE,CAAC;IACzB,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,aAAa,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;IACnC,IAAI,CAAC;QACH,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,uCAAuC;IACzC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;uDACuD;AACvD,MAAM,UAAU,SAAS;IACvB,MAAM,IAAI,GAAG,SAAS,EAAE,CAAC;IACzB,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACnC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;QAC9C,IAAI,GAAG,CAAC,MAAM,KAAK,gBAAgB;YAAE,OAAO,IAAI,CAAC;QACjD,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC;QAC3C,OAAO,GAAG,CAAC;IACb,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;6EAC6E;AAC7E,MAAM,UAAU,WAAW;IACzB,MAAM,KAAK,GAAG,aAAa,EAAE,CAAC;IAC9B,UAAU,CAAC,KAAK,CAAC,CAAC;IAClB,OAAO,KAAK,CAAC;AACf,CAAC;AAED;8CAC8C;AAC9C,MAAM,UAAU,UAAU;IACxB,MAAM,IAAI,GAAG,SAAS,EAAE,CAAC;IACzB,IAAI,CAAC;QACH,IAAI,UAAU,CAAC,IAAI,CAAC;YAAE,UAAU,CAAC,IAAI,CAAC,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,SAAS;IACX,CAAC;AACH,CAAC"}
|