Files
PaperClipAI/packages/paperclip-runner/scripts/release-provider-pack.test.mjs
DottaandPaperclip 36d9eb0c3c fix: preserve release lanes and ship a portable Linux daemon
Build the npm Linux daemon as static musl and validate its ELF load contract. Keep the Docker daemon and provider-pack image authority separate. Preserve asset assembly when unrelated workflow ancestors are skipped, and reuse the release verifier for a bounded Linux-only check.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-08 07:33:16 -05:00

280 lines
20 KiB
JavaScript

import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { spawnSync } from "node:child_process";
import { cpSync, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import test from "node:test";
import profiles from "../acpx-profiles.json" with { type: "json" };
import distributions from "../cursor-distributions.json" with { type: "json" };
import { verifyReleaseProviderPack } from "./release-provider-pack.mjs";
import { makeRunnerReleaseArchive, readRunnerReleaseArchive, verifyPortableLinuxDaemon, verifyReleaseDaemonMetadata, verifyRunnerReleaseData } from "../../../scripts/release-runner-artifacts.mjs";
import { gzipSync, gunzipSync } from "node:zlib";
const revision = "a".repeat(40);
const digest = "sha256:" + "b".repeat(64);
const canonical = value => Array.isArray(value) ? `[${value.map(canonical).join(",")}]`
: value && typeof value === "object" ? `{${Object.keys(value).sort().map(key => `${JSON.stringify(key)}:${canonical(value[key])}`).join(",")}}` : JSON.stringify(value);
function fixture() {
const payload = { target: { platform: "linux", architecture: "x64" }, runnerSourceRevision: revision,
pins: { acpx: profiles.acpxVersion },
acpxProfileDigests: Object.fromEntries(["grok", "claude", "codex"].map(agent => [agent, profiles.profiles[agent].commandDigest])),
providers: { cursor: { version: profiles.profiles.cursor.agentServerVersion, profileDigest: profiles.profiles.cursor.commandDigest,
closureDigest: `sha256:${distributions.platforms["linux-x64"].closureSha256}`, qualification: "qualified",
path: "provider-assets/cursor/linux-x64", sha256: digest } } };
return payload;
}
function seal(payload) { return { schema: "paperclip-runner/remote-provider-pack/v1", payload,
digest: `sha256:${createHash("sha256").update(canonical(payload)).digest("hex")}` }; }
function daemonMetadata() { return { schema: 'paperclip-runner/runnerd-build-metadata/v1', binaryName: 'paperclip-runnerd',
packageName: '@paperclipai/paperclip-runner', packageVersion: '0.0.0', binaryContractVersion: 2, nativeExecutionVersion: 1,
harnessDriverVersion: 1, prp: { name: 'paperclip.runner', minimumVersion: 1, maximumVersion: 2 },
durableSessionCapabilities: ['unlimited_runtime', 'connection_lease_renewal'],
prpTransportModes: ['dial_ws_loopback', 'dial_wss', 'listen_ws'] }; }
test('release daemon compatibility rejects lookalikes, incompatible contracts and missing production capabilities', () => {
assert.equal(verifyReleaseDaemonMetadata(daemonMetadata()).binaryContractVersion, 2);
for (const mismatch of [
{ schema: 'other' }, { binaryName: 'other' }, { packageName: 'other' }, { packageVersion: '' },
{ binaryContractVersion: 999 }, { nativeExecutionVersion: 2 }, { harnessDriverVersion: 2 },
{ prp: { name: 'other', minimumVersion: 1, maximumVersion: 2 } },
{ prp: { name: 'paperclip.runner', minimumVersion: 2, maximumVersion: 2 } },
{ prp: { name: 'paperclip.runner', minimumVersion: 1, maximumVersion: 1 } },
{ durableSessionCapabilities: ['unlimited_runtime'] }, { prpTransportModes: ['dial_ws_loopback'] },
]) assert.throws(() => verifyReleaseDaemonMetadata({ ...daemonMetadata(), ...mismatch }));
});
function linuxElf(types = [1]) {
const bytes = Buffer.alloc(64 + types.length * 56 + 32);
bytes.set([0x7f, 0x45, 0x4c, 0x46, 2, 1, 1]);
bytes.writeUInt16LE(3, 16); bytes.writeUInt16LE(62, 18); bytes.writeUInt32LE(1, 20);
bytes.writeBigUInt64LE(64n, 32); bytes.writeUInt16LE(64, 52); bytes.writeUInt16LE(56, 54);
bytes.writeUInt16LE(types.length, 56);
for (const [index, type] of types.entries()) {
const header = 64 + index * 56;
bytes.writeUInt32LE(type, header); bytes.writeUInt32LE(5, header + 4);
if (type === 2) {
bytes.writeBigUInt64LE(BigInt(64 + types.length * 56), header + 8);
bytes.writeBigUInt64LE(32n, header + 32);
}
}
return bytes;
}
test('portable Linux release daemons reject interpreters, shared libraries and malformed static ELF claims', () => {
assert.deepEqual(verifyPortableLinuxDaemon(linuxElf()), { buildTarget: 'x86_64-unknown-linux-musl', linkage: 'static' });
assert.equal(verifyPortableLinuxDaemon(linuxElf([1, 2])).linkage, 'static', 'Static PIE may retain its own terminated relocation table');
assert.throws(() => verifyPortableLinuxDaemon(linuxElf([1, 3])), /libc interpreter/);
const dynamic = linuxElf([1, 2]); dynamic.writeBigUInt64LE(1n, 176);
assert.throws(() => verifyPortableLinuxDaemon(dynamic), /shared library/);
const truncated = linuxElf().subarray(0, 64);
assert.throws(() => verifyPortableLinuxDaemon(truncated), /invalid program table/);
const overflow = linuxElf(); overflow.writeBigUInt64LE(2n ** 63n, 32);
assert.throws(() => verifyPortableLinuxDaemon(overflow), /invalid program table/);
assert.throws(() => verifyPortableLinuxDaemon(linuxElf([2])), /executable load/);
const unterminated = linuxElf([1, 2]); unterminated.writeBigUInt64LE(7n, 176); unterminated.writeBigUInt64LE(7n, 192);
assert.throws(() => verifyPortableLinuxDaemon(unterminated), /unterminated dynamic table/);
});
function releaseData() {
const files = new Map(), platforms = {};
for (const target of ["darwin-arm64", "darwin-x64", "linux-x64"]) {
const bytes = target === 'linux-x64' ? linuxElf() : Buffer.alloc(64);
if (target !== 'linux-x64') { bytes.writeUInt32LE(0xfeedfacf, 0); bytes.writeUInt32LE(target === "darwin-arm64" ? 0x0100000c : 0x01000007, 4); }
files.set(`bin/${target}/paperclip-runnerd`, bytes);
platforms[target] = { path: `${target}/paperclip-runnerd`, sha256: `sha256:${createHash("sha256").update(bytes).digest("hex")}` };
}
const pack = seal(fixture());
files.set("remote-provider-packs/linux-x64/provider-pack.json", Buffer.from(JSON.stringify(pack)));
files.set("bin/release-manifest.json", Buffer.from(JSON.stringify({ schema: "paperclip.runner.release-binaries.v1", sourceRevision: revision,
platforms, remoteProviderPack: { target: "linux-x64", sourceRevision: revision, digest: pack.digest } })));
files.set("assembly-receipt.json", Buffer.from(JSON.stringify({ schema: "paperclip.runner.release-assembly.v1", sourceRevision: revision,
sourcePins: { profiles: `sha256:${createHash("sha256").update(readFileSync(new URL('../acpx-profiles.json', import.meta.url))).digest('hex')}`,
distributions: `sha256:${createHash("sha256").update(readFileSync(new URL('../cursor-distributions.json', import.meta.url))).digest('hex')}` },
providerImage: { sourceRevision: revision, platform: "linux/amd64", imageId: digest },
linuxDaemon: { sourceRevision: revision, buildTarget: 'x86_64-unknown-linux-musl', linkage: 'static' },
daemonMetadata: Object.fromEntries(Object.keys(platforms).map(target => [target, daemonMetadata()])),
files: Object.fromEntries([...files].map(([path, bytes]) => [path, `sha256:${createHash("sha256").update(bytes).digest("hex")}`])) })));
return files;
}
test("release transfer preserves only the assembled daemon and actual provider-pack data", () => {
const files = releaseData(), archive = makeRunnerReleaseArchive(files, revision);
const checksum = `sha256:${createHash("sha256").update(archive).digest("hex")}`;
assert.deepEqual(readRunnerReleaseArchive(archive, revision, checksum), files);
assert.throws(() => readRunnerReleaseArchive(archive, "c".repeat(40), checksum), /source mismatch/);
assert.throws(() => readRunnerReleaseArchive(archive, revision, digest), /archive checksum mismatch/);
});
test('the real transfer CLI validates before extraction, restores executable modes and refuses existing output', () => {
const root = mkdtempSync(join(tmpdir(), 'runner-release-transfer-'));
const helper = fileURLToPath(new URL('../../../scripts/release-runner-artifacts.mjs', import.meta.url));
try {
const archive = makeRunnerReleaseArchive(releaseData(), revision), archivePath = join(root, 'assets.tar.gz');
const checksumPath = join(root, 'assets.sha256'), output = join(root, 'output');
writeFileSync(archivePath, archive); writeFileSync(checksumPath, digest);
const invoke = (...args) => spawnSync(process.execPath, [helper, ...args], { encoding: 'utf8' });
let result = invoke('unpack', revision, archivePath, checksumPath, output);
assert.equal(result.status, 1); assert.match(result.stderr, /archive checksum mismatch/);
assert.equal(existsSync(output), false, 'No data can be written before the entire envelope passes');
writeFileSync(checksumPath, `sha256:${createHash('sha256').update(archive).digest('hex')}`);
result = invoke('unpack', revision, archivePath, checksumPath, output);
assert.equal(result.status, 0, result.stderr);
assert.equal(lstatSync(join(output, 'bin/darwin-arm64/paperclip-runnerd')).mode & 0o777, 0o755);
assert.equal(lstatSync(join(output, 'assembly-receipt.json')).mode & 0o777, 0o644);
result = invoke('unpack', revision, archivePath, checksumPath, output);
assert.equal(result.status, 1); assert.match(result.stderr, /new absolute owned directory/);
} finally { rmSync(root, { recursive: true, force: true }); }
});
test('image-sourced Linux daemon recording validates data without executing its ELF on the host', () => {
const root = mkdtempSync(join(tmpdir(), 'runner-image-daemon-record-'));
const helper = fileURLToPath(new URL('../../../scripts/release-runner-artifacts.mjs', import.meta.url));
try {
// This header fixture cannot execute. Successful recording proves this
// transfer leg consumes image-probed metadata rather than host execution.
const binary = join(root, 'runnerd'), metadata = join(root, 'metadata.json'), imagePath = join(root, 'image.json');
const image = `ghcr.io/paperclipai/paperclip@${digest}`;
writeFileSync(binary, releaseData().get('bin/linux-x64/paperclip-runnerd'), { mode: 0o755 });
writeFileSync(metadata, JSON.stringify(daemonMetadata()));
const imageData = { Os: 'linux', Architecture: 'amd64', Id: digest, RepoDigests: [image],
Config: { Labels: { 'org.opencontainers.image.revision': revision }, Env: ['PRIVATE_TOKEN=do-not-export'] } };
writeFileSync(imagePath, JSON.stringify([imageData]));
const output = join(root, 'output');
let result = spawnSync(process.execPath, [helper, 'record-image-binary', revision, binary, metadata, imagePath, output, image], { encoding: 'utf8' });
assert.equal(result.status, 0, result.stderr);
const receipt = readFileSync(join(output, 'receipt.json'), 'utf8');
assert.equal(JSON.parse(receipt).providerImage.requestedImage, image);
assert.equal(JSON.parse(receipt).sha256, `sha256:${createHash('sha256').update(readFileSync(binary)).digest('hex')}`);
assert.doesNotMatch(receipt, /PRIVATE_TOKEN|do-not-export|Config/);
imageData.Config.Labels['org.opencontainers.image.revision'] = 'c'.repeat(40);
writeFileSync(imagePath, JSON.stringify([imageData]));
const wrong = join(root, 'wrong');
result = spawnSync(process.execPath, [helper, 'record-image-binary', revision, binary, metadata, imagePath, wrong, image], { encoding: 'utf8' });
assert.equal(result.status, 1); assert.equal(existsSync(wrong), false);
} finally { rmSync(root, { recursive: true, force: true }); }
});
test("release transfer rejects links, unsafe or duplicate paths, truncation and permissions before extraction", () => {
const archive = makeRunnerReleaseArchive(releaseData(), revision);
for (const corrupt of [
tar => { tar.fill(0, 0, 100); tar.write("../paperclip-runnerd", 0); },
tar => { tar[156] = 50; },
tar => { tar.subarray(0, 100).copy(tar, 1024); },
tar => { tar.write("00004755\0", 100, 8); },
]) {
const tar = gunzipSync(archive); corrupt(tar);
const headerStart = tar[1024] === tar[0] && tar.subarray(1024, 1124).equals(tar.subarray(0, 100)) ? 1024 : 0;
const header = tar.subarray(headerStart, headerStart + 512); header.fill(32, 148, 156);
const sum = [...header].reduce((sum, byte) => sum + byte, 0);
header.write(sum.toString(8).padStart(6, "0") + "\0 ", 148, 8);
const bytes = gzipSync(tar), hash = `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
assert.throws(() => readRunnerReleaseArchive(bytes, revision, hash), /Unsafe|forbidden|permissions/);
}
const truncated = archive.subarray(0, archive.length - 4);
assert.throws(() => readRunnerReleaseArchive(truncated, revision, `sha256:${createHash("sha256").update(truncated).digest("hex")}`));
});
test("release data rejects rehashed wrong targets and provider-image provenance", () => {
const files = releaseData();
files.set("bin/darwin-arm64/paperclip-runnerd", files.get("bin/linux-x64/paperclip-runnerd"));
const receipt = JSON.parse(files.get("assembly-receipt.json"));
receipt.files["bin/darwin-arm64/paperclip-runnerd"] = `sha256:${createHash("sha256").update(files.get("bin/darwin-arm64/paperclip-runnerd")).digest("hex")}`;
files.set("assembly-receipt.json", Buffer.from(JSON.stringify(receipt)));
assert.throws(() => verifyRunnerReleaseData(files, revision), /architecture mismatch/);
for (const changes of [{ sourceRevision: "c".repeat(40) }, { requestedImage: `ghcr.io/paperclipai/paperclip@${digest}`, repoDigests: [] }]) {
const source = releaseData(), receipt = JSON.parse(source.get("assembly-receipt.json"));
Object.assign(receipt.providerImage, changes); source.set("assembly-receipt.json", Buffer.from(JSON.stringify(receipt)));
assert.throws(() => verifyRunnerReleaseData(source, revision), /image (?:source|digest) mismatch/);
}
const incompatible = releaseData(), incompatibleReceipt = JSON.parse(incompatible.get('assembly-receipt.json'));
incompatibleReceipt.daemonMetadata['linux-x64'].nativeExecutionVersion = 999;
incompatible.set('assembly-receipt.json', Buffer.from(JSON.stringify(incompatibleReceipt)));
assert.throws(() => verifyRunnerReleaseData(incompatible, revision), /Incompatible daemon nativeExecutionVersion/);
const mixed = releaseData(), mixedReceipt = JSON.parse(mixed.get('assembly-receipt.json'));
mixedReceipt.linuxDaemon.sourceRevision = 'c'.repeat(40);
mixed.set('assembly-receipt.json', Buffer.from(JSON.stringify(mixedReceipt)));
assert.throws(() => verifyRunnerReleaseData(mixed, revision), /portable exact-source/);
const unportable = releaseData(), executable = linuxElf([1, 3]);
unportable.set('bin/linux-x64/paperclip-runnerd', executable);
const manifest = JSON.parse(unportable.get('bin/release-manifest.json'));
const unportableReceipt = JSON.parse(unportable.get('assembly-receipt.json'));
const hash = `sha256:${createHash('sha256').update(executable).digest('hex')}`;
manifest.platforms['linux-x64'].sha256 = hash;
unportableReceipt.files['bin/linux-x64/paperclip-runnerd'] = hash;
unportable.set('bin/release-manifest.json', Buffer.from(JSON.stringify(manifest)));
unportableReceipt.files['bin/release-manifest.json'] = `sha256:${createHash('sha256').update(unportable.get('bin/release-manifest.json')).digest('hex')}`;
unportable.set('assembly-receipt.json', Buffer.from(JSON.stringify(unportableReceipt)));
assert.throws(() => verifyRunnerReleaseData(unportable, revision), /libc interpreter/, 'Rehashed GNU binary must not inherit a static claim');
});
test("release transfer binds qualification JSON snapshots to the selected source", () => {
const files = releaseData();
assert.throws(() => verifyRunnerReleaseData(files, revision, { profiles: Buffer.from('{}'), distributions: Buffer.from('{}') }), /source profile snapshot mismatch/);
for (const snapshots of [[{}, distributions], [profiles, {}], [null, null]]) {
assert.throws(() => verifyReleaseProviderPack(seal(fixture()), revision, ...snapshots), /Invalid release qualification snapshots/);
}
const noCursorVersion = structuredClone(profiles); delete noCursorVersion.profiles.cursor.agentServerVersion;
const malformedClosure = structuredClone(distributions); malformedClosure.platforms['linux-x64'].closureSha256 = 'unknown';
for (const snapshots of [[noCursorVersion, distributions], [profiles, malformedClosure]]) {
assert.throws(() => verifyReleaseProviderPack(seal(fixture()), revision, ...snapshots), /Invalid release qualification snapshots/);
}
const altered = structuredClone(profiles); altered.profiles.claude.commandDigest = digest;
assert.throws(() => verifyReleaseProviderPack(seal(fixture()), revision, altered, distributions), /ACPX profiles/);
assert.equal(verifyReleaseProviderPack(seal(fixture()), revision, profiles, distributions).payload.runnerSourceRevision, revision);
});
test("accepts a pack whose source and profiles match the assembled release", () => {
const manifest = seal(fixture()); assert.equal(verifyReleaseProviderPack(manifest, revision), manifest);
});
for (const field of ["version", "profileDigest", "closureDigest", "qualification", "path"]) {
test(`rejects an independently rehashed stale Cursor ${field}`, () => {
const payload = fixture(); payload.providers.cursor[field] = field.endsWith("Digest") ? digest : "stale";
assert.throws(() => verifyReleaseProviderPack(seal(payload), revision), /Cursor identity/);
});
}
test("rejects omitted Cursor, conflicting legacy inventory, stale sources and forged payload digests", () => {
const missing = fixture(); delete missing.providers.cursor;
assert.throws(() => verifyReleaseProviderPack(seal(missing), revision), /Cursor identity/);
const mixed = fixture(); mixed.candidateProviders = { cursor: { ...mixed.providers.cursor, profileDigest: digest } };
assert.throws(() => verifyReleaseProviderPack(seal(mixed), revision), /Cursor identity/);
const old = fixture(); old.runnerSourceRevision = "c".repeat(40);
assert.throws(() => verifyReleaseProviderPack(seal(old), revision), /release source/);
const forged = seal(fixture()); forged.digest = digest;
assert.throws(() => verifyReleaseProviderPack(forged, revision), /payload digest/);
});
test("release assembly rejects rehashed stale or missing packs before staging any files", () => {
const root = mkdtempSync(join(tmpdir(), "release-provider-pack-"));
const source = dirname(dirname(fileURLToPath(import.meta.url)));
try {
// Run the actual assembler in an isolated layout so it cannot overwrite built artifacts.
for (const file of ["scripts/stage-release-runner-binaries.mjs", "scripts/release-provider-pack.mjs", "src/live/runner-binary.ts", "acpx-profiles.json", "cursor-distributions.json"]) {
mkdirSync(dirname(join(root, file)), { recursive: true }); cpSync(join(source, file), join(root, file));
}
const platforms = {};
for (const target of ["darwin-arm64", "darwin-x64", "linux-x64"]) {
const bytes = Buffer.alloc(64);
if (target === "linux-x64") {
bytes.set([0x7f, 0x45, 0x4c, 0x46, 2, 1]); bytes.writeUInt16LE(62, 18);
} else {
bytes.writeUInt32LE(0xfeedfacf, 0); bytes.writeUInt32LE(target === "darwin-arm64" ? 0x0100000c : 0x01000007, 4);
}
const path = join(root, target); writeFileSync(path, bytes);
platforms[target] = { path, sha256: `sha256:${createHash("sha256").update(bytes).digest("hex")}` };
}
const payload = fixture(); payload.providers.cursor.profileDigest = digest;
const bytes = JSON.stringify(seal(payload)), path = join(root, "provider-pack.json"); writeFileSync(path, bytes);
const manifest = { sourceRevision: revision, platforms, remoteProviderPack: { path, sha256: `sha256:${createHash("sha256").update(bytes).digest("hex")}` } };
for (const error of [/Cursor identity/, /requires the matching Linux remote provider pack/]) {
writeFileSync(join(root, "release.json"), JSON.stringify(manifest));
const result = spawnSync(process.execPath, [join(root, "scripts/stage-release-runner-binaries.mjs"), join(root, "release.json")], { encoding: "utf8" });
assert.equal(result.status, 1); assert.match(result.stderr, error);
assert.equal(existsSync(join(root, "dist")), false);
delete manifest.remoteProviderPack;
}
} finally { rmSync(root, { recursive: true, force: true }); }
});