@sellable/install
Version:
One-command installer for Sellable MCP in Claude Code, Codex, and Hermes
152 lines (144 loc) • 6.55 kB
JavaScript
import { spawn, spawnSync } from "node:child_process";
import {
existsSync,
lstatSync,
mkdirSync,
readFileSync,
writeFileSync,
} from "node:fs";
import { join } from "node:path";
import {
buildRuntimeBoundaryContract,
buildRuntimeBoundaryLaunchSpec,
buildRuntimeBoundaryNftProgram,
expectedRuntimeCgroup,
inspectRuntimeBoundaryInputs,
runtimeBoundaryNftRulesMatch,
} from "../lib/sellable-agent/runtime-boundary.mjs";
const CONFIG_PATH = "/etc/sellable-agent/runtime-boundary.json";
const SECRET = /xox[bap]-|sat_[A-Za-z0-9_-]+|asec\.v1\.|access[_-]?token|refresh[_-]?token/i;
function fixedRootConfig() {
const link = lstatSync(CONFIG_PATH);
if (link.isSymbolicLink() || !link.isFile() || link.uid !== 0 || link.gid !== 0 || (link.mode & 0o777) !== 0o400) {
throw new Error("runtime boundary config rejected");
}
const bytes = readFileSync(CONFIG_PATH, "utf8");
if (bytes.length > 64 * 1024 || SECRET.test(bytes)) throw new Error("runtime boundary config rejected");
const built = buildRuntimeBoundaryContract(JSON.parse(bytes));
if (!built.ok) throw new Error("runtime boundary contract rejected");
return built.contract;
}
function provisionCgroup(contract) {
const parent = contract.paths.cgroupRoot;
const systemRoot = "/sys/fs/cgroup";
const rootInfo = lstatSync(systemRoot);
if (rootInfo.isSymbolicLink() || !rootInfo.isDirectory()) throw new Error("cgroup v2 root rejected");
const controllers = readFileSync(join(systemRoot, "cgroup.controllers"), "utf8").trim().split(/\s+/);
if (!["cpu", "memory", "pids"].every((name) => controllers.includes(name))) {
throw new Error("required cgroup controllers unavailable");
}
const requiredControllers = ["cpu", "memory", "pids"];
const enableControllers = (path) => {
const controlPath = join(path, "cgroup.subtree_control");
const enabled = new Set(readFileSync(controlPath, "utf8").trim().split(/\s+/).filter(Boolean));
const missing = requiredControllers.filter((name) => !enabled.has(name));
if (missing.length) writeFileSync(controlPath, `${missing.map((name) => `+${name}`).join(" ")}\n`);
const observed = new Set(readFileSync(controlPath, "utf8").trim().split(/\s+/).filter(Boolean));
if (requiredControllers.some((name) => !observed.has(name))) throw new Error("cgroup controller enablement failed");
};
enableControllers(systemRoot);
if (!existsSync(parent)) mkdirSync(parent, { mode: 0o755 });
const parentInfo = lstatSync(parent);
if (parentInfo.isSymbolicLink() || !parentInfo.isDirectory() || parentInfo.uid !== 0 || (parentInfo.mode & 0o022) !== 0) {
throw new Error("runtime cgroup parent rejected");
}
enableControllers(parent);
const expected = expectedRuntimeCgroup(contract);
if (!existsSync(expected.path)) mkdirSync(expected.path, { mode: 0o755 });
const groupInfo = lstatSync(expected.path);
if (groupInfo.isSymbolicLink() || !groupInfo.isDirectory() || groupInfo.uid !== 0 || (groupInfo.mode & 0o022) !== 0) {
throw new Error("runtime cgroup rejected");
}
if (readFileSync(join(expected.path, "cgroup.procs"), "utf8").trim()) {
throw new Error("runtime cgroup still has processes");
}
for (const [name, value] of Object.entries(expected.files)) {
writeFileSync(join(expected.path, name), `${value}\n`);
if (readFileSync(join(expected.path, name), "utf8").trim() !== value) throw new Error("runtime cgroup limit drift");
}
writeFileSync(join(expected.path, "cgroup.procs"), `${process.pid}\n`);
}
function provisionFirewall(contract) {
const policy = buildRuntimeBoundaryNftProgram(contract);
if (!policy.ok) throw new Error("runtime firewall contract rejected");
const listed = spawnSync(contract.paths.nftPath, ["-j", "list", "tables"], {
encoding: "utf8",
env: { PATH: "/usr/sbin:/usr/bin:/sbin:/bin" },
timeout: 10_000,
});
if (listed.status !== 0) throw new Error("runtime firewall inventory failed");
let inventory;
try { inventory = JSON.parse(listed.stdout); } catch { throw new Error("runtime firewall inventory rejected"); }
const exists = Array.isArray(inventory?.nftables) && inventory.nftables.some((entry) =>
entry?.table?.family === "inet" && entry.table.name === policy.table);
const program = `${exists ? `delete table inet ${policy.table}\n` : ""}${policy.program}`;
const applied = spawnSync(contract.paths.nftPath, policy.applyArgs, {
input: program,
encoding: "utf8",
env: { PATH: "/usr/sbin:/usr/bin:/sbin:/bin" },
timeout: 10_000,
maxBuffer: 64 * 1024,
});
if (applied.status !== 0 || SECRET.test(`${applied.stdout}${applied.stderr}`)) {
throw new Error("runtime firewall apply failed");
}
const observed = spawnSync(contract.paths.nftPath, ["list", "table", "inet", policy.table], {
encoding: "utf8",
env: { PATH: "/usr/sbin:/usr/bin:/sbin:/bin" },
timeout: 10_000,
});
if (observed.status !== 0 || !runtimeBoundaryNftRulesMatch(contract, observed.stdout)) {
throw new Error("runtime firewall readback failed");
}
}
async function run() {
if (process.argv.length !== 2 || process.getuid?.() !== 0 || process.getgid?.() !== 0) {
throw new Error("runtime launcher identity rejected");
}
for (const fd of [8, 9]) {
const link = lstatSync(`/proc/self/fd/${fd}`);
if (!link.isSymbolicLink()) throw new Error("runtime Slack descriptor rejected");
}
const contract = fixedRootConfig();
if (!inspectRuntimeBoundaryInputs(contract).ok) throw new Error("runtime boundary inputs rejected");
provisionCgroup(contract);
provisionFirewall(contract);
const spec = buildRuntimeBoundaryLaunchSpec(contract);
if (!spec.ok) throw new Error("runtime launch spec rejected");
const stdio = Array(10).fill("ignore");
stdio[1] = "inherit";
stdio[2] = "inherit";
stdio[8] = 8;
stdio[9] = 9;
const child = spawn(spec.command, spec.args, {
env: spec.env,
shell: false,
stdio,
});
const forwardTerm = () => { if (!child.killed) child.kill("SIGTERM"); };
const forwardInt = () => { if (!child.killed) child.kill("SIGINT"); };
process.on("SIGTERM", forwardTerm);
process.on("SIGINT", forwardInt);
const exitCode = await new Promise((resolveExit, rejectExit) => {
child.once("error", rejectExit);
child.once("exit", (code, signal) => resolveExit(code ?? (signal ? 1 : 0)));
});
process.exitCode = exitCode;
}
try {
await run();
} catch {
process.stderr.write("sellable-agent runtime boundary failed closed\n");
process.exitCode = 1;
}