@remcp/remcp 0.2.206 → 0.2.208

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/README.md CHANGED
@@ -63,7 +63,20 @@ Two things it deliberately does not do:
63
63
  a non-interactive command cannot type one. Nothing in this mode grants root by itself.
64
64
 
65
65
  While it is on, `get_runtime_info` reports `policy.unrestricted: true`, so the model can see it and
66
- say so instead of assuming the guardrails are still there.
66
+ say so instead of assuming the guardrails are still there. The same response includes
67
+ `osPrivilegeBoundary`, reporting effective user and `NoNewPrivs` on Linux, the user boundary on
68
+ macOS, and the actual Windows process integrity level detected via `whoami.exe /groups`.
69
+ If that command is unavailable, Windows elevation remains `unknown`; process integrity
70
+ is not equivalent to blanket administrator access.
71
+
72
+ The owner-managed Linux `remcp-agent.service` does not set `NoNewPrivileges=true`: sudo/polkit
73
+ may request normal OS approval instead of being blocked by systemd. `remcp update` also removes
74
+ this legacy line from older user units. A systemd `daemon-reload` does not change an already
75
+ running process; activating the new privilege boundary requires an agent restart, which can stop
76
+ running terminal sessions and child processes. This does **not** enable passwordless root.
77
+ On macOS, launchd runs as the signed-in user, and TCC controls Full Disk Access and Accessibility.
78
+ On Windows, the interactive scheduled task requests the highest privileges available to its
79
+ principal; UAC and the actual process token remain authoritative.
67
80
 
68
81
  ## macOS folder permissions
69
82
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remcp/remcp",
3
- "version": "0.2.206",
3
+ "version": "0.2.208",
4
4
  "description": "ReMCP device client: pair a computer with ReMCP and run the outbound-only agent that hosts the local MCP runtime.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -3,12 +3,35 @@
3
3
  // after verifying its installed manifest and on-disk path. No sudo or shell.
4
4
  import fs from 'node:fs';
5
5
  import path from 'node:path';
6
+ import process from 'node:process';
7
+ import { spawn } from 'node:child_process';
6
8
 
7
9
  const SENSOR_NAME = 'macos-temperature-sensor';
8
10
  const SENSOR_VERSION = '2.1.1';
9
11
  const SCRIPT = 'node-gyp rebuild';
10
12
  const BINARY = path.join('build', 'Release', 'mac_temp_native.node');
11
13
 
14
+ // Repair a module omitted by an old updater after starting the new agent.
15
+ export function scheduleMacTemperatureRepair({
16
+ platform = process.platform, runtimePackageName, nodeExecutable = process.execPath,
17
+ cliEntry = process.argv[1], environment = process.env,
18
+ spawnProcess = spawn, warn = console.error,
19
+ } = {}) {
20
+ if (platform !== 'darwin' || runtimePackageName !== '@remcp/runtime') return false;
21
+ if (!path.isAbsolute(nodeExecutable) || !path.isAbsolute(cliEntry || '')) return false;
22
+ try {
23
+ const child = spawnProcess(nodeExecutable, [cliEntry, '__repair-macos-temperature'], {
24
+ detached: true, stdio: ['ignore', 'inherit', 'inherit'], env: environment,
25
+ });
26
+ child.on?.('error', () => warn('ReMCP: optional macOS temperature repair could not start.'));
27
+ child.unref?.();
28
+ return true;
29
+ } catch {
30
+ warn('ReMCP: optional macOS temperature repair could not start.');
31
+ return false;
32
+ }
33
+ }
34
+
12
35
  function checkedManifest(file) {
13
36
  const stats = fs.lstatSync(file);
14
37
  if (!stats.isFile() || stats.size > 16384) throw new Error('invalid manifest');
@@ -67,6 +67,23 @@ export function npmGlobalInstall(...specs) {
67
67
  }
68
68
  }
69
69
 
70
+ // Called only by the post-start macOS helper: an older updater may have
71
+ // installed this version while leaving the optional native binary missing.
72
+ // The same version-pinned, path-checked installer is reused; no npm install
73
+ // or arbitrary dependency lifecycle scripts are permitted.
74
+ export function repairMacTemperatureAfterStart(runtimePackageName) {
75
+ if (servicePlatform() !== 'darwin' || runtimePackageName !== '@remcp/runtime') return false;
76
+ try {
77
+ return buildVettedMacTemperatureAddon({
78
+ runtimeRoot:globalPackagePath(assertSafeReMcpNpmPrefix(globalPrefix()), '@remcp/runtime'),
79
+ resolved:npm, runCommand:run,
80
+ });
81
+ } catch {
82
+ console.error('ReMCP: optional macOS temperature sensor unavailable; other tools remain functional.');
83
+ return false;
84
+ }
85
+ }
86
+
70
87
  function pathEntryExists(file) {
71
88
  try {
72
89
  fs.lstatSync(file);
@@ -62,7 +62,7 @@ function writeLinuxServiceLauncher(cliPath = globalCliPath(), nodePath = process
62
62
 
63
63
  export function installLinuxService(cliPath = globalCliPath(), nodePath = process.execPath) {
64
64
  const launcherFile = writeLinuxServiceLauncher(cliPath, nodePath);
65
- const unit = `[Unit]\nDescription=ReMCP device agent\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nType=simple\nExecStart=${quoteSystemd(launcherFile)} start --service\nRestart=always\nRestartSec=3\nNoNewPrivileges=true\n\n[Install]\nWantedBy=default.target\n`;
65
+ const unit = `[Unit]\nDescription=ReMCP device agent\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nType=simple\nExecStart=${quoteSystemd(launcherFile)} start --service\nRestart=always\nRestartSec=3\n\n[Install]\nWantedBy=default.target\n`;
66
66
  fs.mkdirSync(path.dirname(linuxServiceFile), { recursive: true });
67
67
  fs.writeFileSync(linuxServiceFile, unit);
68
68
  run('systemctl', ['--user', 'daemon-reload']);
@@ -279,23 +279,29 @@ export function ensureServiceIfRecorded(config) {
279
279
  const launcherFile = writeLinuxServiceLauncher(cliPath, nodePath);
280
280
  const unit = fs.readFileSync(fd, 'utf8');
281
281
  const expected = `ExecStart=${quoteSystemd(launcherFile)} start --service`;
282
- if (!unit.includes(expected)) {
283
- const repaired = /^ExecStart=/m.test(unit) ? unit.replace(/^ExecStart=.*$/m, expected) : '';
284
- if (!repaired) {
285
- reinstall = true;
286
- } else {
287
- const data = Buffer.from(repaired, 'utf8');
288
- fs.ftruncateSync(fd, 0);
289
- let offset = 0;
290
- while (offset < data.length) {
291
- const written = fs.writeSync(fd, data, offset, data.length - offset, offset);
292
- if (!written) throw new Error('Could not finish rewriting the systemd unit');
293
- offset += written;
294
- }
295
- fs.ftruncateSync(fd, data.length);
296
- fs.fsyncSync(fd);
297
- run('systemctl', ['--user', 'daemon-reload']);
282
+ // Legacy versions added NoNewPrivileges=true to the user agent unit.
283
+ // Drop that line when repairing so ordinary OS-authenticated sudo/polkit
284
+ // can work for the owner's session. ReMCP never becomes root by itself.
285
+ // Do not modify unrelated systemd services or owner-managed drop-ins.
286
+ let repaired = unit.replace(/^NoNewPrivileges=true\r?\n/gm, '');
287
+ if (!repaired.includes(expected)) {
288
+ repaired = /^ExecStart=/m.test(repaired)
289
+ ? repaired.replace(/^ExecStart=.*$/m, expected) : '';
290
+ }
291
+ if (!repaired) {
292
+ reinstall = true;
293
+ } else if (repaired !== unit) {
294
+ const data = Buffer.from(repaired, 'utf8');
295
+ fs.ftruncateSync(fd, 0);
296
+ let offset = 0;
297
+ while (offset < data.length) {
298
+ const written = fs.writeSync(fd, data, offset, data.length - offset, offset);
299
+ if (!written) throw new Error('Could not finish rewriting the systemd unit');
300
+ offset += written;
298
301
  }
302
+ fs.ftruncateSync(fd, data.length);
303
+ fs.fsyncSync(fd);
304
+ run('systemctl', ['--user', 'daemon-reload']);
299
305
  }
300
306
  } finally {
301
307
  fs.closeSync(fd);
package/src/cli.mjs CHANGED
@@ -12,6 +12,8 @@ import { assertRuntimeTrust, assertSecureServerUrl, pairWithDeviceCode } from '.
12
12
  import { diagnoseLocalRuntime, installedVersion, runtimeAllowedRoots } from './cli/doctor.mjs';
13
13
  import { configFile, npm, officialOrigin, runtimeConfigFile } from './cli/env.mjs';
14
14
  import { currentInstallationInfo, ensureMacCliCommand, installPersistentAgent, persistentServiceState, servicePlatform, rememberCurrentInstallation, restartPersistentServiceIfInstalled, serviceInstallationInfo, uninstallPersistentService } from './cli/service.mjs';
15
+ import { scheduleMacTemperatureRepair } from './cli/mac-native-temperature.mjs';
16
+ import { repairMacTemperatureAfterStart } from './cli/npm-global.mjs';
15
17
  import { run } from './cli/shell.mjs';
16
18
  import { updateCommand } from './cli/update.mjs';
17
19
  function fetchConfiguredServer(url, options) {
@@ -156,6 +158,9 @@ export async function main(argv = process.argv.slice(2)) {
156
158
  console.log(`ReMCP is already running as a background service (${service.name}). Use \`remcp status\` to inspect it or \`remcp doctor\` to diagnose it.`);
157
159
  return;
158
160
  }
161
+ // Older updaters can leave the optional native binary absent. Repair via
162
+ // a separate, fixed-purpose process without delaying the relay connection.
163
+ scheduleMacTemperatureRepair({ runtimePackageName: cfg.runtime?.packageName });
159
164
  const telemetry = telemetryState();
160
165
  const agent = await runAgent({
161
166
  ...cfg,
@@ -179,6 +184,16 @@ export async function main(argv = process.argv.slice(2)) {
179
184
  return;
180
185
  }
181
186
 
187
+ if (command === '__repair-macos-temperature') {
188
+ // Only the pinned first-party sensor is eligible; no external script input.
189
+ if (servicePlatform() !== 'darwin') return;
190
+ const cfg = loadConfig(false);
191
+ if (repairMacTemperatureAfterStart(cfg?.runtime?.packageName)) {
192
+ console.log('ReMCP: vetted macOS temperature sensor is ready.');
193
+ }
194
+ return;
195
+ }
196
+
182
197
  if (command === 'auto-update') {
183
198
  const action = String(positional[0] || 'status').toLowerCase();
184
199
  const cfg = loadConfig(false) || {};