@sellable/install
Version:
One-command installer for Sellable MCP in Claude Code, Codex, and Hermes
89 lines (81 loc) • 2.98 kB
JavaScript
import { spawn } from "node:child_process";
import { closeSync, constants, fstatSync, openSync, unlinkSync } from "node:fs";
import { buildRuntimeIdentityLaunchArgs } from "../lib/sellable-agent/runtime-boundary.mjs";
const APP_PATH = "/run/bootstrap/slack-app";
const BOT_PATH = "/run/bootstrap/slack-bot";
const SAFE_ID = /^[A-Za-z0-9_-]{1,128}$/;
function required(name) {
const value = process.env[name];
if (typeof value !== "string" || !value) throw new Error(`missing ${name}`);
return value;
}
function identity(name) {
const value = required(name);
if (!/^[1-9][0-9]{0,9}$/.test(value)) throw new Error("runtime identity rejected");
return value;
}
function inheritedSecret(path) {
const fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);
const info = fstatSync(fd);
if (!info.isFile() || info.uid !== 0 || info.gid !== 0 || (info.mode & 0o777) !== 0o600 || info.size < 6 || info.size > 4096) {
closeSync(fd);
throw new Error("runtime Slack descriptor source rejected");
}
unlinkSync(path);
return fd;
}
async function run() {
if (process.argv.length !== 2 || process.getuid?.() !== 0 || process.getgid?.() !== 0) {
throw new Error("sandbox init identity rejected");
}
const uid = identity("SELLABLE_AGENT_RUNTIME_UID");
const gid = identity("SELLABLE_AGENT_RUNTIME_GID");
const profileId = required("SELLABLE_AGENT_PROFILE_ID");
if (!SAFE_ID.test(profileId)) throw new Error("sandbox profile rejected");
const hermes = required("SELLABLE_AGENT_HERMES_EXECUTABLE");
const setpriv = required("SELLABLE_AGENT_SETPRIV_EXECUTABLE");
if (!hermes.startsWith("/") || !setpriv.startsWith("/") || setpriv !== "/usr/bin/setpriv") {
throw new Error("sandbox executable rejected");
}
const appFd = inheritedSecret(APP_PATH);
const botFd = inheritedSecret(BOT_PATH);
try {
const args = buildRuntimeIdentityLaunchArgs({
uid,
gid,
profileId,
hermesExecutablePath: hermes,
});
const stdio = Array(10).fill("ignore");
stdio[1] = "inherit";
stdio[2] = "inherit";
stdio[8] = appFd;
stdio[9] = botFd;
const child = spawn(setpriv, args, {
env: process.env,
shell: false,
stdio,
});
closeSync(appFd);
closeSync(botFd);
const forwardTerm = () => { if (!child.killed) child.kill("SIGTERM"); };
const forwardInt = () => { if (!child.killed) child.kill("SIGINT"); };
process.once("SIGTERM", forwardTerm);
process.once("SIGINT", forwardInt);
process.exitCode = await new Promise((resolveExit, rejectExit) => {
child.once("error", rejectExit);
child.once("exit", (code, signal) => resolveExit(code ?? (signal ? 1 : 0)));
});
} catch (error) {
try { closeSync(appFd); } catch {}
try { closeSync(botFd); } catch {}
throw error;
}
}
try {
await run();
} catch {
process.stderr.write("sellable-agent sandbox init failed closed\n");
process.exitCode = 1;
}