@tom2012/cc-web 2026.4.26-b → 2026.4.26-d

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 (57) hide show
  1. package/README.md +1 -1
  2. package/backend/dist/approval-manager.d.ts.map +1 -1
  3. package/backend/dist/approval-manager.js +19 -1
  4. package/backend/dist/approval-manager.js.map +1 -1
  5. package/backend/dist/hooks-manager.d.ts.map +1 -1
  6. package/backend/dist/hooks-manager.js +10 -1
  7. package/backend/dist/hooks-manager.js.map +1 -1
  8. package/backend/dist/index.d.ts.map +1 -1
  9. package/backend/dist/index.js +99 -120
  10. package/backend/dist/index.js.map +1 -1
  11. package/backend/dist/notify-service.d.ts.map +1 -1
  12. package/backend/dist/notify-service.js +55 -18
  13. package/backend/dist/notify-service.js.map +1 -1
  14. package/backend/dist/plugin-manager.d.ts +3 -1
  15. package/backend/dist/plugin-manager.d.ts.map +1 -1
  16. package/backend/dist/plugin-manager.js +6 -1
  17. package/backend/dist/plugin-manager.js.map +1 -1
  18. package/backend/dist/routes/claude.d.ts.map +1 -1
  19. package/backend/dist/routes/claude.js +2 -1
  20. package/backend/dist/routes/claude.js.map +1 -1
  21. package/backend/dist/routes/filesystem.d.ts.map +1 -1
  22. package/backend/dist/routes/filesystem.js +88 -25
  23. package/backend/dist/routes/filesystem.js.map +1 -1
  24. package/backend/dist/routes/git.d.ts.map +1 -1
  25. package/backend/dist/routes/git.js +15 -1
  26. package/backend/dist/routes/git.js.map +1 -1
  27. package/backend/dist/routes/notify.d.ts.map +1 -1
  28. package/backend/dist/routes/notify.js +3 -7
  29. package/backend/dist/routes/notify.js.map +1 -1
  30. package/backend/dist/routes/plugin-bridge.d.ts.map +1 -1
  31. package/backend/dist/routes/plugin-bridge.js +53 -3
  32. package/backend/dist/routes/plugin-bridge.js.map +1 -1
  33. package/backend/dist/routes/projects.d.ts.map +1 -1
  34. package/backend/dist/routes/projects.js +22 -1
  35. package/backend/dist/routes/projects.js.map +1 -1
  36. package/frontend/dist/assets/{AssistantMessageContent-BBXSX58q.js → AssistantMessageContent-DqHct3Ck.js} +2 -2
  37. package/frontend/dist/assets/{GraphPreview-BAnpFmtm.js → GraphPreview-DDzwYb3w.js} +2 -2
  38. package/frontend/dist/assets/MobilePage-5nKL4ztD.js +14 -0
  39. package/frontend/dist/assets/{OfficePreview-C6EJB80i.js → OfficePreview-BUzkkas_.js} +2 -2
  40. package/frontend/dist/assets/{ProjectPage-fWJrKu-y.js → ProjectPage-CNFXiRv-.js} +5 -5
  41. package/frontend/dist/assets/SettingsPage-Dhmz6Kys.js +13 -0
  42. package/frontend/dist/assets/{SkillHubPage-DJzYMG0j.js → SkillHubPage-DRLSM0ND.js} +3 -3
  43. package/frontend/dist/assets/{chevron-down-CxmD4ySz.js → chevron-down-CtzzxYuu.js} +1 -1
  44. package/frontend/dist/assets/{chevron-up-BznxDhNi.js → chevron-up-CBkngNQ6.js} +1 -1
  45. package/frontend/dist/assets/index-BrpW1kym.js +75 -0
  46. package/frontend/dist/assets/index-CR3beFmz.css +1 -0
  47. package/frontend/dist/assets/{index-BptnURrX.js → index-UPu5taSd.js} +1 -1
  48. package/frontend/dist/assets/index-YAu_Jeuq.js +13 -0
  49. package/frontend/dist/assets/{jszip.min-CfribT02.js → jszip.min-CIqhAS2U.js} +1 -1
  50. package/frontend/dist/assets/{search-BPzU-LtZ.js → search-W7udjbkr.js} +1 -1
  51. package/frontend/dist/index.html +2 -2
  52. package/package.json +1 -1
  53. package/frontend/dist/assets/MobilePage-BR1R_xht.js +0 -14
  54. package/frontend/dist/assets/SettingsPage-BxT6q4Fq.js +0 -13
  55. package/frontend/dist/assets/index-Big9FXD0.css +0 -1
  56. package/frontend/dist/assets/index-CxHOCf_s.js +0 -75
  57. package/frontend/dist/assets/index-DwNsYVM3.js +0 -1
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  A self-hosted web application (distributed as npm package) that provides a browser-based interface for [Claude Code](https://docs.anthropic.com/en/docs/claude-code) CLI sessions. Create projects, each with a persistent terminal running Claude Code, and interact with them through a real-time terminal UI.
4
4
 
5
- **Current version**: v2026.4.26-b | [GitHub](https://github.com/zbc0315/cc-web) | MIT License
5
+ **Current version**: v2026.4.26-d | [GitHub](https://github.com/zbc0315/cc-web) | MIT License
6
6
 
7
7
  ## Features
8
8
 
@@ -1 +1 @@
1
- {"version":3,"file":"approval-manager.d.ts","sourceRoot":"","sources":["../src/approval-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAYH,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,OAAO,GAAG,MAAM,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAwBD,cAAM,eAAe;IACnB,OAAO,CAAC,OAAO,CAAmC;IAClD,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAA2C;;IAM5D,aAAa,IAAI,MAAM;IAEvB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAI1B,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO;IAQhD,OAAO,CAAC,KAAK;IAIb,qFAAqF;IACrF,QAAQ,CAAC,GAAG,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAkB5E,6EAA6E;IAC7E,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,gBAAgB,GAAG,OAAO;IAWjF,uHAAuH;IACvH,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO;IAWrE,mEAAmE;IACnE,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,eAAe,EAAE;IAWjD,SAAS,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,aAAa,KAAK,IAAI,GAAG,MAAM,IAAI;IAKvD,OAAO,CAAC,IAAI;CAKb;AAED,MAAM,MAAM,aAAa,GACrB,CAAC;IAAE,IAAI,EAAE,kBAAkB,CAAA;CAAE,GAAG,eAAe,CAAC,GAChD;IAAE,IAAI,EAAE,mBAAmB,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAErH,eAAO,MAAM,eAAe,iBAAwB,CAAC"}
1
+ {"version":3,"file":"approval-manager.d.ts","sourceRoot":"","sources":["../src/approval-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAYH,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,OAAO,GAAG,MAAM,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAyCD,cAAM,eAAe;IACnB,OAAO,CAAC,OAAO,CAAmC;IAClD,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAA2C;;IAM5D,aAAa,IAAI,MAAM;IAEvB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAI1B,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO;IAQhD,OAAO,CAAC,KAAK;IAIb,qFAAqF;IACrF,QAAQ,CAAC,GAAG,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAkB5E,6EAA6E;IAC7E,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,gBAAgB,GAAG,OAAO;IAWjF,uHAAuH;IACvH,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO;IAWrE,mEAAmE;IACnE,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,eAAe,EAAE;IAWjD,SAAS,CAAC,EAAE,EAAE,CAAC,GAAG,EAAE,aAAa,KAAK,IAAI,GAAG,MAAM,IAAI;IAKvD,OAAO,CAAC,IAAI;CAKb;AAED,MAAM,MAAM,aAAa,GACrB,CAAC;IAAE,IAAI,EAAE,kBAAkB,CAAA;CAAE,GAAG,eAAe,CAAC,GAChD;IAAE,IAAI,EAAE,mBAAmB,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAErH,eAAO,MAAM,eAAe,iBAAwB,CAAC"}
@@ -67,8 +67,26 @@ function loadOrCreateSecret() {
67
67
  if (fs.existsSync(SECRET_FILE)) {
68
68
  try {
69
69
  const existing = fs.readFileSync(SECRET_FILE, 'utf-8').trim();
70
- if (existing)
70
+ if (existing) {
71
+ // Verify file is still 0600. An earlier ccweb version (or a user
72
+ // copy/restore) may have left it world-readable; tighten in place
73
+ // rather than silently trusting it.
74
+ try {
75
+ const st = fs.statSync(SECRET_FILE);
76
+ const tooOpen = (st.mode & 0o077) !== 0;
77
+ if (tooOpen) {
78
+ try {
79
+ fs.chmodSync(SECRET_FILE, 0o600);
80
+ log.warn({ file: SECRET_FILE, oldMode: (st.mode & 0o777).toString(8) }, 'approval-secret was group/other-readable; restored to 0600');
81
+ }
82
+ catch (err) {
83
+ log.error({ err, file: SECRET_FILE, mode: (st.mode & 0o777).toString(8) }, 'approval-secret has unsafe perms and chmod failed — fix manually before relying on this secret');
84
+ }
85
+ }
86
+ }
87
+ catch { /* stat fail — leave as is */ }
71
88
  return existing;
89
+ }
72
90
  }
73
91
  catch (err) {
74
92
  log.error({ err }, 'approval-secret exists but unreadable — refusing to regenerate (would break active hooks); fix permissions');
@@ -1 +1 @@
1
- {"version":3,"file":"approval-manager.js","sourceRoot":"","sources":["../src/approval-manager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"approval-manager.js","sourceRoot":"","sources":["../src/approval-manager.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"hooks-manager.d.ts","sourceRoot":"","sources":["../src/hooks-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AA8BH,cAAM,YAAY;IAChB,OAAO,CAAC,QAAQ,CAAS;gBAEb,QAAQ,EAAE,MAAM;IAI5B,wFAAwF;IACxF,OAAO,CAAC,mBAAmB;IAwC3B,2CAA2C;IAC3C,OAAO,CAAC,iBAAiB;IAoCzB,6DAA6D;IAC7D,SAAS,IAAI,IAAI;IAMjB,mGAAmG;IACnG,OAAO,IAAI,IAAI;IAMf,WAAW,IAAI,OAAO;CAavB;AAED,OAAO,EAAE,YAAY,EAAE,CAAC"}
1
+ {"version":3,"file":"hooks-manager.d.ts","sourceRoot":"","sources":["../src/hooks-manager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAuCH,cAAM,YAAY;IAChB,OAAO,CAAC,QAAQ,CAAS;gBAEb,QAAQ,EAAE,MAAM;IAI5B,wFAAwF;IACxF,OAAO,CAAC,mBAAmB;IAwC3B,2CAA2C;IAC3C,OAAO,CAAC,iBAAiB;IAoCzB,6DAA6D;IAC7D,SAAS,IAAI,IAAI;IAMjB,mGAAmG;IACnG,OAAO,IAAI,IAAI;IAMf,WAAW,IAAI,OAAO;CAavB;AAED,OAAO,EAAE,YAAY,EAAE,CAAC"}
@@ -62,7 +62,16 @@ function readSettings(settingsPath) {
62
62
  if (!fs.existsSync(settingsPath))
63
63
  return {};
64
64
  try {
65
- return JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
65
+ const parsed = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
66
+ // Refuse non-plain-object roots — a settings.json that parsed to an
67
+ // array or primitive would survive readSettings, get a named property
68
+ // assigned (`settings.hooks = …`), and stringify back into invalid
69
+ // structure that Claude Code can no longer parse.
70
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
71
+ log.warn({ settingsPath }, 'settings file is not a plain object — hook management skipped');
72
+ return null;
73
+ }
74
+ return parsed;
66
75
  }
67
76
  catch {
68
77
  log.warn({ settingsPath }, 'settings file has invalid JSON — hook management skipped');
@@ -1 +1 @@
1
- {"version":3,"file":"hooks-manager.js","sourceRoot":"","sources":["../src/hooks-manager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"hooks-manager.js","sourceRoot":"","sources":["../src/hooks-manager.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA+EA,QAAA,MAAM,GAAG,6CAAY,CAAC;AAg0BtB,eAAe,GAAG,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA+EA,QAAA,MAAM,GAAG,6CAAY,CAAC;AAk0BtB,eAAe,GAAG,CAAC"}
@@ -323,15 +323,43 @@ function writeTerminalInputSplit(projectId, data) {
323
323
  terminal_manager_1.terminalManager.writeRaw(projectId, submitCr);
324
324
  });
325
325
  }
326
+ /**
327
+ * Bounded WS send. Skips closed sockets, enforces a per-socket bufferedAmount
328
+ * ceiling so a slow/stuck client cannot accumulate unbounded backlog. When the
329
+ * ceiling is hit we terminate the socket — preferable to silently growing the
330
+ * Node heap. Replaces direct `ws.send` calls per CLAUDE.md red line ("WS send
331
+ * 必须经队列, 不直接 ws.send").
332
+ *
333
+ * Threshold (8 MiB) is generous for normal terminal data + chat blocks; only
334
+ * pathological clients hit it. A real queue with drop policy would be more
335
+ * sophisticated but this single guard already closes the unbounded-memory hole.
336
+ */
337
+ const WS_BACKPRESSURE_LIMIT_BYTES = 8 * 1024 * 1024;
338
+ function safeSend(ws, payload) {
339
+ if (ws.readyState !== WebSocket.WebSocket.OPEN)
340
+ return false;
341
+ if (ws.bufferedAmount > WS_BACKPRESSURE_LIMIT_BYTES) {
342
+ try {
343
+ ws.terminate();
344
+ }
345
+ catch { /**/ }
346
+ return false;
347
+ }
348
+ try {
349
+ ws.send(payload);
350
+ return true;
351
+ }
352
+ catch {
353
+ return false;
354
+ }
355
+ }
326
356
  function broadcast(projectId, rawData) {
327
357
  const clients = projectClients.get(projectId);
328
358
  if (!clients)
329
359
  return;
330
360
  const payload = JSON.stringify({ type: 'terminal_data', data: rawData });
331
- for (const client of clients) {
332
- if (client.readyState === WebSocket.WebSocket.OPEN)
333
- client.send(payload);
334
- }
361
+ for (const client of clients)
362
+ safeSend(client, payload);
335
363
  }
336
364
  // Approval events leak tool inputs (command strings, file paths). Withhold from view-only clients.
337
365
  approval_manager_1.approvalManager.subscribe((evt) => {
@@ -342,14 +370,9 @@ approval_manager_1.approvalManager.subscribe((evt) => {
342
370
  return;
343
371
  const payload = JSON.stringify(evt);
344
372
  for (const client of clients) {
345
- if (client.readyState !== WebSocket.WebSocket.OPEN)
346
- continue;
347
373
  if (client.__readOnly)
348
374
  continue;
349
- try {
350
- client.send(payload);
351
- }
352
- catch { /* ignore */ }
375
+ safeSend(client, payload);
353
376
  }
354
377
  });
355
378
  function isLocalWs(req) {
@@ -359,6 +382,32 @@ function isLocalWs(req) {
359
382
  // ── Dashboard WebSocket clients (activity push) ─────────────────────────────
360
383
  const dashboardClients = new Set();
361
384
  const SEMANTIC_STALE_MS = 30000;
385
+ /**
386
+ * Per-user visibility check used by every dashboard WS broadcast. Without it,
387
+ * any connected user sees activity / semantic / project_stopped events for
388
+ * every other user's projects (cross-user information leak). __username is
389
+ * tagged on the socket at auth time (localhost path → admin; jwt path → user).
390
+ */
391
+ function userCanSeeProject(username, projectId) {
392
+ if (!username)
393
+ return false;
394
+ if ((0, config_1.isAdminUser)(username))
395
+ return true; // admin (incl. localhost preauth) sees all
396
+ const project = (0, config_1.getProject)(projectId);
397
+ if (!project)
398
+ return false;
399
+ if ((0, config_1.isProjectOwner)(project, username))
400
+ return true;
401
+ return project.shares?.some((s) => s.username === username) ?? false;
402
+ }
403
+ function sendToDashboardClientsForProject(projectId, payload) {
404
+ for (const client of dashboardClients) {
405
+ const clientUser = client.__username;
406
+ if (!userCanSeeProject(clientUser, projectId))
407
+ continue;
408
+ safeSend(client, payload);
409
+ }
410
+ }
362
411
  function broadcastDashboardActivity(projectId, lastActivityAt) {
363
412
  if (dashboardClients.size === 0)
364
413
  return;
@@ -373,14 +422,7 @@ function broadcastDashboardActivity(projectId, lastActivityAt) {
373
422
  status: terminal_manager_1.terminalManager.getProjectStatus(projectId),
374
423
  semantic: semantic && !stale ? semantic : undefined,
375
424
  });
376
- for (const client of dashboardClients) {
377
- if (client.readyState === WebSocket.OPEN) {
378
- try {
379
- client.send(payload);
380
- }
381
- catch { /**/ }
382
- }
383
- }
425
+ sendToDashboardClientsForProject(projectId, payload);
384
426
  }
385
427
  function broadcastDashboardSemantic(projectId, status) {
386
428
  if (dashboardClients.size === 0)
@@ -397,14 +439,7 @@ function broadcastDashboardSemantic(projectId, status) {
397
439
  status: terminal_manager_1.terminalManager.getProjectStatus(projectId),
398
440
  semantic: status ?? undefined,
399
441
  });
400
- for (const client of dashboardClients) {
401
- if (client.readyState === WebSocket.OPEN) {
402
- try {
403
- client.send(payload);
404
- }
405
- catch { /**/ }
406
- }
407
- }
442
+ sendToDashboardClientsForProject(projectId, payload);
408
443
  }
409
444
  function buildSemanticSnapshot(projectId) {
410
445
  const semantic = session_manager_1.sessionManager.getSemanticStatus(projectId);
@@ -416,14 +451,8 @@ function broadcastProjectSemantic(projectId, status) {
416
451
  if (!clients || clients.size === 0)
417
452
  return;
418
453
  const payload = JSON.stringify({ type: 'semantic_update', active: !!status, semantic: status ?? undefined });
419
- for (const client of clients) {
420
- if (client.readyState === WebSocket.OPEN) {
421
- try {
422
- client.send(payload);
423
- }
424
- catch { /**/ }
425
- }
426
- }
454
+ for (const client of clients)
455
+ safeSend(client, payload);
427
456
  }
428
457
  /**
429
458
  * PTY-driven `semantic_update` push. Emits `{active: true, semantic?:...}`
@@ -447,14 +476,8 @@ function broadcastProjectActivity(projectId, lastActivityAt) {
447
476
  active: Date.now() - lastActivityAt < 3000,
448
477
  semantic: fresh ? semantic : undefined,
449
478
  });
450
- for (const client of clients) {
451
- if (client.readyState === WebSocket.OPEN) {
452
- try {
453
- client.send(payload);
454
- }
455
- catch { /**/ }
456
- }
457
- }
479
+ for (const client of clients)
480
+ safeSend(client, payload);
458
481
  }
459
482
  /**
460
483
  * When PTY / semantic activity stops, the project WS needs an explicit
@@ -474,14 +497,8 @@ function broadcastProjectIdle(projectId) {
474
497
  if (!clients || clients.size === 0)
475
498
  return;
476
499
  const payload = JSON.stringify({ type: 'semantic_update', active: false, semantic: undefined });
477
- for (const client of clients) {
478
- if (client.readyState === WebSocket.OPEN) {
479
- try {
480
- client.send(payload);
481
- }
482
- catch { /**/ }
483
- }
484
- }
500
+ for (const client of clients)
501
+ safeSend(client, payload);
485
502
  }
486
503
  function armProjectIdleTimer(projectId) {
487
504
  const prev = projectIdleTimers.get(projectId);
@@ -503,16 +520,20 @@ function initProjectTerminal(project, projectId) {
503
520
  terminal_manager_1.terminalManager.updateBroadcast(projectId, fn);
504
521
  }
505
522
  function sendActivitySnapshot(ws) {
523
+ const username = ws.__username;
506
524
  const allActivity = terminal_manager_1.terminalManager.getAllActivity();
507
525
  const allSemantic = session_manager_1.sessionManager.getAllSemanticStatus();
508
526
  // Include all running/restarting projects, even those with no PTY output yet
509
527
  const allRunningIds = new Set([...Object.keys(allActivity), ...terminal_manager_1.terminalManager.getAllRunningIds()]);
510
528
  const now = Date.now();
511
529
  for (const id of allRunningIds) {
530
+ // Per-user filter: never leak other users' project state on initial snapshot.
531
+ if (!userCanSeeProject(username, id))
532
+ continue;
512
533
  const lastActivityAt = allActivity[id] ?? 0;
513
534
  const semantic = allSemantic[id];
514
535
  const stale = semantic && now - semantic.updatedAt > SEMANTIC_STALE_MS;
515
- ws.send(JSON.stringify({
536
+ safeSend(ws, JSON.stringify({
516
537
  type: 'activity_update',
517
538
  projectId: id,
518
539
  lastActivityAt,
@@ -545,27 +566,12 @@ session_manager_1.sessionManager.on('semantic', ({ projectId, status }) => {
545
566
  });
546
567
  notify_service_1.notifyService.on('stopped', ({ projectId, projectName }) => {
547
568
  const msg = JSON.stringify({ type: 'project_stopped', projectId, projectName });
548
- // Broadcast to dashboard clients
549
- for (const client of dashboardClients) {
550
- if (client.readyState === WebSocket.OPEN) {
551
- try {
552
- client.send(msg);
553
- }
554
- catch { /**/ }
555
- }
556
- }
557
- // Broadcast to project-specific clients (so ProjectPage also receives notifications)
569
+ // Per-user filter on dashboard side (project clients are already per-project + ownership-gated at subscribe time).
570
+ sendToDashboardClientsForProject(projectId, msg);
558
571
  const clients = projectClients.get(projectId);
559
- if (clients) {
560
- for (const client of clients) {
561
- if (client.readyState === WebSocket.OPEN) {
562
- try {
563
- client.send(msg);
564
- }
565
- catch { /**/ }
566
- }
567
- }
568
- }
572
+ if (clients)
573
+ for (const client of clients)
574
+ safeSend(client, msg);
569
575
  });
570
576
  // ── Sync progress bridge ────────────────────────────────────────────────────
571
577
  // Forward rsync-driven start/progress/done events to both the per-project WS
@@ -581,26 +587,14 @@ notify_service_1.notifyService.on('stopped', ({ projectId, projectName }) => {
581
587
  sync_service_1.syncEvents.on('event', (evt) => {
582
588
  const payload = JSON.stringify({ type: `sync.${evt.kind}`, ...evt });
583
589
  const projectBucket = projectClients.get(evt.projectId);
584
- if (projectBucket) {
585
- for (const client of projectBucket) {
586
- if (client.readyState === WebSocket.OPEN) {
587
- try {
588
- client.send(payload);
589
- }
590
- catch { /**/ }
591
- }
592
- }
593
- }
590
+ if (projectBucket)
591
+ for (const client of projectBucket)
592
+ safeSend(client, payload);
594
593
  for (const client of dashboardClients) {
595
- if (client.readyState !== WebSocket.OPEN)
596
- continue;
597
594
  const clientUser = client.__username;
598
595
  if (clientUser !== evt.username)
599
596
  continue;
600
- try {
601
- client.send(payload);
602
- }
603
- catch { /**/ }
597
+ safeSend(client, payload);
604
598
  }
605
599
  });
606
600
  wss.on('connection', (ws, req) => {
@@ -650,10 +644,7 @@ wss.on('connection', (ws, req) => {
650
644
  let authenticated = localConnection; // localhost = pre-authenticated
651
645
  let wsReadOnly = false; // true for view-only shared projects
652
646
  const chatListener = (msg) => {
653
- try {
654
- ws.send(JSON.stringify({ type: 'chat_message', ...msg }));
655
- }
656
- catch { /**/ }
647
+ safeSend(ws, JSON.stringify({ type: 'chat_message', ...msg }));
657
648
  };
658
649
  const authTimeout = localConnection ? null : setTimeout(() => {
659
650
  if (!authenticated)
@@ -663,13 +654,13 @@ wss.on('connection', (ws, req) => {
663
654
  if (localConnection) {
664
655
  const project = (0, config_1.getProject)(projectId);
665
656
  if (!project) {
666
- ws.send(JSON.stringify({ type: 'error', message: 'Project not found' }));
657
+ safeSend(ws, JSON.stringify({ type: 'error', message: 'Project not found' }));
667
658
  ws.close(1008, 'Project not found');
668
659
  return;
669
660
  }
670
661
  initProjectTerminal(project, projectId);
671
- ws.send(JSON.stringify({ type: 'connected', projectId }));
672
- ws.send(JSON.stringify({ type: 'status', status: project.status }));
662
+ safeSend(ws, JSON.stringify({ type: 'connected', projectId }));
663
+ safeSend(ws, JSON.stringify({ type: 'status', status: project.status }));
673
664
  }
674
665
  ws.on('message', (rawMsg) => {
675
666
  try {
@@ -696,7 +687,7 @@ wss.on('connection', (ws, req) => {
696
687
  authenticated = true;
697
688
  const project = (0, config_1.getProject)(projectId);
698
689
  if (!project) {
699
- ws.send(JSON.stringify({ type: 'error', message: 'Project not found' }));
690
+ safeSend(ws, JSON.stringify({ type: 'error', message: 'Project not found' }));
700
691
  ws.close(1008, 'Project not found');
701
692
  return;
702
693
  }
@@ -705,7 +696,7 @@ wss.on('connection', (ws, req) => {
705
696
  if (!(0, config_1.isProjectOwner)(project, wsUsername)) {
706
697
  const share = project.shares?.find((s) => s.username === wsUsername);
707
698
  if (!share) {
708
- ws.send(JSON.stringify({ type: 'error', message: 'Access denied' }));
699
+ safeSend(ws, JSON.stringify({ type: 'error', message: 'Access denied' }));
709
700
  ws.close(1008, 'Access denied');
710
701
  return;
711
702
  }
@@ -714,8 +705,8 @@ wss.on('connection', (ws, req) => {
714
705
  }
715
706
  ws.__readOnly = wsReadOnly;
716
707
  initProjectTerminal(project, projectId);
717
- ws.send(JSON.stringify({ type: 'connected', projectId, readOnly: wsReadOnly }));
718
- ws.send(JSON.stringify({ type: 'status', status: project.status }));
708
+ safeSend(ws, JSON.stringify({ type: 'connected', projectId, readOnly: wsReadOnly }));
709
+ safeSend(ws, JSON.stringify({ type: 'status', status: project.status }));
719
710
  return;
720
711
  }
721
712
  // For local connections, skip the auth message if sent anyway
@@ -732,22 +723,22 @@ wss.on('connection', (ws, req) => {
732
723
  {
733
724
  const scrollback = terminal_manager_1.terminalManager.getScrollback(projectId);
734
725
  if (scrollback)
735
- ws.send(JSON.stringify({ type: 'terminal_data', data: scrollback }));
726
+ safeSend(ws, JSON.stringify({ type: 'terminal_data', data: scrollback }));
736
727
  }
737
728
  // Register as a live client
738
729
  if (!projectClients.has(projectId))
739
730
  projectClients.set(projectId, new Set());
740
731
  projectClients.get(projectId).add(ws);
741
- ws.send(JSON.stringify({ type: 'terminal_subscribed' }));
732
+ safeSend(ws, JSON.stringify({ type: 'terminal_subscribed' }));
742
733
  // Send initial context data if available
743
734
  {
744
735
  const ctxData = (0, hooks_1.getContextData)(projectId);
745
736
  if (ctxData)
746
- ws.send(JSON.stringify({ type: 'context_update', ...ctxData }));
737
+ safeSend(ws, JSON.stringify({ type: 'context_update', ...ctxData }));
747
738
  }
748
739
  {
749
740
  const snap = buildSemanticSnapshot(projectId);
750
- ws.send(JSON.stringify({ type: 'semantic_update', ...snap }));
741
+ safeSend(ws, JSON.stringify({ type: 'semantic_update', ...snap }));
751
742
  }
752
743
  break;
753
744
  case 'terminal_input':
@@ -773,10 +764,7 @@ wss.on('connection', (ws, req) => {
773
764
  const history = session_manager_1.sessionManager.getChatHistory(projectId);
774
765
  const slice = replayLimit >= history.length ? history : history.slice(-replayLimit);
775
766
  for (const block of slice) {
776
- try {
777
- ws.send(JSON.stringify({ type: 'chat_message', ...block }));
778
- }
779
- catch { /**/ }
767
+ safeSend(ws, JSON.stringify({ type: 'chat_message', ...block }));
780
768
  }
781
769
  }
782
770
  }
@@ -790,21 +778,18 @@ wss.on('connection', (ws, req) => {
790
778
  {
791
779
  const ctxData = (0, hooks_1.getContextData)(projectId);
792
780
  if (ctxData)
793
- ws.send(JSON.stringify({ type: 'context_update', ...ctxData }));
781
+ safeSend(ws, JSON.stringify({ type: 'context_update', ...ctxData }));
794
782
  }
795
783
  {
796
784
  const snap = buildSemanticSnapshot(projectId);
797
- ws.send(JSON.stringify({ type: 'semantic_update', ...snap }));
785
+ safeSend(ws, JSON.stringify({ type: 'semantic_update', ...snap }));
798
786
  }
799
787
  break;
800
788
  }
801
789
  }
802
790
  catch (err) {
803
791
  log.error({ err, projectId, mod: 'ws' }, 'ws message handling error');
804
- try {
805
- ws.send(JSON.stringify({ type: 'error', message: 'Internal server error' }));
806
- }
807
- catch { /**/ }
792
+ safeSend(ws, JSON.stringify({ type: 'error', message: 'Internal server error' }));
808
793
  }
809
794
  });
810
795
  ws.on('close', () => {
@@ -893,14 +878,8 @@ function tryListen(port, maxAttempts = 20) {
893
878
  if (!clients)
894
879
  return;
895
880
  const payload = JSON.stringify({ type: 'context_update', ...data });
896
- for (const client of clients) {
897
- if (client.readyState === WebSocket.OPEN) {
898
- try {
899
- client.send(payload);
900
- }
901
- catch { /**/ }
902
- }
903
- }
881
+ for (const client of clients)
882
+ safeSend(client, payload);
904
883
  });
905
884
  });
906
885
  }