@livedesk/client 0.1.238 → 0.1.239

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.
@@ -251,7 +251,7 @@ async function waitForClientUpdateShutdownSignal(
251
251
  return { ready: false, error };
252
252
  }
253
253
 
254
- function markClientUpdateConnected() {
254
+ function markClientUpdateConnected() {
255
255
  const operationId = String(process.env.LIVEDESK_CLIENT_UPDATE_OPERATION_ID || '').trim();
256
256
  if (!operationId) return;
257
257
  void writeClientUpdateState('connected', {
@@ -262,8 +262,59 @@ function markClientUpdateConnected() {
262
262
  error: ''
263
263
  }).catch(error => {
264
264
  console.error(`LiveDesk client update diagnostics could not be persisted: ${error?.message || error}`);
265
- });
266
- }
265
+ });
266
+ }
267
+
268
+ async function publishClientUpdateSupervisorProof(socket) {
269
+ const operationId = String(process.env.LIVEDESK_CLIENT_UPDATE_OPERATION_ID || '').trim();
270
+ const attemptId = String(process.env.LIVEDESK_CLIENT_UPDATE_ATTEMPT_ID || '').trim();
271
+ const expectedLauncherPid = Number(process.env.LIVEDESK_CLIENT_PARENT_PID || 0);
272
+ if (!operationId || !attemptId || !Number.isInteger(expectedLauncherPid) || expectedLauncherPid <= 1) return;
273
+ const deadline = Date.now() + 60_000;
274
+ while (Date.now() < deadline && !socket.destroyed) {
275
+ try {
276
+ const state = JSON.parse(await fs.readFile(getClientUpdateStatePath(), 'utf8'));
277
+ if (String(state?.operationId || '') !== operationId
278
+ || String(state?.attemptId || '') !== attemptId) {
279
+ await wait(100);
280
+ continue;
281
+ }
282
+ if (['failed', 'restored', 'cancelled'].includes(String(state.stage || ''))) return;
283
+ const launcherPid = Number(state.launcherPid || 0);
284
+ const agentPid = Number(state.agentPid || 0);
285
+ const supervisorPid = Number(state.supervisorPid || 0);
286
+ const completedAt = String(state.completedAt || '').trim();
287
+ const exactSupervisorCompletion = state.stage === 'connected'
288
+ && state.restartVerified === true
289
+ && Number.isFinite(Date.parse(completedAt))
290
+ && launcherPid === expectedLauncherPid
291
+ && agentPid === process.pid
292
+ && Number.isInteger(supervisorPid)
293
+ && supervisorPid > 1
294
+ && String(state.productVersion || '') === PRODUCT_VERSION
295
+ && String(state.agentVersion || '') === AGENT_VERSION
296
+ && String(state.targetProductVersion || '') === PRODUCT_VERSION
297
+ && String(state.targetAgentVersion || state.targetVersion || '') === AGENT_VERSION;
298
+ if (exactSupervisorCompletion) {
299
+ writeJsonLine(socket, {
300
+ type: 'client.update.verified',
301
+ operationId,
302
+ attemptId,
303
+ launcherPid,
304
+ agentPid,
305
+ supervisorPid,
306
+ productVersion: PRODUCT_VERSION,
307
+ agentVersion: AGENT_VERSION,
308
+ completedAt
309
+ });
310
+ return;
311
+ }
312
+ } catch {
313
+ // The exact package supervisor may be replacing the atomic state file.
314
+ }
315
+ await wait(100);
316
+ }
317
+ }
267
318
 
268
319
  function printHelp() {
269
320
  console.log(`
@@ -1972,9 +2023,10 @@ function connectOnce(options, deviceId) {
1972
2023
  remoteFiles: true,
1973
2024
  fileTransferMaxBytes: MAX_FILE_TRANSFER_BYTES,
1974
2025
  computerAgent: options.taskEnabled,
1975
- taskDispatch: options.taskEnabled,
1976
- clientUpdate: true,
1977
- productVersion: PRODUCT_VERSION,
2026
+ taskDispatch: options.taskEnabled,
2027
+ clientUpdate: true,
2028
+ clientUpdateVerifiedProof: true,
2029
+ productVersion: PRODUCT_VERSION,
1978
2030
  agentApproval: options.taskEnabled,
1979
2031
  agentAudit: options.taskEnabled,
1980
2032
  agentTools: [...NODE_AGENT_OPERATIONS],
@@ -2000,8 +2052,13 @@ function connectOnce(options, deviceId) {
2000
2052
  options.effectivePolicy = message.effectivePolicy && typeof message.effectivePolicy === 'object'
2001
2053
  ? message.effectivePolicy
2002
2054
  : null;
2003
- console.log(`Connected to LiveDesk Hub as ${options.name} (${deviceId})`);
2004
- markClientUpdateConnected();
2055
+ console.log(`Connected to LiveDesk Hub as ${options.name} (${deviceId})`);
2056
+ markClientUpdateConnected();
2057
+ void publishClientUpdateSupervisorProof(socket).catch(error => {
2058
+ if (!socket.destroyed) {
2059
+ console.error(`LiveDesk client update verification proof failed: ${error?.message || error}`);
2060
+ }
2061
+ });
2005
2062
  writeJsonLine(socket, {
2006
2063
  type: 'status',
2007
2064
  status: getStatus(options)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livedesk/client",
3
- "version": "0.1.238",
3
+ "version": "0.1.239",
4
4
  "description": "LiveDesk local remote client",
5
5
  "type": "module",
6
6
  "bin": {
@@ -42,10 +42,10 @@
42
42
  "ws": "^8.18.3"
43
43
  },
44
44
  "optionalDependencies": {
45
- "@livedesk/fast-linux-x64": "0.1.436",
46
- "@livedesk/fast-osx-arm64": "0.1.436",
47
- "@livedesk/fast-osx-x64": "0.1.436",
48
- "@livedesk/fast-win-x64": "0.1.436"
45
+ "@livedesk/fast-linux-x64": "0.1.437",
46
+ "@livedesk/fast-osx-arm64": "0.1.437",
47
+ "@livedesk/fast-osx-x64": "0.1.437",
48
+ "@livedesk/fast-win-x64": "0.1.437"
49
49
  },
50
50
  "publishConfig": {
51
51
  "access": "public"
@@ -1022,10 +1022,9 @@ function isLoopbackRequest(req) {
1022
1022
  return address === '127.0.0.1' || address === '::1' || address === 'localhost' || !address;
1023
1023
  }
1024
1024
 
1025
- function isTrustedApiOrigin(value, trustedWebOrigins) {
1026
- const rawOrigin = normalizeString(value, 300);
1027
- if (rawOrigin === 'null') return true;
1028
- const origin = normalizeOrigin(rawOrigin);
1025
+ function isTrustedApiOrigin(value, trustedWebOrigins) {
1026
+ const rawOrigin = normalizeString(value, 300);
1027
+ const origin = normalizeOrigin(rawOrigin);
1029
1028
  if (!origin) return false;
1030
1029
  if (trustedWebOrigins.has(origin)) return true;
1031
1030
  try {
@@ -1049,11 +1048,10 @@ function isTrustedLocalRequest(req, host, port, options = {}) {
1049
1048
  const requestHost = normalizeString(req.headers.host, 300).split(':')[0].toLowerCase();
1050
1049
  if (requestHost && requestHost !== host.toLowerCase() && requestHost !== 'localhost' && requestHost !== '127.0.0.1') return false;
1051
1050
 
1052
- // Chromium can mark modulepreload/stylesheet requests as cross-site, or
1053
- // send Origin: null when the page was opened from a local shell. Static
1054
- // assets are not privileged, and the local shell still needs to read the
1055
- // loopback runtime API to determine the active role. Mutating API routes
1056
- // remain protected by the CSRF token check below.
1051
+ // Chromium can mark modulepreload/stylesheet requests as cross-site. Static
1052
+ // assets are not privileged. An opaque Origin (`null`) is never accepted for
1053
+ // an API route because it cannot distinguish the LiveDesk shell from an
1054
+ // attacker-controlled file, sandbox, or data page.
1057
1055
  if (options.allowStaticCrossSite === true) {
1058
1056
  return true;
1059
1057
  }
@@ -1073,10 +1071,9 @@ function isTrustedLocalRequest(req, host, port, options = {}) {
1073
1071
  || fetchSite === 'none';
1074
1072
  }
1075
1073
 
1076
- function resolveCorsOrigin(requestOrigin, port, trustedWebOrigins) {
1077
- const origin = normalizeString(requestOrigin, 300);
1078
- if (origin === 'null') return 'null';
1079
- return origin && isTrustedApiOrigin(origin, trustedWebOrigins)
1074
+ function resolveCorsOrigin(requestOrigin, port, trustedWebOrigins) {
1075
+ const origin = normalizeString(requestOrigin, 300);
1076
+ return origin && isTrustedApiOrigin(origin, trustedWebOrigins)
1080
1077
  ? normalizeOrigin(origin)
1081
1078
  : `http://127.0.0.1:${port}`;
1082
1079
  }