@livedesk/client 0.1.130 → 0.1.132

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.
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  import net from 'net';
4
4
  import os from 'os';
@@ -197,7 +197,7 @@ function parseArgs(argv) {
197
197
  return result;
198
198
  }
199
199
 
200
- function parseManagerAddress(value) {
200
+ function parseManagerAddress(value) {
201
201
  let text = String(value || DEFAULT_MANAGER).trim();
202
202
  text = text.replace(/^tcp:\/\//i, '');
203
203
  const separator = text.lastIndexOf(':');
@@ -210,11 +210,25 @@ function parseManagerAddress(value) {
210
210
 
211
211
  const host = text.slice(0, separator).trim() || '127.0.0.1';
212
212
  const port = Number(text.slice(separator + 1));
213
- return {
214
- host,
215
- port: Number.isFinite(port) ? port : 5197
216
- };
217
- }
213
+ return {
214
+ host,
215
+ port: Number.isFinite(port) ? port : 5197
216
+ };
217
+ }
218
+
219
+ function isPrivateLanHost(host) {
220
+ const value = String(host || '').trim().toLowerCase().replace(/^\[|\]$/g, '');
221
+ if (!value || value === 'localhost' || value === '::1') return true;
222
+ if (net.isIPv4(value)) {
223
+ const octets = value.split('.').map(Number);
224
+ return octets[0] === 10
225
+ || (octets[0] === 172 && octets[1] >= 16 && octets[1] <= 31)
226
+ || (octets[0] === 192 && octets[1] === 168)
227
+ || octets[0] === 127;
228
+ }
229
+ if (net.isIPv6(value)) return value.startsWith('fc') || value.startsWith('fd') || value.startsWith('fe80:');
230
+ return false;
231
+ }
218
232
 
219
233
  function normalizeDeviceId(value) {
220
234
  return String(value || '').trim().replace(/[^a-zA-Z0-9_.:-]/g, '-').slice(0, 128);
@@ -1267,10 +1281,27 @@ function normalizeNodeAgentTaskResult(result) {
1267
1281
  }
1268
1282
 
1269
1283
  const NODE_AGENT_OPERATIONS = new Set(['process.control', 'service.control', 'application.launch', 'application.close', 'file.read', 'file.write', 'file.delete', 'file.list', 'command.run', 'script.run', 'software.install', 'network.status', 'system.power', 'system.configure', 'logs.collect']);
1270
-
1271
- async function handleRemoteCommand(socket, options, message, nextFrameSeq, activeStreams) {
1284
+
1285
+ function remotePolicyAllows(options, command) {
1286
+ const policy = options.effectivePolicy;
1287
+ if (!policy || typeof policy !== 'object') return { ok: true };
1288
+ if (policy.accessMode === 'block-remote-access') return { ok: false, error: 'remote-access-blocked-by-settings' };
1289
+ const required = command === 'input.control'
1290
+ ? ['allowControl']
1291
+ : command.startsWith('file.transfer')
1292
+ ? ['allowFileTransfer']
1293
+ : command === 'audio.start'
1294
+ ? ['allowRemoteAudio']
1295
+ : command === 'agent.task' || NODE_AGENT_OPERATIONS.has(command)
1296
+ ? ['allowAgent']
1297
+ : [];
1298
+ const denied = required.find(key => policy[key] === false);
1299
+ return denied ? { ok: false, error: `${denied}-blocked-by-settings` } : { ok: true };
1300
+ }
1301
+
1302
+ async function handleRemoteCommand(socket, options, message, nextFrameSeq, activeStreams) {
1272
1303
  const command = String(message.command || '');
1273
- if (command === 'ping') {
1304
+ if (command === 'ping') {
1274
1305
  writeJsonLine(socket, {
1275
1306
  type: 'command.result',
1276
1307
  commandId: message.commandId,
@@ -1279,8 +1310,19 @@ async function handleRemoteCommand(socket, options, message, nextFrameSeq, activ
1279
1310
  at: new Date().toISOString()
1280
1311
  }
1281
1312
  });
1282
- return;
1283
- }
1313
+ return;
1314
+ }
1315
+
1316
+ const policyDecision = remotePolicyAllows(options, command);
1317
+ if (!policyDecision.ok) {
1318
+ writeJsonLine(socket, {
1319
+ type: 'command.result',
1320
+ commandId: message.commandId,
1321
+ error: policyDecision.error,
1322
+ result: { ok: false, status: 'failed', error: policyDecision.error }
1323
+ });
1324
+ return;
1325
+ }
1284
1326
 
1285
1327
  if (NODE_AGENT_OPERATIONS.has(command)) {
1286
1328
  if (!options.taskEnabled) {
@@ -1544,9 +1586,12 @@ async function handleRemoteCommand(socket, options, message, nextFrameSeq, activ
1544
1586
  });
1545
1587
  }
1546
1588
 
1547
- function connectOnce(options, deviceId) {
1548
- const manager = parseManagerAddress(options.manager);
1549
- return new Promise((resolve, reject) => {
1589
+ function connectOnce(options, deviceId) {
1590
+ const manager = parseManagerAddress(options.manager);
1591
+ if (!isPrivateLanHost(manager.host) && !isTruthy(process.env.LIVEDESK_ALLOW_UNENCRYPTED_LAN)) {
1592
+ return Promise.reject(new Error('Plain TCP LiveDesk connections are limited to loopback/private LAN. Use an encrypted endpoint for external connections.'));
1593
+ }
1594
+ return new Promise((resolve, reject) => {
1550
1595
  const socket = net.createConnection({
1551
1596
  host: manager.host,
1552
1597
  port: manager.port
@@ -1636,9 +1681,12 @@ function connectOnce(options, deviceId) {
1636
1681
  continue;
1637
1682
  }
1638
1683
 
1639
- const message = JSON.parse(line);
1640
- if (message.type === 'welcome') {
1641
- console.log(`Connected to LiveDesk Hub as ${options.name} (${deviceId})`);
1684
+ const message = JSON.parse(line);
1685
+ if (message.type === 'welcome') {
1686
+ options.effectivePolicy = message.effectivePolicy && typeof message.effectivePolicy === 'object'
1687
+ ? message.effectivePolicy
1688
+ : null;
1689
+ console.log(`Connected to LiveDesk Hub as ${options.name} (${deviceId})`);
1642
1690
  writeJsonLine(socket, {
1643
1691
  type: 'status',
1644
1692
  status: getStatus(options)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livedesk/client",
3
- "version": "0.1.130",
3
+ "version": "0.1.132",
4
4
  "description": "LiveDesk local remote client",
5
5
  "type": "module",
6
6
  "bin": {