@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 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
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1presence/bridge",
3
- "version": "0.1.19",
3
+ "version": "0.3.0",
4
4
  "description": "Run 1Presence on your Mac and use your Claude.ai Pro subscription from any device",
5
5
  "bin": {
6
6
  "1presence-bridge": "dist/index.js"