Verify installed Claude SDK admission in native clean consumers

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-10-12 14:34:15 -05:00
1 parent 605433affe
commit 0953983526
3 files changed
+37 -4

No files matched your search

@@ -7,13 +7,30 @@ import { join } from 'node:path';
import test from 'node:test';
import { fileURLToPath } from 'node:url';
import { isWorkspacePackage, parseWorkspaceEntries } from '../build-standalone-public-packages.mjs';
import { GROK_PUBLIC_INSTALL_IMAGE, GROK_PUBLIC_INSTALL_LIFECYCLE, assertInstalledCodexPlatformProvenance, assertInstalledNativeConsumerContinuity, assertNoBundledCodexPayloads, grokConsumerDockerArgs, installedCodexProbeSource, installedDaemonProbeSource, macNodeHeadersDirectory, macPublicInstallLifecycleProfile, macInstalledStartupProbeSource } from '../grok-public-install-sandbox.mjs';
import { GROK_PUBLIC_INSTALL_IMAGE, GROK_PUBLIC_INSTALL_LIFECYCLE, assertInstalledCodexPlatformProvenance, assertInstalledNativeConsumerContinuity, assertNoBundledCodexPayloads, grokConsumerDockerArgs, installedClaudeProbeSource, installedCodexProbeSource, installedDaemonProbeSource, macNodeHeadersDirectory, macPublicInstallLifecycleProfile, macInstalledStartupProbeSource } from '../grok-public-install-sandbox.mjs';
import { verifyPiPackagedDaemon } from '../pi-public-install-probe.mjs';
import { runnerBinaryTarget } from '../../packages/paperclip-runner/src/live/runner-binary.ts';
const paths = { assets: '/private/staging/assets', consumer: '/private/staging/consumer', cache: '/private/staging/cache', uid: 1001, gid: 1001 };
const values = (args, flag) => args.flatMap((value, index) => value === flag ? [args[index + 1]] : []);
test('installed Claude admission propagates failure without emitting a passing receipt', () => {
const root = mkdtempSync(join(tmpdir(), 'installed-claude-admission-'));
try {
const fixture = join(root, 'runner.mjs'), probe = join(root, 'probe.mjs');
writeFileSync(probe, installedClaudeProbeSource(fixture, root));
writeFileSync(fixture, "export async function probeAcpxClaudeInstallation(model) { if (model !== 'claude-opus-5-5') throw Error('wrong model'); }\n");
const accepted = spawnSync(process.execPath, [probe], { encoding: 'utf8', timeout: 5000 });
assert.equal(accepted.status, 0, accepted.stderr);
assert.equal(JSON.parse(accepted.stdout).packageIntegrityAndNoFollowLease, 'passed');
assert.equal(JSON.parse(accepted.stdout).providerCalls, 0);
writeFileSync(fixture, "export async function probeAcpxClaudeInstallation() { throw Error('integrity rejected'); }\n");
const rejected = spawnSync(process.execPath, [probe], { encoding: 'utf8', timeout: 5000 });
assert.notEqual(rejected.status, 0);
assert.equal(rejected.stdout, '');
} finally { rmSync(root, { recursive: true, force: true }); }
});
test('native host startup preserves the consumer lock and both executable byte identities', () => {
const before = { lockSha256: 'a'.repeat(64), runnerSha256: 'b'.repeat(64), codexCommandSha256: 'c'.repeat(64), codexNativeExecutableSha256: 'e'.repeat(64) };
assert.doesNotThrow(() => assertInstalledNativeConsumerContinuity(before, { ...before }));
+14
View File
@@ -87,6 +87,20 @@ export function assertInstalledCodexPlatformProvenance(lock, installed, target)
// Execute this in the existing offline public consumer, never in the checkout.
// The public index and returned command must both come from the installed graph.
export function installedClaudeProbeSource(indexPath, serverRoot) {
return `
import { pathToFileURL } from 'node:url';
// Match the production sidecar's published-server package authority.
process.env.PAPERCLIP_ACPX_PROVIDER_PACKAGE_ROOT = ${JSON.stringify(serverRoot)};
process.env.PAPERCLIP_ACPX_PROVIDER_PACKAGE_MANIFEST = ${JSON.stringify(join(serverRoot, 'package.json'))};
const { probeAcpxClaudeInstallation } = await import(pathToFileURL(${JSON.stringify(indexPath)}).href);
await probeAcpxClaudeInstallation('claude-opus-5-5');
console.log(JSON.stringify({ schema: 'paperclip.claude-installed-admission.v1',
platform: process.platform, architecture: process.arch, model: 'claude-opus-5-5',
packageIntegrityAndNoFollowLease: 'passed', providerCalls: 0 }));
`;
}
export function installedCodexProbeSource(indexPath, consumerRoot, expectedVersion, { nativeCodex = false } = {}) {
return `
import assert from 'node:assert/strict';
+5 -3
View File
@@ -13,7 +13,7 @@ import { basename, dirname, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { packConcurrency, packStagedPackages, packTimings } from './pack-staged-packages.mjs';
import { materializePublishManifest } from './prepare-bundled-package.mjs';
import { GROK_PUBLIC_INSTALL_IMAGE, GROK_PUBLIC_INSTALL_LIFECYCLE, assertInstalledNativeConsumerContinuity, assertNoBundledCodexPayloads, grokConsumerDockerArgs, installedCodexProbeSource, installedDaemonProbeSource, macNodeHeadersDirectory, macPublicInstallLifecycleProfile, macInstalledStartupProbeSource } from './grok-public-install-sandbox.mjs';
import { GROK_PUBLIC_INSTALL_IMAGE, GROK_PUBLIC_INSTALL_LIFECYCLE, assertInstalledNativeConsumerContinuity, assertNoBundledCodexPayloads, grokConsumerDockerArgs, installedClaudeProbeSource, installedCodexProbeSource, installedDaemonProbeSource, macNodeHeadersDirectory, macPublicInstallLifecycleProfile, macInstalledStartupProbeSource } from './grok-public-install-sandbox.mjs';
import { startPrefetch, stopPrefetches } from './prefetch-child.mjs';
import { retainRunnerQualificationPackages } from './retain-runner-qualification-packages.mjs';
import { bundledInstallTimings, startBundledInstalls } from './stage-bundled-packages.mjs';
@@ -84,6 +84,7 @@ async function verifyMacConsumer(packageDirectory, sourceRevision, receiptPath)
const probe = (name, source) => { const file = join(root, name); writeFileSync(file, source); return JSON.parse(run(process.execPath, [file]).trim()); };
const daemon = probe('daemon-probe.mjs', installedDaemonProbeSource(installed, consumer, sourceRevision));
const codex = probe('codex-probe.mjs', installedCodexProbeSource(join(installed, 'index.js'), consumer, undefined, { nativeCodex: true }));
const claude = probe('claude-probe.mjs', installedClaudeProbeSource(join(installed, 'index.js'), server));
const startupScript = macInstalledStartupProbeSource(consumer, server, daemon.sha256, join(repo, 'tests/runner-e2e/installed-cli.ts'));
const startupFile = join(root, 'startup-probe.mts'); writeFileSync(startupFile, startupScript);
const startup = JSON.parse(run(process.execPath, ['--import', 'tsx', startupFile], join(repo, 'server')).trim());
@@ -91,7 +92,7 @@ async function verifyMacConsumer(packageDirectory, sourceRevision, receiptPath)
cleanNpmInstall: true, packageCount: manifest.archives.length, codexPackedPayloadsOmitted: true,
consumerLockPreserved: true, lifecycleSentinelVerified: true, lifecycleNodeHeadersVerified: true, lifecycleNodeVersion: process.versions.node,
lifecycleNetwork: 'sandbox-exec denied (loopback negative control)',
...daemon, ...codex, startup, providerCalls: 0 };
...daemon, ...codex, claude, startup, providerCalls: 0 };
writeFileSync(receiptPath, JSON.stringify(receipt, null, 2) + '\n');
console.log(JSON.stringify(receipt));
} finally { rmSync(root, { recursive: true, force: true }); }
@@ -165,6 +166,7 @@ async function verifyLinuxArmConsumer(packageDirectory, sourceRevision, receiptP
const hostServer = join(consumer, 'node_modules/@paperclipai/server'), hostInstalled = join(hostServer, 'dist/vendor/paperclip-runner');
const hostDaemon = () => hostProbe('host-daemon-probe.mjs', installedDaemonProbeSource(hostInstalled, consumer, sourceRevision));
const hostCodex = () => hostProbe('host-codex-probe.mjs', installedCodexProbeSource(join(hostInstalled, 'index.js'), consumer, undefined, { nativeCodex: true }));
const claude = hostProbe('host-claude-probe.mjs', installedClaudeProbeSource(join(hostInstalled, 'index.js'), hostServer));
const digest = value => createHash('sha256').update(value).digest('hex');
const beforeCodex = hostCodex();
const before = { lockSha256: digest(lock), runnerSha256: hostDaemon().sha256.slice(7), codexCommandSha256: beforeCodex.codexCommandSha256, codexNativeExecutableSha256: beforeCodex.codexNativeExecutableSha256 };
@@ -186,7 +188,7 @@ async function verifyLinuxArmConsumer(packageDirectory, sourceRevision, receiptP
startupEnvironment: 'native host, same installed graph, isolated empty homes',
startupHost: { nodeVersion: process.versions.node, nodeModulesAbi: process.versions.modules, glibcVersionRuntime: hostHeader.glibcVersionRuntime, kernelRelease: hostHeader.osRelease },
installedIdentityBefore: before, installedIdentityAfter: after,
...daemon, ...codex, startup, providerCalls: 0 };
...daemon, ...codex, claude, startup, providerCalls: 0 };
writeFileSync(receiptPath, JSON.stringify(receipt, null, 2) + '\n');
console.log(JSON.stringify(receipt));
} finally { rmSync(root, { recursive: true, force: true }); }