@ccpocket/bridge 1.32.0 → 1.33.1

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/websocket.js CHANGED
@@ -20,6 +20,7 @@ import { PushRelayClient } from "./push-relay.js";
20
20
  import { normalizePushLocale, t } from "./push-i18n.js";
21
21
  import { fetchAllUsage } from "./usage.js";
22
22
  import { getPackageVersion } from "./version.js";
23
+ import { isPathWithinAllowedDirectory, resolvePlatformPath, } from "./path-utils.js";
23
24
  // ---- Available model lists (delivered to clients via session_list) ----
24
25
  const CLAUDE_MODELS = [
25
26
  "claude-opus-4-6",
@@ -151,8 +152,9 @@ export class BridgeWebSocketServer {
151
152
  tokenPrivacyMode = new Map();
152
153
  failSetPermissionMode = envFlagEnabled("BRIDGE_FAIL_SET_PERMISSION_MODE");
153
154
  failSetSandboxMode = envFlagEnabled("BRIDGE_FAIL_SET_SANDBOX_MODE");
155
+ platform;
154
156
  constructor(options) {
155
- const { server, apiKey, allowedDirs, imageStore, galleryStore, projectHistory, debugTraceStore, recordingStore, firebaseAuth, promptHistoryBackup, } = options;
157
+ const { server, apiKey, allowedDirs, imageStore, galleryStore, projectHistory, debugTraceStore, recordingStore, firebaseAuth, promptHistoryBackup, platform, } = options;
156
158
  this.apiKey = apiKey ?? null;
157
159
  this.allowedDirs = allowedDirs ?? [];
158
160
  this.imageStore = imageStore ?? null;
@@ -163,6 +165,7 @@ export class BridgeWebSocketServer {
163
165
  this.worktreeStore = new WorktreeStore();
164
166
  this.pushRelay = new PushRelayClient({ firebaseAuth });
165
167
  this.promptHistoryBackup = promptHistoryBackup ?? null;
168
+ this.platform = platform ?? process.platform;
166
169
  this.archiveStore = new ArchiveStore();
167
170
  void this.debugTraceStore.init().catch((err) => {
168
171
  console.error("[ws] Failed to initialize debug trace store:", err);
@@ -218,8 +221,7 @@ export class BridgeWebSocketServer {
218
221
  isPathAllowed(path) {
219
222
  if (this.allowedDirs.length === 0)
220
223
  return true;
221
- const resolved = resolve(path);
222
- return this.allowedDirs.some((dir) => resolved === dir || resolved.startsWith(dir + "/"));
224
+ return this.allowedDirs.some((dir) => isPathWithinAllowedDirectory(path, dir, this.platform));
223
225
  }
224
226
  /** Build a user-friendly error for disallowed project paths. */
225
227
  buildPathNotAllowedError(projectPath) {
@@ -385,7 +387,8 @@ export class BridgeWebSocketServer {
385
387
  }
386
388
  switch (msg.type) {
387
389
  case "start": {
388
- if (!this.isPathAllowed(msg.projectPath)) {
390
+ const projectPath = resolvePlatformPath(msg.projectPath, this.platform);
391
+ if (!this.isPathAllowed(projectPath)) {
389
392
  this.send(ws, this.buildPathNotAllowedError(msg.projectPath));
390
393
  break;
391
394
  }
@@ -414,9 +417,9 @@ export class BridgeWebSocketServer {
414
417
  console.log(`[ws] start(codex): execution=${executionMode} plan=${planMode}`);
415
418
  }
416
419
  const cached = provider === "claude"
417
- ? this.sessionManager.getCachedCommands(msg.projectPath)
420
+ ? this.sessionManager.getCachedCommands(projectPath)
418
421
  : undefined;
419
- const sessionId = this.sessionManager.create(msg.projectPath, {
422
+ const sessionId = this.sessionManager.create(projectPath, {
420
423
  sessionId: msg.sessionId,
421
424
  continueMode: msg.continue,
422
425
  permissionMode: legacyPermissionMode,
@@ -453,11 +456,11 @@ export class BridgeWebSocketServer {
453
456
  : undefined);
454
457
  const createdSession = this.sessionManager.get(sessionId);
455
458
  // Load saved session name from CLI storage (for resumed sessions)
456
- void this.loadAndSetSessionName(createdSession, provider, msg.projectPath, msg.sessionId).then(() => {
459
+ void this.loadAndSetSessionName(createdSession, provider, projectPath, msg.sessionId).then(() => {
457
460
  this.send(ws, this.buildSessionCreatedMessage({
458
461
  sessionId,
459
462
  provider,
460
- projectPath: msg.projectPath,
463
+ projectPath,
461
464
  session: createdSession,
462
465
  permissionMode: legacyPermissionMode,
463
466
  executionMode,
@@ -491,14 +494,14 @@ export class BridgeWebSocketServer {
491
494
  direction: "internal",
492
495
  channel: "bridge",
493
496
  type: "session_created",
494
- detail: `provider=${provider} projectPath=${msg.projectPath}`,
497
+ detail: `provider=${provider} projectPath=${projectPath}`,
495
498
  });
496
499
  this.recordingStore?.saveMeta(sessionId, {
497
500
  bridgeSessionId: sessionId,
498
- projectPath: msg.projectPath,
501
+ projectPath,
499
502
  createdAt: new Date().toISOString(),
500
503
  });
501
- this.projectHistory?.addProject(msg.projectPath);
504
+ this.projectHistory?.addProject(projectPath);
502
505
  }
503
506
  catch (err) {
504
507
  console.error(`[ws] Failed to start session:`, err);
@@ -1481,7 +1484,8 @@ export class BridgeWebSocketServer {
1481
1484
  }
1482
1485
  case "resume_session": {
1483
1486
  console.log(`[ws] resume_session: sessionId=${msg.sessionId} projectPath=${msg.projectPath} provider=${msg.provider ?? "claude"}`);
1484
- if (!this.isPathAllowed(msg.projectPath)) {
1487
+ const resumeProjectPath = resolvePlatformPath(msg.projectPath, this.platform);
1488
+ if (!this.isPathAllowed(resumeProjectPath)) {
1485
1489
  this.send(ws, this.buildPathNotAllowedError(msg.projectPath));
1486
1490
  break;
1487
1491
  }
@@ -1510,7 +1514,7 @@ export class BridgeWebSocketServer {
1510
1514
  // via get_history(sessionId) to avoid duplicate/missed replay races.
1511
1515
  if (provider === "codex") {
1512
1516
  const wtMapping = this.worktreeStore.get(sessionRefId);
1513
- const effectiveProjectPath = wtMapping?.projectPath ?? msg.projectPath;
1517
+ const effectiveProjectPath = resolvePlatformPath(wtMapping?.projectPath ?? resumeProjectPath, this.platform);
1514
1518
  let worktreeOpts;
1515
1519
  if (wtMapping) {
1516
1520
  if (worktreeExists(wtMapping.worktreePath)) {
@@ -1576,7 +1580,7 @@ export class BridgeWebSocketServer {
1576
1580
  break;
1577
1581
  }
1578
1582
  const claudeSessionId = sessionRefId;
1579
- const cached = this.sessionManager.getCachedCommands(msg.projectPath);
1583
+ const cached = this.sessionManager.getCachedCommands(resumeProjectPath);
1580
1584
  // Look up worktree mapping for this Claude session
1581
1585
  const wtMapping = this.worktreeStore.get(claudeSessionId);
1582
1586
  let worktreeOpts;
@@ -1598,7 +1602,7 @@ export class BridgeWebSocketServer {
1598
1602
  }
1599
1603
  getSessionHistory(claudeSessionId)
1600
1604
  .then((pastMessages) => {
1601
- const sessionId = this.sessionManager.create(msg.projectPath, {
1605
+ const sessionId = this.sessionManager.create(resumeProjectPath, {
1602
1606
  sessionId: claudeSessionId,
1603
1607
  permissionMode: legacyPermissionMode,
1604
1608
  model: msg.model,
@@ -1613,12 +1617,12 @@ export class BridgeWebSocketServer {
1613
1617
  : {}),
1614
1618
  }, pastMessages, worktreeOpts);
1615
1619
  const createdSession = this.sessionManager.get(sessionId);
1616
- void this.loadAndSetSessionName(createdSession, "claude", msg.projectPath, claudeSessionId).then(() => {
1620
+ void this.loadAndSetSessionName(createdSession, "claude", resumeProjectPath, claudeSessionId).then(() => {
1617
1621
  this.send(ws, {
1618
1622
  ...this.buildSessionCreatedMessage({
1619
1623
  sessionId,
1620
1624
  provider: "claude",
1621
- projectPath: msg.projectPath,
1625
+ projectPath: resumeProjectPath,
1622
1626
  session: createdSession,
1623
1627
  permissionMode: legacyPermissionMode,
1624
1628
  executionMode,
@@ -1645,7 +1649,7 @@ export class BridgeWebSocketServer {
1645
1649
  type: "session_resumed",
1646
1650
  detail: `provider=claude session=${claudeSessionId}`,
1647
1651
  });
1648
- this.projectHistory?.addProject(msg.projectPath);
1652
+ this.projectHistory?.addProject(resumeProjectPath);
1649
1653
  })
1650
1654
  .catch((err) => {
1651
1655
  this.send(ws, {
@@ -3333,7 +3337,7 @@ export class BridgeWebSocketServer {
3333
3337
  else {
3334
3338
  const absPath = resolve(cwd, filePath);
3335
3339
  // Verify resolved path stays within cwd
3336
- if (!absPath.startsWith(resolve(cwd) + "/")) {
3340
+ if (!isPathWithinAllowedDirectory(absPath, cwd, this.platform)) {
3337
3341
  return { error: "Invalid file path" };
3338
3342
  }
3339
3343
  buf = await readFile(absPath);