livedesk 0.1.613 → 0.1.614

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/bin/livedesk.js CHANGED
@@ -5,8 +5,9 @@ import net from 'node:net';
5
5
  import { dirname, join, resolve } from 'node:path';
6
6
  import { fileURLToPath, pathToFileURL } from 'node:url';
7
7
  import { execFile, spawn } from 'node:child_process';
8
- import {
9
- createWriteStream,
8
+ import {
9
+ chmodSync,
10
+ createWriteStream,
10
11
  existsSync,
11
12
  mkdirSync,
12
13
  readFileSync,
@@ -597,12 +598,18 @@ function normalizePort(value, fallback) {
597
598
  return number;
598
599
  }
599
600
 
600
- function readStablePairToken() {
601
- try {
602
- const state = JSON.parse(readFileSync(MANAGER_STATE_PATH, 'utf8'));
603
- const pairToken = String(state?.pairToken || '').trim();
604
- if (pairToken.length >= 20) {
605
- return pairToken;
601
+ function readStablePairToken() {
602
+ try {
603
+ const state = JSON.parse(readFileSync(MANAGER_STATE_PATH, 'utf8'));
604
+ const pairToken = String(state?.pairToken || '').trim();
605
+ if (pairToken.length >= 20) {
606
+ try {
607
+ chmodSync(MANAGER_STATE_DIR, 0o700);
608
+ chmodSync(MANAGER_STATE_PATH, 0o600);
609
+ } catch {
610
+ // Windows and restricted filesystems may not expose POSIX modes.
611
+ }
612
+ return pairToken;
606
613
  }
607
614
  } catch {
608
615
  // A missing or unreadable state file is repaired below.
@@ -615,13 +622,19 @@ function getStablePairToken() {
615
622
  if (existing) {
616
623
  return existing;
617
624
  }
618
- const pairToken = randomBytes(18).toString('hex');
619
- try {
620
- mkdirSync(MANAGER_STATE_DIR, { recursive: true });
621
- writeFileSync(MANAGER_STATE_PATH, JSON.stringify({
622
- pairToken,
623
- createdAt: new Date().toISOString()
624
- }, null, 2));
625
+ const pairToken = randomBytes(32).toString('hex');
626
+ try {
627
+ mkdirSync(MANAGER_STATE_DIR, { recursive: true, mode: 0o700 });
628
+ writeFileSync(MANAGER_STATE_PATH, JSON.stringify({
629
+ pairToken,
630
+ createdAt: new Date().toISOString()
631
+ }, null, 2), { encoding: 'utf8', mode: 0o600 });
632
+ try {
633
+ chmodSync(MANAGER_STATE_DIR, 0o700);
634
+ chmodSync(MANAGER_STATE_PATH, 0o600);
635
+ } catch {
636
+ // Windows and restricted filesystems may not expose POSIX modes.
637
+ }
625
638
  } catch {
626
639
  // If disk persistence is blocked, keep this manager process usable.
627
640
  }
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import net from 'net';
4
- import os from 'os';
3
+ import os from 'os';
5
4
  import path from 'path';
6
5
  import crypto from 'crypto';
7
6
  import { existsSync, promises as fs, statfsSync } from 'fs';
8
7
  import { spawn } from 'child_process';
9
8
  import { createRequire } from 'node:module';
10
- import { fileURLToPath } from 'node:url';
11
- import { resolveAgentShellCommand } from '../src/runtime/agent-shell.js';
9
+ import { fileURLToPath } from 'node:url';
10
+ import { resolveAgentShellCommand } from '../src/runtime/agent-shell.js';
11
+ import { connectSecureDirectSocket } from '../../runtime-core/src/direct-secure-transport.js';
12
12
 
13
13
  const require = createRequire(import.meta.url);
14
14
  const CLIENT_UPDATE_BOOTSTRAP_PATH = fileURLToPath(
@@ -488,21 +488,7 @@ function parseManagerAddress(value) {
488
488
  };
489
489
  }
490
490
 
491
- function isPrivateLanHost(host) {
492
- const value = String(host || '').trim().toLowerCase().replace(/^\[|\]$/g, '');
493
- if (!value || value === 'localhost' || value === '::1') return true;
494
- if (net.isIPv4(value)) {
495
- const octets = value.split('.').map(Number);
496
- return octets[0] === 10
497
- || (octets[0] === 172 && octets[1] >= 16 && octets[1] <= 31)
498
- || (octets[0] === 192 && octets[1] === 168)
499
- || octets[0] === 127;
500
- }
501
- if (net.isIPv6(value)) return value.startsWith('fc') || value.startsWith('fd') || value.startsWith('fe80:');
502
- return false;
503
- }
504
-
505
- function normalizeDeviceId(value) {
491
+ function normalizeDeviceId(value) {
506
492
  return String(value || '').trim().replace(/[^a-zA-Z0-9_.:-]/g, '-').slice(0, 128);
507
493
  }
508
494
 
@@ -2005,18 +1991,24 @@ async function handleRemoteCommand(socket, options, message, nextFrameSeq, activ
2005
1991
  });
2006
1992
  }
2007
1993
 
2008
- function connectOnce(options, deviceId) {
2009
- const manager = parseManagerAddress(options.manager);
2010
- if (!isPrivateLanHost(manager.host) && !isTruthy(process.env.LIVEDESK_ALLOW_UNENCRYPTED_LAN)) {
2011
- return Promise.reject(new Error('Plain TCP VuvoDesk connections are limited to loopback/private LAN. Use an encrypted endpoint for external connections.'));
2012
- }
2013
- return new Promise((resolve, reject) => {
2014
- const socket = net.createConnection({
2015
- host: manager.host,
2016
- port: manager.port
2017
- });
2018
-
2019
- let buffer = '';
1994
+ async function connectOnce(options, deviceId) {
1995
+ const manager = parseManagerAddress(options.manager);
1996
+ let socket;
1997
+ try {
1998
+ socket = await connectSecureDirectSocket(
1999
+ manager,
2000
+ options.pair,
2001
+ 'control');
2002
+ } catch (error) {
2003
+ if ((error?.code === 'LIVEDESK_INVALID_PAIR_TOKEN'
2004
+ || error?.message === 'invalid-pair-token')
2005
+ && options.exitOnInvalidPair) {
2006
+ error.exitCode = EXIT_INVALID_PAIR_TOKEN;
2007
+ }
2008
+ throw error;
2009
+ }
2010
+ return new Promise((resolve, reject) => {
2011
+ let buffer = '';
2020
2012
  let heartbeatTimer = null;
2021
2013
  let resolved = false;
2022
2014
  let frameSeq = 0;
@@ -2050,47 +2042,7 @@ function connectOnce(options, deviceId) {
2050
2042
  socket.setNoDelay(true);
2051
2043
  socket.setKeepAlive(true, options.heartbeatMs);
2052
2044
 
2053
- socket.once('connect', () => {
2054
- writeJsonLine(socket, {
2055
- type: 'hello',
2056
- pairToken: options.pair,
2057
- deviceId,
2058
- deviceName: options.name,
2059
- slotNumber: options.slotNumber || undefined,
2060
- hostname: os.hostname(),
2061
- platform: os.platform(),
2062
- arch: os.arch(),
2063
- pid: process.pid,
2064
- agentVersion: AGENT_VERSION,
2065
- productVersion: PRODUCT_VERSION,
2066
- capabilities: {
2067
- status: true,
2068
- thumbnail: options.thumbnailEnabled,
2069
- liveStream: options.liveEnabled,
2070
- monitorSelection: true,
2071
- screenCount: 1,
2072
- monitorCount: 1,
2073
- control: false,
2074
- audio: false,
2075
- remoteAudio: false,
2076
- fileTransfer: true,
2077
- remoteFiles: true,
2078
- fileTransferMaxBytes: MAX_FILE_TRANSFER_BYTES,
2079
- computerAgent: options.taskEnabled,
2080
- taskDispatch: options.taskEnabled,
2081
- clientUpdate: true,
2082
- clientUpdateVerifiedProof: true,
2083
- productVersion: PRODUCT_VERSION,
2084
- agentApproval: options.taskEnabled,
2085
- agentAudit: options.taskEnabled,
2086
- agentTools: [...NODE_AGENT_OPERATIONS],
2087
- elevation: typeof process.getuid === 'function' ? process.getuid() === 0 : false,
2088
- externalEffects: options.taskEnabled
2089
- }
2090
- });
2091
- });
2092
-
2093
- socket.on('data', chunk => {
2045
+ socket.on('data', chunk => {
2094
2046
  buffer += chunk;
2095
2047
  let newlineIndex = buffer.indexOf('\n');
2096
2048
  while (newlineIndex >= 0) {
@@ -2157,10 +2109,47 @@ function connectOnce(options, deviceId) {
2157
2109
  }
2158
2110
  });
2159
2111
 
2160
- socket.once('error', err => finish(err));
2161
- socket.once('close', () => finish());
2162
- });
2163
- }
2112
+ socket.once('error', err => finish(err));
2113
+ socket.once('close', () => finish());
2114
+ writeJsonLine(socket, {
2115
+ type: 'hello',
2116
+ pairToken: options.pair,
2117
+ deviceId,
2118
+ deviceName: options.name,
2119
+ slotNumber: options.slotNumber || undefined,
2120
+ hostname: os.hostname(),
2121
+ platform: os.platform(),
2122
+ arch: os.arch(),
2123
+ pid: process.pid,
2124
+ agentVersion: AGENT_VERSION,
2125
+ productVersion: PRODUCT_VERSION,
2126
+ capabilities: {
2127
+ status: true,
2128
+ thumbnail: options.thumbnailEnabled,
2129
+ liveStream: options.liveEnabled,
2130
+ monitorSelection: true,
2131
+ screenCount: 1,
2132
+ monitorCount: 1,
2133
+ control: false,
2134
+ audio: false,
2135
+ remoteAudio: false,
2136
+ fileTransfer: true,
2137
+ remoteFiles: true,
2138
+ fileTransferMaxBytes: MAX_FILE_TRANSFER_BYTES,
2139
+ computerAgent: options.taskEnabled,
2140
+ taskDispatch: options.taskEnabled,
2141
+ clientUpdate: true,
2142
+ clientUpdateVerifiedProof: true,
2143
+ productVersion: PRODUCT_VERSION,
2144
+ agentApproval: options.taskEnabled,
2145
+ agentAudit: options.taskEnabled,
2146
+ agentTools: [...NODE_AGENT_OPERATIONS],
2147
+ elevation: typeof process.getuid === 'function' ? process.getuid() === 0 : false,
2148
+ externalEffects: options.taskEnabled
2149
+ }
2150
+ });
2151
+ });
2152
+ }
2164
2153
 
2165
2154
  async function connectWithRetry(options) {
2166
2155
  if (!options.pair) {
@@ -25,8 +25,9 @@ import {
25
25
  inspectLinuxVideoAcceleration,
26
26
  installLinuxVideoAcceleration
27
27
  } from '../src/runtime/linux-video-acceleration.js';
28
- import { normalizeRuntimeAuthSession } from '../../runtime-core/src/auth-session.js';
29
- import { startRoleTransitionSupervisor } from '../../runtime-core/src/role-transition-supervisor.js';
28
+ import { normalizeRuntimeAuthSession } from '../../runtime-core/src/auth-session.js';
29
+ import { startRoleTransitionSupervisor } from '../../runtime-core/src/role-transition-supervisor.js';
30
+ import { connectSecureDirectSocket } from '../../runtime-core/src/direct-secure-transport.js';
30
31
 
31
32
  const __dirname = dirname(fileURLToPath(import.meta.url));
32
33
  const require = createRequire(import.meta.url);
@@ -3674,7 +3675,7 @@ export function shouldSkipAutomaticDirectProbe(endpoint, options = {}) {
3674
3675
  && !isEndpointOnLocalNetwork(endpoint, options.networkInterfaces);
3675
3676
  }
3676
3677
 
3677
- function requestHubSlotAssignment({ manager, pairToken, deviceId, slotNumber, timeoutMs = 5000 }) {
3678
+ async function requestHubSlotAssignment({ manager, pairToken, deviceId, slotNumber, timeoutMs = 5000 }) {
3678
3679
  const endpoint = parseManagerEndpoint(manager);
3679
3680
  const normalizedPairToken = String(pairToken || '').trim();
3680
3681
  const normalizedDeviceId = String(deviceId || '').trim();
@@ -3692,9 +3693,19 @@ function requestHubSlotAssignment({ manager, pairToken, deviceId, slotNumber, ti
3692
3693
  return Promise.resolve({ ok: false, error: 'invalid-slot-number' });
3693
3694
  }
3694
3695
 
3695
- return new Promise(resolveAssignment => {
3696
- const socket = net.createConnection(endpoint);
3697
- let settled = false;
3696
+ let socket;
3697
+ try {
3698
+ socket = await connectSecureDirectSocket(
3699
+ endpoint,
3700
+ normalizedPairToken,
3701
+ 'control',
3702
+ { connectTimeoutMs: timeoutMs, handshakeTimeoutMs: timeoutMs });
3703
+ } catch (error) {
3704
+ return { ok: false, error: error?.message || 'hub-slot-secure-connection-failed' };
3705
+ }
3706
+
3707
+ return new Promise(resolveAssignment => {
3708
+ let settled = false;
3698
3709
  let buffer = '';
3699
3710
  const settle = result => {
3700
3711
  if (settled) return;
@@ -3705,15 +3716,7 @@ function requestHubSlotAssignment({ manager, pairToken, deviceId, slotNumber, ti
3705
3716
  };
3706
3717
  socket.setEncoding('utf8');
3707
3718
  socket.setTimeout(timeoutMs);
3708
- socket.once('connect', () => {
3709
- socket.write(`${JSON.stringify({
3710
- type: 'slot.assign',
3711
- pairToken: normalizedPairToken,
3712
- deviceId: normalizedDeviceId,
3713
- slotNumber: Number(normalizedSlot)
3714
- })}\n`);
3715
- });
3716
- socket.on('data', chunk => {
3719
+ socket.on('data', chunk => {
3717
3720
  buffer += chunk;
3718
3721
  const newlineIndex = buffer.indexOf('\n');
3719
3722
  if (newlineIndex < 0) return;
@@ -3730,11 +3733,17 @@ function requestHubSlotAssignment({ manager, pairToken, deviceId, slotNumber, ti
3730
3733
  });
3731
3734
  socket.once('timeout', () => settle({ ok: false, error: 'hub-slot-request-timeout' }));
3732
3735
  socket.once('error', error => settle({ ok: false, error: error?.message || 'hub-slot-request-failed' }));
3733
- socket.once('close', () => {
3734
- if (!settled) settle({ ok: false, error: 'hub-slot-response-missing' });
3735
- });
3736
- });
3737
- }
3736
+ socket.once('close', () => {
3737
+ if (!settled) settle({ ok: false, error: 'hub-slot-response-missing' });
3738
+ });
3739
+ socket.write(`${JSON.stringify({
3740
+ type: 'slot.assign',
3741
+ pairToken: normalizedPairToken,
3742
+ deviceId: normalizedDeviceId,
3743
+ slotNumber: Number(normalizedSlot)
3744
+ })}\n`);
3745
+ });
3746
+ }
3738
3747
 
3739
3748
  export function canConnectToEndpoint(endpoint, timeoutMs = ENDPOINT_PROBE_TIMEOUT_MS) {
3740
3749
  const parsedEndpoint = parseManagerEndpoint(endpoint);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livedesk/client",
3
- "version": "0.1.263",
3
+ "version": "0.1.264",
4
4
  "description": "VuvoDesk local remote client",
5
5
  "type": "module",
6
6
  "bin": {
@@ -36,16 +36,16 @@
36
36
  "dependencies": {
37
37
  "@ffmpeg-installer/ffmpeg": "^1.1.0",
38
38
  "ffmpeg-static": "^5.3.0",
39
- "@livedesk/runtime-core": "0.1.6",
39
+ "@livedesk/runtime-core": "0.1.7",
40
40
  "@supabase/supabase-js": "^2.110.0",
41
41
  "node-screenshots": "^0.2.8",
42
42
  "ws": "^8.18.3"
43
43
  },
44
44
  "optionalDependencies": {
45
- "@livedesk/fast-linux-x64": "0.1.459",
46
- "@livedesk/fast-osx-arm64": "0.1.459",
47
- "@livedesk/fast-osx-x64": "0.1.459",
48
- "@livedesk/fast-win-x64": "0.1.459"
45
+ "@livedesk/fast-linux-x64": "0.1.460",
46
+ "@livedesk/fast-osx-arm64": "0.1.460",
47
+ "@livedesk/fast-osx-x64": "0.1.460",
48
+ "@livedesk/fast-win-x64": "0.1.460"
49
49
  },
50
50
  "publishConfig": {
51
51
  "access": "public"
package/hub/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livedesk/hub",
3
- "version": "0.1.54",
3
+ "version": "0.1.55",
4
4
  "description": "VuvoDesk local Hub API and browser frame bridge",
5
5
  "type": "module",
6
6
  "main": "src/server.js",
@@ -16,7 +16,7 @@
16
16
  },
17
17
  "dependencies": {
18
18
  "@ffmpeg-installer/ffmpeg": "^1.1.0",
19
- "@livedesk/runtime-core": "0.1.6",
19
+ "@livedesk/runtime-core": "0.1.7",
20
20
  "@openai/codex-sdk": "0.145.0",
21
21
  "cors": "^2.8.5",
22
22
  "express": "^4.21.2",
@@ -1,7 +1,14 @@
1
- import net from 'net';
2
- import os from 'os';
3
- import crypto from 'crypto';
4
- import { createHubRelayControl } from './transport/relay-hub-control.js';
1
+ import net from 'net';
2
+ import os from 'os';
3
+ import crypto from 'crypto';
4
+ import {
5
+ acceptSecureDirectSocket,
6
+ isDirectSecureChannel,
7
+ isLoopbackSocketAddress,
8
+ isPrivateSocketAddress,
9
+ isSecureDirectHandshakeStart
10
+ } from '../../runtime-core/src/direct-secure-transport.js';
11
+ import { createHubRelayControl } from './transport/relay-hub-control.js';
5
12
  import { parseExactLiveStreamMonitorIndex } from './live-stream-monitor-contract.js';
6
13
  import {
7
14
  BoundedSegmentedBuffer,
@@ -2397,9 +2404,12 @@ export function createRemoteHub(options = {}) {
2397
2404
  const hostInstanceId = safeString(options.hostInstanceId ?? env.LIVEDESK_HUB_INSTANCE_ID ?? env.MINDEXEC_BRIDGE_INSTANCE_ID ?? crypto.randomUUID(), 128) || crypto.randomUUID();
2398
2405
  const publicEndpoint = safeString(env.LIVEDESK_REMOTE_PUBLIC_ENDPOINT || env.MINDEXEC_REMOTE_PUBLIC_ENDPOINT || env.REMOTE_HUB_PUBLIC_ENDPOINT, 256);
2399
2406
  const publicHost = safeString(env.LIVEDESK_REMOTE_PUBLIC_HOST || env.MINDEXEC_REMOTE_PUBLIC_HOST || env.REMOTE_HUB_PUBLIC_HOST, 128);
2400
- const pairToken = safeString(
2401
- options.pairToken || env.REMOTE_HUB_PAIR_TOKEN || env.LIVEDESK_CLIENT_PAIR_TOKEN || env.MINDEXEC_REMOTE_PAIR_TOKEN || crypto.randomBytes(6).toString('hex'),
2402
- 256);
2407
+ const pairToken = safeString(
2408
+ options.pairToken || env.REMOTE_HUB_PAIR_TOKEN || env.LIVEDESK_CLIENT_PAIR_TOKEN || env.MINDEXEC_REMOTE_PAIR_TOKEN || crypto.randomBytes(32).toString('hex'),
2409
+ 256);
2410
+ const allowInsecurePrivateDirect = isEnabledValue(
2411
+ options.allowInsecureDirect ?? env.LIVEDESK_ALLOW_INSECURE_DIRECT,
2412
+ false);
2403
2413
  const relayControl = options.relayControl && typeof options.relayControl === 'object'
2404
2414
  ? options.relayControl
2405
2415
  : createHubRelayControl({
@@ -7949,10 +7959,24 @@ export function createRemoteHub(options = {}) {
7949
7959
  function handleAgentMessage(socket, state, message) {
7950
7960
  if (!message || typeof message !== 'object') {
7951
7961
  return;
7952
- }
7953
-
7954
- if (!state.authenticated) {
7955
- if (message.type === 'hello' && socket.__liveDeskRelayControl === true) {
7962
+ }
7963
+
7964
+ if (!state.authenticated) {
7965
+ if (socket.__liveDeskSecureDirect === true) {
7966
+ const applicationChannel = message.type === 'slot.assign'
7967
+ ? 'control'
7968
+ : message.type === 'hello'
7969
+ ? (safeString(message.channel || message.Channel, 40).toLowerCase() || 'control')
7970
+ : '';
7971
+ if (applicationChannel
7972
+ && (!isDirectSecureChannel(applicationChannel)
7973
+ || applicationChannel !== socket.__liveDeskSecureChannel)) {
7974
+ writeJsonLine(socket, { type: 'error', error: 'direct-secure-channel-mismatch' });
7975
+ socket.end();
7976
+ return;
7977
+ }
7978
+ }
7979
+ if (message.type === 'hello' && socket.__liveDeskRelayControl === true) {
7956
7980
  message = {
7957
7981
  ...message,
7958
7982
  capabilities: {
@@ -7988,7 +8012,7 @@ export function createRemoteHub(options = {}) {
7988
8012
  return;
7989
8013
  }
7990
8014
 
7991
- const channel = safeString(message.channel || message.Channel, 40).toLowerCase();
8015
+ const channel = safeString(message.channel || message.Channel, 40).toLowerCase() || 'control';
7992
8016
  if (channel === 'input') {
7993
8017
  const device = attachInputSocket(socket, message);
7994
8018
  if (!device) {
@@ -8538,18 +8562,60 @@ export function createRemoteHub(options = {}) {
8538
8562
  if (firstChunk) {
8539
8563
  processData(firstChunk);
8540
8564
  }
8541
- }
8542
-
8543
- function handleSocket(socket) {
8544
- socket.once('data', chunk => {
8545
- const firstChunk = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
8546
- if (isRemoteHubWebSocketUpgradeStart(firstChunk)) {
8547
- handleWebSocketUpgradeSocket(socket, firstChunk);
8548
- return;
8549
- }
8550
-
8551
- handleTcpAgentSocket(socket, firstChunk);
8552
- });
8565
+ }
8566
+
8567
+ function handleSocket(socket) {
8568
+ allSockets.add(socket);
8569
+ const prefaceTimer = setTimeout(() => {
8570
+ if (!socket.destroyed) socket.destroy();
8571
+ }, 10_000);
8572
+ prefaceTimer.unref?.();
8573
+ socket.once('close', () => {
8574
+ clearTimeout(prefaceTimer);
8575
+ allSockets.delete(socket);
8576
+ });
8577
+ socket.once('data', chunk => {
8578
+ clearTimeout(prefaceTimer);
8579
+ const firstChunk = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
8580
+ if (isSecureDirectHandshakeStart(firstChunk)) {
8581
+ void acceptSecureDirectSocket(socket, firstChunk, pairToken)
8582
+ .then(secureSocket => {
8583
+ allSockets.delete(socket);
8584
+ handleTcpAgentSocket(secureSocket);
8585
+ })
8586
+ .catch(error => {
8587
+ if (socket.destroyed) return;
8588
+ if (error?.code === 'LIVEDESK_INVALID_PAIR_TOKEN_REJECTED') {
8589
+ const forcedCloseTimer = setTimeout(() => socket.destroy(), 1_000);
8590
+ forcedCloseTimer.unref?.();
8591
+ socket.once('close', () => clearTimeout(forcedCloseTimer));
8592
+ if (typeof socket.destroySoon === 'function') socket.destroySoon();
8593
+ else socket.end();
8594
+ return;
8595
+ }
8596
+ socket.destroy();
8597
+ });
8598
+ return;
8599
+ }
8600
+
8601
+ const remoteAddress = socket.remoteAddress;
8602
+ const allowLegacyPlaintext = isLoopbackSocketAddress(remoteAddress)
8603
+ || (allowInsecurePrivateDirect && isPrivateSocketAddress(remoteAddress));
8604
+ if (isRemoteHubWebSocketUpgradeStart(firstChunk)) {
8605
+ if (!allowLegacyPlaintext) {
8606
+ socket.destroy();
8607
+ return;
8608
+ }
8609
+ handleWebSocketUpgradeSocket(socket, firstChunk);
8610
+ return;
8611
+ }
8612
+
8613
+ if (!allowLegacyPlaintext) {
8614
+ socket.destroy();
8615
+ return;
8616
+ }
8617
+ handleTcpAgentSocket(socket, firstChunk);
8618
+ });
8553
8619
 
8554
8620
  socket.once('error', () => {
8555
8621
  // The transport-specific handler owns logging after the first byte.
@@ -8571,10 +8637,10 @@ export function createRemoteHub(options = {}) {
8571
8637
  started = true;
8572
8638
  boundPort = candidateServer.address()?.port || port;
8573
8639
  lastError = '';
8574
- logEvent('remote', `VuvoDesk Hub client endpoint listening on tcp://${host}:${boundPort}`, 'success');
8575
- if (host === '0.0.0.0' || host === '::') {
8576
- logWarn('remote', 'VuvoDesk Hub client endpoint is externally reachable. Use a strong pairing token and trusted network.');
8577
- }
8640
+ logEvent('remote', `VuvoDesk Hub client endpoint listening on tcp://${host}:${boundPort}`, 'success');
8641
+ if (host === '0.0.0.0' || host === '::') {
8642
+ logWarn('remote', 'VuvoDesk Hub client endpoint is externally reachable. Non-loopback Direct connections require authenticated encryption.');
8643
+ }
8578
8644
  emitRemoteEvent('RemoteHubStarted', null);
8579
8645
  resolve();
8580
8646
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "livedesk",
3
- "version": "0.1.613",
4
- "livedeskClientVersion": "0.1.263",
3
+ "version": "0.1.614",
4
+ "livedeskClientVersion": "0.1.264",
5
5
  "buildFlavor": "production",
6
6
  "description": "VuvoDesk Hub and client launcher",
7
7
  "type": "module",
@@ -51,10 +51,10 @@
51
51
  "ws": "^8.18.3"
52
52
  },
53
53
  "optionalDependencies": {
54
- "@livedesk/fast-linux-x64": "0.1.459",
55
- "@livedesk/fast-osx-arm64": "0.1.459",
56
- "@livedesk/fast-osx-x64": "0.1.459",
57
- "@livedesk/fast-win-x64": "0.1.459"
54
+ "@livedesk/fast-linux-x64": "0.1.460",
55
+ "@livedesk/fast-osx-arm64": "0.1.460",
56
+ "@livedesk/fast-osx-x64": "0.1.460",
57
+ "@livedesk/fast-win-x64": "0.1.460"
58
58
  },
59
59
  "publishConfig": {
60
60
  "access": "public"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livedesk/runtime-core",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "Shared VuvoDesk runtime role and lifecycle contracts",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -8,7 +8,7 @@
8
8
  "src/"
9
9
  ],
10
10
  "scripts": {
11
- "check": "node --check src/index.js",
11
+ "check": "node --check src/index.js && node --check src/direct-secure-transport.js",
12
12
  "prepublishOnly": "node ../../scripts/livedesk-release-git-gate.mjs"
13
13
  },
14
14
  "engines": {