@zswarm/core 0.2.0 → 0.2.1

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.
@@ -66,14 +66,15 @@ export declare const defaultRunner: Runner;
66
66
  /** The account services are installed for: the current user, or `runAs` when root. */
67
67
  export declare function serviceAccount(runAs?: string, run?: Runner): ServiceAccount;
68
68
  /**
69
- * Run fs work in another account's home as that account. Root installing
70
- * with --run-as drops its effective uid and gid for the duration, so the
69
+ * The argv prefix that runs a command as the account: `runuser` on Linux,
70
+ * `sudo -u` on macOS. Root writes into the account's home through it, so the
71
71
  * kernel refuses what the account itself could not do (a symlink it planted
72
- * towards a root-owned file, say) and new files are the account's own. The
73
- * work must be synchronous: the effective ids are process-wide.
72
+ * towards a root-owned file, say) and new files are the account's own. (Not
73
+ * seteuid in this process: Node disables it on Linux while io_uring may be
74
+ * enabled, CVE-2024-22017.)
74
75
  */
75
- export declare function asAccount<T>(account: ServiceAccount, fn: () => T): T;
76
- /** Write a file in the account's home as the account (see asAccount). */
76
+ export declare function accountArgv(account: ServiceAccount): string[];
77
+ /** Write a file in the account's home as the account (see accountArgv). */
77
78
  export declare function writeAccountFile(account: ServiceAccount, path: string, text: string | Buffer, mode: number): void;
78
79
  export declare function systemdUnitName(name: string, kind: ServiceKind): string;
79
80
  export declare function systemdUnits(spec: ServiceSpec): Array<{
@@ -38,33 +38,38 @@ export function serviceAccount(runAs, run = defaultRunner) {
38
38
  throw new ZellijError("usage", `cannot find the home of ${target}`);
39
39
  return { platform, user: target, uid: id("-u"), gid: id("-g"), home, foreign: true };
40
40
  }
41
+ /** Root installing for another account (`--run-as`). */
42
+ function installingFor(account) {
43
+ return account.foreign && typeof process.geteuid === "function" && process.geteuid() === 0;
44
+ }
41
45
  /**
42
- * Run fs work in another account's home as that account. Root installing
43
- * with --run-as drops its effective uid and gid for the duration, so the
46
+ * The argv prefix that runs a command as the account: `runuser` on Linux,
47
+ * `sudo -u` on macOS. Root writes into the account's home through it, so the
44
48
  * kernel refuses what the account itself could not do (a symlink it planted
45
- * towards a root-owned file, say) and new files are the account's own. The
46
- * work must be synchronous: the effective ids are process-wide.
49
+ * towards a root-owned file, say) and new files are the account's own. (Not
50
+ * seteuid in this process: Node disables it on Linux while io_uring may be
51
+ * enabled, CVE-2024-22017.)
47
52
  */
48
- export function asAccount(account, fn) {
49
- const root = account.foreign && typeof process.geteuid === "function" && process.geteuid() === 0;
50
- if (!root)
51
- return fn();
52
- process.setegid(account.gid);
53
- process.seteuid(account.uid);
54
- try {
55
- return fn();
56
- }
57
- finally {
58
- process.seteuid(0);
59
- process.setegid(0);
53
+ export function accountArgv(account) {
54
+ return account.platform === "linux"
55
+ ? ["runuser", "-u", account.user, "--"]
56
+ : ["sudo", "-n", "-u", account.user, "-H", "--"];
57
+ }
58
+ function runAsAccount(account, argv, input) {
59
+ const [cmd, ...rest] = [...accountArgv(account), ...argv];
60
+ const res = spawnSync(cmd, rest, { input, encoding: "utf8" });
61
+ if (res.status !== 0) {
62
+ throw new ZellijError("failed", `${argv[0]} as ${account.user} failed: ${(res.stderr || res.error?.message || "").trim().slice(0, 300)}`);
60
63
  }
61
64
  }
62
- /** Write a file in the account's home as the account (see asAccount). */
65
+ /** Write a file in the account's home as the account (see accountArgv). */
63
66
  export function writeAccountFile(account, path, text, mode) {
64
- asAccount(account, () => {
67
+ if (!installingFor(account)) {
65
68
  writeFileSync(path, text, { mode });
66
69
  chmodSync(path, mode);
67
- });
70
+ return;
71
+ }
72
+ runAsAccount(account, ["sh", "-c", 'umask 077 && cat > "$1" && chmod "$2" "$1"', "sh", path, mode.toString(8)], text);
68
73
  }
69
74
  function realFs(account) {
70
75
  return {
@@ -319,7 +324,10 @@ export function ensureAccountDir(account, dir, mode = 0o700) {
319
324
  for (let d = dir; d && d !== "/" && !existsSync(d); d = d.slice(0, d.lastIndexOf("/")) || "/")
320
325
  missing.unshift(d);
321
326
  for (const d of missing) {
322
- asAccount(account, () => mkdirSync(d, { mode }));
327
+ if (installingFor(account))
328
+ runAsAccount(account, ["mkdir", "-m", mode.toString(8), d]);
329
+ else
330
+ mkdirSync(d, { mode });
323
331
  }
324
332
  }
325
333
  /** Write a mode-600 file for the account, creating its directory (700) as needed. */
package/dist/index.d.ts CHANGED
@@ -32,7 +32,7 @@ export { cliUsage, mcpInputSchema, parseCliArgv, MCP_TOOL_DESCRIPTION, OP_NAMES,
32
32
  export { HOST_COMMANDS, hostUsage, parseHostArgs, runHostCommand } from "./host/cli.js";
33
33
  export { findPython, runSlot, slotScriptPath } from "./host/slot.js";
34
34
  export { installServeService, serveServiceName, serveServiceSpec, serveTokenFromEnv, serveTokenPath, uninstallServeService, SERVE_AWAIT_SESSION_ENV, SERVE_TOKEN_FILE_ENV, type ServeServiceInput, } from "./host/serve-service.js";
35
- export { asAccount, installServices, launchdLabel, launchdPlist, removeServices, serviceAccount, serviceStates, systemdUnitName, systemdUnits, writeAccountFile, type Runner, type ServiceAccount, type ServiceHost, type ServiceKind, type ServiceSpec, type ServiceState, } from "./host/services.js";
35
+ export { accountArgv, installServices, launchdLabel, launchdPlist, removeServices, serviceAccount, serviceStates, systemdUnitName, systemdUnits, writeAccountFile, type Runner, type ServiceAccount, type ServiceHost, type ServiceKind, type ServiceSpec, type ServiceState, } from "./host/services.js";
36
36
  export { crewCheck, crewDir, crewDown, crewServiceSpecs, crewStart, crewStatus, crewUp, firstLayoutPane, parseEnvFile, CREW_UNIT_ENV, CREW_WATCH_SEC, type CrewDeps, type CrewUpInput, } from "./host/crew.js";
37
37
  export { deliverMessage, doneLines, fillMessage, freshDoneLine, launchRelay, prepareRelay, pruneRelays, relayLoop, relaysDir, runRelayDir, scrubRelayToken, startRelay, waitForRelay, type RelayConfig, type RelayDeps, type RelayStartInput, type RelayStatus, } from "./host/relay.js";
38
38
  export { hostChecks, hostDoctor, hostInstall, hostInstallArgs, hostScriptPath, parseHostReport, type HostCheck, type HostDoctorInput, type HostInstallInput, type SshRunner, } from "./host/host.js";
package/dist/index.js CHANGED
@@ -30,7 +30,7 @@ export { cliUsage, mcpInputSchema, parseCliArgv, MCP_TOOL_DESCRIPTION, OP_NAMES,
30
30
  export { HOST_COMMANDS, hostUsage, parseHostArgs, runHostCommand } from "./host/cli.js";
31
31
  export { findPython, runSlot, slotScriptPath } from "./host/slot.js";
32
32
  export { installServeService, serveServiceName, serveServiceSpec, serveTokenFromEnv, serveTokenPath, uninstallServeService, SERVE_AWAIT_SESSION_ENV, SERVE_TOKEN_FILE_ENV, } from "./host/serve-service.js";
33
- export { asAccount, installServices, launchdLabel, launchdPlist, removeServices, serviceAccount, serviceStates, systemdUnitName, systemdUnits, writeAccountFile, } from "./host/services.js";
33
+ export { accountArgv, installServices, launchdLabel, launchdPlist, removeServices, serviceAccount, serviceStates, systemdUnitName, systemdUnits, writeAccountFile, } from "./host/services.js";
34
34
  export { crewCheck, crewDir, crewDown, crewServiceSpecs, crewStart, crewStatus, crewUp, firstLayoutPane, parseEnvFile, CREW_UNIT_ENV, CREW_WATCH_SEC, } from "./host/crew.js";
35
35
  export { deliverMessage, doneLines, fillMessage, freshDoneLine, launchRelay, prepareRelay, pruneRelays, relayLoop, relaysDir, runRelayDir, scrubRelayToken, startRelay, waitForRelay, } from "./host/relay.js";
36
36
  export { hostChecks, hostDoctor, hostInstall, hostInstallArgs, hostScriptPath, parseHostReport, } from "./host/host.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zswarm/core",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "description": "zSwarm Zellij client and shared ops dispatch",
6
6
  "exports": {