fix: separate Codex login and ARM host qualification

Keep daemon ABI admission separate from CLI login and preserve restricted Docker installation checks before native host startup.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-10-12 11:20:36 -05:00
1 parent 695506ad39
commit 123a188374
7 files changed
+152 -14

No files matched your search

@@ -7,13 +7,31 @@ 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, 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, 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('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) };
assert.doesNotThrow(() => assertInstalledNativeConsumerContinuity(before, { ...before }));
for (const field of Object.keys(before)) {
assert.throws(() => assertInstalledNativeConsumerContinuity(before, { ...before, [field]: 'd'.repeat(64) }), new RegExp(field));
assert.throws(() => assertInstalledNativeConsumerContinuity({ ...before, [field]: '' }, { ...before, [field]: '' }), new RegExp(field));
}
const source = readFileSync(new URL('../verify-grok-npm-install.mjs', import.meta.url), 'utf8');
const arm = source.split('async function verifyLinuxArmConsumer')[1].split("if (process.argv[2] === '--linux-arm64-consumer')")[0];
assert.match(arm, /const hostEnv = \{ PATH: .*HOME: home, TMPDIR: hostTmp, LC_ALL: 'C', TZ: 'UTC' \}/);
assert.match(arm, /execFileSync\(process.execPath, \[file\], \{ cwd: consumer, env: hostEnv/);
assert.ok(arm.indexOf('isolated(GROK_PUBLIC_INSTALL_LIFECYCLE)') < arm.indexOf("hostProbe('host-startup-probe.mjs'"));
assert.ok(arm.indexOf('const before =') < arm.indexOf("hostProbe('host-startup-probe.mjs'"));
assert.ok(arm.indexOf("hostProbe('host-startup-probe.mjs'") < arm.indexOf('const after ='));
assert.match(arm, /assertInstalledNativeConsumerContinuity\(before, after\)/);
assert.doesNotMatch(arm, /externallySandboxed|externalSandbox|sudo|sysctl|--privileged|seccomp=unconfined|unshare/);
});
test('Mac offline lifecycle uses only the matching executing Node SDK', () => {
const root = realpathSync(mkdtempSync(join(tmpdir(), 'mac-node-headers-test-')));
try {
@@ -266,6 +284,7 @@ test('the installed Codex probe exercises the public export and rejects incomple
assert.equal(JSON.parse(result.stdout).pinnedCodexCommandVerified, true);
assert.equal(JSON.parse(result.stdout).codexPlatformPackageSource, 'official npm');
assert.equal(JSON.parse(result.stdout).codexConsumerPlatform, target);
assert.equal(JSON.parse(result.stdout).codexCommandSha256, createHash('sha256').update(readFileSync(command)).digest('hex'));
assert.equal(JSON.parse(result.stdout).codexQualifiedInstallationVerified, true);
assert.equal(JSON.parse(result.stdout).codexCommandLeaseVerified, true);
assert.equal(JSON.parse(result.stdout).codexNativeSandboxResourcesVerified, true);
@@ -438,7 +457,9 @@ test('native ARM clean consumers keep the existing container and installed-runti
assert.match(arm, /isolated\(GROK_PUBLIC_INSTALL_LIFECYCLE\)/);
assert.match(arm, /installedCodexProbeSource\(installed \+ '\/index.js', '\/consumer', undefined, \{ nativeCodex: true \}\)/);
assert.match(arm, /tests\/runner-e2e\/installed-cli.ts/);
assert.match(arm, /temporarySizeMiB: 2048, temporaryExecutable: true/);
assert.match(arm, /process.versions.node.split\('\.'\)\[0\], '24'/);
assert.match(arm, /macInstalledStartupProbeSource\(consumer, hostServer, daemon.sha256, join\(assets, 'installed-cli.ts'\)\)/);
assert.match(arm, /startupEnvironment: 'native host, same installed graph, isolated empty homes'/);
assert.doesNotMatch(arm, /stage-runner|runner\/target|PAPERCLIP_RUNNER_BINARY|prerequisite:/);
});
+11
View File
@@ -91,6 +91,7 @@ export function installedCodexProbeSource(indexPath, consumerRoot, expectedVersi
return `
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import { createHash } from 'node:crypto';
import { createRequire } from 'node:module';
import { accessSync, constants, existsSync, lstatSync, readFileSync, readdirSync, realpathSync, statSync } from 'node:fs';
import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
@@ -174,6 +175,7 @@ export function installedCodexProbeSource(indexPath, consumerRoot, expectedVersi
const lease = await installation.openCommand();
await lease.close();`}
console.log(JSON.stringify({ pinnedCodexCommandVerified: true, installedCodexVersion: version,
codexCommandSha256: createHash('sha256').update(readFileSync(command)).digest('hex'),
...provenance, codexSelectedPlatformPath: selectedPlatform.path, codexSelectedPlatformVersion: selectedMetadata.version,
codexNativeSandboxResourcesVerified: true,
${nativeCodex ? '' : `pinnedCodexVersion: ${JSON.stringify(expectedVersion)}, codexQualifiedInstallationVerified: true, codexCommandLeaseVerified: true,`}
@@ -181,6 +183,15 @@ export function installedCodexProbeSource(indexPath, consumerRoot, expectedVersi
`;
}
/** Installed bytes and consumer resolution must survive host protocol startup. */
export function assertInstalledNativeConsumerContinuity(before, after) {
for (const field of ['lockSha256', 'runnerSha256', 'codexCommandSha256']) {
if (!/^[a-f0-9]{64}$/.test(before[field]) || before[field] !== after[field]) {
throw new Error(`Installed native host startup changed ${field}`);
}
}
}
/** The installed consumer must select its packaged host daemon, never PATH. */
export function installedDaemonProbeSource(outputRoot, consumerRoot, sourceRevision) {
return `
+30 -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, 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, 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';
@@ -99,6 +99,7 @@ async function verifyMacConsumer(packageDirectory, sourceRevision, receiptPath)
async function verifyLinuxArmConsumer(packageDirectory, sourceRevision, receiptPath) {
assert.equal(process.env.GITHUB_ACTIONS, 'true', 'Native clean consumers run only on disposable hosted CI');
assert.equal(process.platform + '-' + process.arch, 'linux-arm64', 'ARM qualification requires a native ARM host');
assert.equal(process.versions.node.split('.')[0], '24', 'ARM host startup requires the qualified Node24 runtime');
assert.match(sourceRevision, /^[a-f0-9]{40}$/);
const packages = realpathSync(packageDirectory);
const manifest = JSON.parse(readFileSync(join(packages, 'manifest.json'), 'utf8'));
@@ -150,12 +151,38 @@ async function verifyLinuxArmConsumer(packageDirectory, sourceRevision, receiptP
// production imports still resolve from the installed consumer graph.
cpSync(join(repo, 'tests/runner-e2e/installed-cli.ts'), join(assets, 'installed-cli.ts'));
writeFileSync(join(assets, 'types.js'), stripTypeScriptTypes(readFileSync(join(repo, 'tests/runner-e2e/types.ts'), 'utf8')));
const startup = probe('startup-probe.mjs', macInstalledStartupProbeSource('/consumer', server, daemon.sha256, '/packages/installed-cli.ts'),
{ temporarySizeMiB: 2048, temporaryExecutable: true });
// The restricted install container deliberately forbids nested namespaces.
// Keep its lifecycle/no-network proof, then test the exact same bind-mounted
// graph on the native host using Codex's normal read-only namespace policy.
const home = join(root, 'host-home'), hostTmp = join(root, 'host-tmp');
for (const directory of [home, hostTmp]) mkdirSync(directory, { mode: 0o700 });
const hostEnv = { PATH: `${dirname(process.execPath)}:/usr/bin:/bin:/usr/sbin:/sbin`, HOME: home, TMPDIR: hostTmp, LC_ALL: 'C', TZ: 'UTC' };
const hostProbe = (name, source) => {
const file = join(assets, name); writeFileSync(file, source);
return JSON.parse(execFileSync(process.execPath, [file], { cwd: consumer, env: hostEnv,
timeout: 120_000, maxBuffer: 1024 * 1024, encoding: 'utf8' }).trim());
};
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 digest = value => createHash('sha256').update(value).digest('hex');
const before = { lockSha256: digest(lock), runnerSha256: hostDaemon().sha256.slice(7), codexCommandSha256: hostCodex().codexCommandSha256 };
assert.equal(before.runnerSha256, daemon.sha256.slice(7), 'Docker and host must select the same packaged daemon bytes');
assert.equal(before.codexCommandSha256, codex.codexCommandSha256, 'Docker and host must select the same official Codex command bytes');
const hostHeader = process.report.getReport().header;
assert.match(hostHeader.glibcVersionRuntime || '', /^\d+\.\d+$/, 'Native host startup must report its actual GNU libc ABI');
const startup = hostProbe('host-startup-probe.mjs', macInstalledStartupProbeSource(consumer, hostServer, daemon.sha256, join(assets, 'installed-cli.ts')));
const after = { lockSha256: digest(readFileSync(join(consumer, 'package-lock.json'))), runnerSha256: hostDaemon().sha256.slice(7), codexCommandSha256: hostCodex().codexCommandSha256 };
assertInstalledNativeConsumerContinuity(before, after);
assert.equal(startup.runnerSha256, before.runnerSha256);
assert.equal(startup.codexSha256, before.codexCommandSha256);
const receipt = { schema: 'paperclip.runner.linux-arm64-public-install.v1', sourceRevision, platform: process.platform, architecture: process.arch,
cleanNpmInstall: true, packageCount: manifest.archives.length, codexPackedPayloadsOmitted: true,
consumerImage: GROK_PUBLIC_INSTALL_IMAGE, consumerImagePlatform: platform,
consumerLockPreserved: true, lifecycleSentinelVerified: true, lifecycleNetwork: 'none',
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 };
writeFileSync(receiptPath, JSON.stringify(receipt, null, 2) + '\n');
console.log(JSON.stringify(receipt));