Files
PaperClipAI/packages/paperclip-runner/scripts/materialize-cursor-distribution.mjs
T
DottaandPaperclip e2b89b6302 Integrate pinned Cursor ACP runtime and explicit public setup
Consolidate Cursor v10 onto current controller behavior, retain admission gates, and prepare native interaction and cleanup qualification. Add the public provisioner and ordinary provider-pack assets.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-03 13:33:15 -05:00

125 lines
8.3 KiB
JavaScript

import { createHash } from "node:crypto";
import { spawnSync } from "node:child_process";
import { chmod, lstat, mkdir, mkdtemp, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
import { dirname, join, resolve, sep } from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { applyCursorRuntimePatch, CURSOR_RUNTIME_PATCH_VERSION } from "./cursor-runtime-patch.mjs";
const MANIFEST_PATH = fileURLToPath(new URL("../cursor-distributions.json", import.meta.url));
const MAX_ARCHIVE_BYTES = 256 * 1024 * 1024;
const MAX_TREE_BYTES = 1024 * 1024 * 1024;
const TREE_MANIFEST = ".paperclip-cursor-closure.json";
const digest = bytes => createHash("sha256").update(bytes).digest("hex");
export async function cursorDistribution(platform = process.platform, architecture = process.arch) {
const manifest = JSON.parse(await readFile(MANIFEST_PATH, "utf8"));
const distribution = manifest.platforms[`${platform}-${architecture}`];
if (!distribution) throw new Error(`Cursor has no pinned distribution for ${platform}/${architecture}`);
if (manifest.patchVersion !== CURSOR_RUNTIME_PATCH_VERSION || !/^[a-f0-9]{64}$/.test(distribution.vendorClosureSha256)) throw new Error("Cursor distribution must pin its owned runtime patch and vendor closure");
return { ...distribution, version: manifest.version, patchVersion: manifest.patchVersion, platform, architecture };
}
/** Hash every runtime file, including bundled Node, native addons and workers. */
export async function cursorDistributionClosure(directory) {
const entries = [];
let bytes = 0;
async function walk(root, prefix = "") {
const stat = await lstat(root);
if (!stat.isDirectory() || stat.isSymbolicLink()) throw new Error("Cursor closure contains a linked or non-directory root");
for (const name of (await readdir(root)).sort()) {
if (prefix === "" && name === TREE_MANIFEST) continue;
const path = prefix ? `${prefix}/${name}` : name;
if (/[\u0000-\u001f\u007f\\]/.test(path)) throw new Error("Cursor closure contains an unsafe path");
const absolute = join(root, name);
const before = await lstat(absolute);
if (before.isDirectory()) await walk(absolute, path);
else {
if (!before.isFile() || before.nlink !== 1) throw new Error("Cursor closure contains links or special files");
bytes += before.size;
if (bytes > MAX_TREE_BYTES || entries.length >= 10_000) throw new Error("Cursor closure exceeds size limits");
const source = await readFile(absolute);
const after = await lstat(absolute);
if (before.ino !== after.ino || before.dev !== after.dev || before.size !== after.size || before.mtimeMs !== after.mtimeMs || before.ctimeMs !== after.ctimeMs || source.length !== after.size || !after.isFile()) throw new Error("Cursor closure changed while being hashed");
entries.push({ path, sha256: digest(source), size: source.length, executable: Boolean(after.mode & 0o111) });
}
}
}
await walk(directory);
entries.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
return { entries, sha256: digest(JSON.stringify(entries)) };
}
export async function verifyCursorDistribution(directory, distribution) {
const closure = await cursorDistributionClosure(directory);
if (closure.sha256 !== distribution.closureSha256) throw new Error("Cursor full execution closure digest mismatch");
for (const required of [distribution.executable, distribution.entrypoint]) {
if (!closure.entries.some(entry => entry.path === required)) throw new Error("Cursor execution closure is missing its entrypoint");
}
return closure;
}
/** A pinned archive only; never runs the vendor installer or discovers PATH. */
export async function materializePinnedCursorDistribution(options = {}) {
const distribution = await cursorDistribution(options.platform, options.architecture);
if (!options.destination) throw new Error("Cursor materialization requires an explicit destination");
const destination = resolve(options.destination);
await mkdir(dirname(destination), { recursive: true, mode: 0o700 });
try {
await lstat(destination);
throw new Error("Cursor destination already exists; refusing to overwrite it");
} catch (error) { if (error.code !== "ENOENT") throw error; }
const temporary = await mkdtemp(join(dirname(destination), ".cursor-install-"));
try {
let archive;
if (options.archivePath) {
const stat = await lstat(options.archivePath);
if (!stat.isFile() || stat.size > MAX_ARCHIVE_BYTES) throw new Error("Cursor archive must be a bounded regular file");
archive = await readFile(options.archivePath);
} else {
const response = await fetch(distribution.url, { redirect: "error", signal: AbortSignal.timeout(180_000) });
if (!response.ok || !response.body) throw new Error(`Cursor archive download failed: ${response.status}`);
const chunks = [];
let length = 0;
for await (const chunk of response.body) {
length += chunk.length;
if (length > MAX_ARCHIVE_BYTES) throw new Error("Cursor archive download exceeds size limit");
chunks.push(chunk);
}
archive = Buffer.concat(chunks);
}
if (digest(archive) !== distribution.archiveSha256) throw new Error("Cursor archive digest mismatch");
const archivePath = join(temporary, "archive.tar.gz");
const unpacked = join(temporary, "unpacked");
await writeFile(archivePath, archive, { mode: 0o600 });
await mkdir(unpacked, { mode: 0o700 });
// Only authenticated vendor archive bytes reach tar. Inspect entries first;
// links/special files are forbidden, and every path shares one fixed root.
for (const flag of ["-tzf", "-tvzf"]) {
const listing = spawnSync("/usr/bin/tar", [flag, archivePath], { encoding: "utf8", timeout: 30_000, maxBuffer: 4 * 1024 * 1024 });
if (listing.status !== 0) throw new Error("Cursor archive listing failed");
const lines = listing.stdout.trim().split("\n");
if (flag === "-tzf" && lines.some(path => !path.startsWith("dist-package/") || path.split("/").includes("..") || /[\u0000-\u001f\u007f\\]/.test(path))) throw new Error("Cursor archive contains unsafe paths");
if (flag === "-tvzf" && lines.some(line => !["-", "d"].includes(line[0]))) throw new Error("Cursor archive contains links or special entries");
}
const extraction = spawnSync("/usr/bin/tar", ["-xzf", archivePath, "-C", unpacked, "--no-same-owner"], { encoding: "utf8", timeout: 120_000 });
if (extraction.status !== 0) throw new Error("Cursor archive extraction failed");
const packageRoot = join(unpacked, "dist-package");
await verifyCursorDistribution(packageRoot, { ...distribution, closureSha256: distribution.vendorClosureSha256 });
await applyCursorRuntimePatch(packageRoot, `${distribution.platform}-${distribution.architecture}`);
const closure = await verifyCursorDistribution(packageRoot, distribution);
// Record the pinned inventory for a later runtime lease. Its digest must be
// checked against the checked-in distribution pin before trusting entries.
await writeFile(join(packageRoot, TREE_MANIFEST), `${JSON.stringify({ schema: "paperclip.cursor_closure.v1", ...distribution, entries: closure.entries })}\n`, { mode: 0o600 });
for (const entry of closure.entries) await chmod(join(packageRoot, ...entry.path.split("/")), entry.executable ? 0o500 : 0o400);
await chmod(packageRoot, 0o700);
await rename(packageRoot, destination);
return { version: distribution.version, patchVersion: distribution.patchVersion, platform: distribution.platform, architecture: distribution.architecture, destination, executable: join(destination, distribution.executable), entrypoint: join(destination, distribution.entrypoint), archiveSha256: distribution.archiveSha256, closureSha256: closure.sha256 };
} finally { await rm(temporary, { recursive: true, force: true }); }
}
if (process.argv[1]?.endsWith("/materialize-cursor-distribution.mjs") && pathToFileURL(resolve(process.argv[1])).href === import.meta.url) {
const [destination, platform, architecture, archivePath] = process.argv.slice(2);
materializePinnedCursorDistribution({ destination, platform, architecture, archivePath }).then(result => process.stdout.write(`${JSON.stringify(result)}\n`)).catch(error => { console.error(error.message); process.exitCode = 1; });
}