@1presence/bridge 0.1.19 → 0.3.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/dist/auth.js +25 -0
- package/dist/claude.js +30 -0
- package/dist/index.js +5 -0
- package/package.json +1 -1
package/dist/auth.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AuthCancelledError = void 0;
|
|
3
4
|
exports.isTokenValid = isTokenValid;
|
|
4
5
|
exports.ensureFreshToken = ensureFreshToken;
|
|
5
6
|
exports.getValidAuth = getValidAuth;
|
|
@@ -69,8 +70,13 @@ function openBrowser(url) {
|
|
|
69
70
|
console.error('Could not open browser automatically. Please open this URL manually:\n' + url);
|
|
70
71
|
});
|
|
71
72
|
}
|
|
73
|
+
class AuthCancelledError extends Error {
|
|
74
|
+
constructor() { super('Sign-in cancelled — the browser tab was closed.'); }
|
|
75
|
+
}
|
|
76
|
+
exports.AuthCancelledError = AuthCancelledError;
|
|
72
77
|
function runBrowserAuthFlow(gatewayUrl, pwaUrl) {
|
|
73
78
|
return new Promise((resolve, reject) => {
|
|
79
|
+
let resolved = false;
|
|
74
80
|
const server = (0, http_1.createServer)((req, res) => {
|
|
75
81
|
// CORS headers so the PWA (https) can POST to http://localhost
|
|
76
82
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
@@ -86,6 +92,21 @@ function runBrowserAuthFlow(gatewayUrl, pwaUrl) {
|
|
|
86
92
|
res.end();
|
|
87
93
|
return;
|
|
88
94
|
}
|
|
95
|
+
// Status beacon from the PWA — currently used so we exit early when
|
|
96
|
+
// the user closes the auth tab before signing in (sendBeacon path).
|
|
97
|
+
const path = (req.url ?? '/').split('?')[0];
|
|
98
|
+
if (path === '/status') {
|
|
99
|
+
const params = new URL(req.url ?? '/', 'http://localhost').searchParams;
|
|
100
|
+
const event = params.get('event');
|
|
101
|
+
res.writeHead(204);
|
|
102
|
+
res.end();
|
|
103
|
+
if (event === 'closed' && !resolved) {
|
|
104
|
+
resolved = true;
|
|
105
|
+
server.close();
|
|
106
|
+
reject(new AuthCancelledError());
|
|
107
|
+
}
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
89
110
|
let body = '';
|
|
90
111
|
req.on('data', (chunk) => { body += chunk.toString(); });
|
|
91
112
|
req.on('end', () => {
|
|
@@ -98,6 +119,7 @@ function runBrowserAuthFlow(gatewayUrl, pwaUrl) {
|
|
|
98
119
|
}
|
|
99
120
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
100
121
|
res.end(JSON.stringify({ ok: true }));
|
|
122
|
+
resolved = true;
|
|
101
123
|
server.close();
|
|
102
124
|
const uid = uidFromToken(token);
|
|
103
125
|
const email = emailFromToken(token);
|
|
@@ -122,6 +144,9 @@ function runBrowserAuthFlow(gatewayUrl, pwaUrl) {
|
|
|
122
144
|
openBrowser(authUrl);
|
|
123
145
|
});
|
|
124
146
|
setTimeout(() => {
|
|
147
|
+
if (resolved)
|
|
148
|
+
return;
|
|
149
|
+
resolved = true;
|
|
125
150
|
server.close();
|
|
126
151
|
reject(new Error('Sign-in timed out after 5 minutes. Please try again.'));
|
|
127
152
|
}, 5 * 60 * 1000);
|
package/dist/claude.js
CHANGED
|
@@ -122,10 +122,23 @@ function spawnClaude(params) {
|
|
|
122
122
|
if (syncedFolders?.length)
|
|
123
123
|
ctxParts.push(`synced_folders: ${syncedFolders.join(', ')}`);
|
|
124
124
|
const promptText = ctxParts.length > 0 ? `[${ctxParts.join(' | ')}]\n\n${text}` : text;
|
|
125
|
+
// Lockdown rationale:
|
|
126
|
+
// - `--tools ""` disables ALL built-in tools (Bash/Read/Write/Edit/Glob/Grep/
|
|
127
|
+
// WebFetch/etc.). MCP tools are not "built-in" so the 1Presence MCP surface
|
|
128
|
+
// remains available.
|
|
129
|
+
// - `--setting-sources ""` prevents claude CLI from loading the user's
|
|
130
|
+
// ~/.claude/settings.json (and project/.local equivalents). Without this,
|
|
131
|
+
// permissive `permissions.allow` rules in the user's personal Claude Code
|
|
132
|
+
// config would silently re-enable Bash/Edit/Write etc. inside the bridge.
|
|
133
|
+
// - `--strict-mcp-config` keeps the MCP surface to exactly what we wire in
|
|
134
|
+
// via --mcp-config. Together these guarantee the bridge can only call
|
|
135
|
+
// `mcp__1presence__*` — no filesystem, no shell, no arbitrary network.
|
|
125
136
|
const args = [
|
|
126
137
|
'-p', promptText,
|
|
127
138
|
'--output-format', 'stream-json',
|
|
128
139
|
'--verbose',
|
|
140
|
+
'--tools', '',
|
|
141
|
+
'--setting-sources', '',
|
|
129
142
|
'--allowedTools', 'mcp__1presence__*',
|
|
130
143
|
'--system-prompt', systemPromptPath,
|
|
131
144
|
'--mcp-config', mcpConfigPath,
|
|
@@ -153,6 +166,7 @@ function spawnClaude(params) {
|
|
|
153
166
|
let usage = null;
|
|
154
167
|
let extractedModel = null;
|
|
155
168
|
let buffer = '';
|
|
169
|
+
let killedForViolation = false;
|
|
156
170
|
proc.stdout.on('data', (chunk) => {
|
|
157
171
|
buffer += chunk.toString('utf-8');
|
|
158
172
|
const lines = buffer.split('\n');
|
|
@@ -218,6 +232,19 @@ function spawnClaude(params) {
|
|
|
218
232
|
const toolName = block['name'];
|
|
219
233
|
const prefix = toolName.startsWith('mcp__') ? '[mcp]' : '[tool]';
|
|
220
234
|
process.stderr.write(`[bridge] ${prefix} ${toolName}\n`);
|
|
235
|
+
// Defense-in-depth: CLI flags (--tools "", --allowedTools, --strict-mcp-config,
|
|
236
|
+
// --setting-sources "") are supposed to make this unreachable. If we see a
|
|
237
|
+
// non-1Presence tool here anyway, something has bypassed those guards — kill
|
|
238
|
+
// immediately so any side effect already in flight is the only damage done.
|
|
239
|
+
if (!toolName.startsWith('mcp__1presence__')) {
|
|
240
|
+
killedForViolation = true;
|
|
241
|
+
const violation = `bridge tool violation: ${toolName} is not allowed in Local Mode`;
|
|
242
|
+
process.stderr.write(`[bridge] FATAL ${violation} — killing\n`);
|
|
243
|
+
active.delete(conversationId);
|
|
244
|
+
proc.kill('SIGKILL');
|
|
245
|
+
onError(violation, usage, extractedModel);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
221
248
|
}
|
|
222
249
|
else if (block['type'] === 'text') {
|
|
223
250
|
const text = block['text'];
|
|
@@ -248,6 +275,9 @@ function spawnClaude(params) {
|
|
|
248
275
|
});
|
|
249
276
|
proc.on('close', (code) => {
|
|
250
277
|
active.delete(conversationId);
|
|
278
|
+
// Violation path already called onError + killed — don't double-fire.
|
|
279
|
+
if (killedForViolation)
|
|
280
|
+
return;
|
|
251
281
|
// Flush any remaining buffer
|
|
252
282
|
if (buffer.trim()) {
|
|
253
283
|
try {
|
package/dist/index.js
CHANGED
|
@@ -290,6 +290,11 @@ async function main() {
|
|
|
290
290
|
process.on('SIGTERM', shutdown);
|
|
291
291
|
}
|
|
292
292
|
main().catch((err) => {
|
|
293
|
+
if (err instanceof auth_1.AuthCancelledError) {
|
|
294
|
+
console.error(`\n${err.message}`);
|
|
295
|
+
console.error('Run `npx @1presence/bridge` again when you are ready to sign in.');
|
|
296
|
+
process.exit(0);
|
|
297
|
+
}
|
|
293
298
|
console.error('Fatal:', err.message);
|
|
294
299
|
process.exit(1);
|
|
295
300
|
});
|