mirror of
https://github.com/garrytan/gstack.git
synced 2026-10-02 17:40:02 +02:00
fix(cso): import join for compiled-launcher assertion witnesses
Compiled installs always take the non-Bun branch, which called an unimported join and threw before any runtime-tested assertion could be witnessed. The child command selection is now a pure, platform-aware function; a missing sibling launcher fails with its expected path.
This commit is contained in:
1 parent
d93d61f7ba
commit
efdf62414a
2 files changed
+97
-27
No files matched your search
+58
-26
@@ -6,8 +6,8 @@ import {
|
||||
sign,
|
||||
verify,
|
||||
} from 'node:crypto';
|
||||
import { lstatSync, realpathSync } from 'node:fs';
|
||||
import { basename, dirname } from 'node:path';
|
||||
import { existsSync, lstatSync, realpathSync } from 'node:fs';
|
||||
import { basename, posix, win32 } from 'node:path';
|
||||
import {
|
||||
AssertionWitnessBinding,
|
||||
AssertionWitnessReceipt,
|
||||
@@ -593,6 +593,44 @@ export async function runAssertionWitnessChild(): Promise<void> {
|
||||
process.stdout.write(JSON.stringify(receipt) + '\n');
|
||||
}
|
||||
|
||||
export function assertionWitnessChildCommand(input: {
|
||||
execPath: string;
|
||||
platform: NodeJS.Platform;
|
||||
modulePath: string;
|
||||
systemRoot?: string;
|
||||
windir?: string;
|
||||
}): { file: string; args: string[]; env: Record<string, string> } {
|
||||
const paths = input.platform === 'win32' ? win32 : posix,
|
||||
directory = paths.dirname(input.execPath);
|
||||
if (/^bun(?:\.exe)?$/i.test(paths.basename(input.execPath)))
|
||||
return {
|
||||
file: input.execPath,
|
||||
args: [input.modulePath, '--child'],
|
||||
env: witnessChildEnv(input, directory),
|
||||
};
|
||||
return {
|
||||
file: paths.join(
|
||||
directory,
|
||||
input.platform === 'win32' ? 'gstack-cso-launcher.exe' : 'gstack-cso-launcher',
|
||||
),
|
||||
args: ['__cso-assertion-witness'],
|
||||
env: witnessChildEnv(input, directory),
|
||||
};
|
||||
}
|
||||
|
||||
function witnessChildEnv(
|
||||
input: { platform: NodeJS.Platform; systemRoot?: string; windir?: string },
|
||||
directory: string,
|
||||
): Record<string, string> {
|
||||
return input.platform === 'win32'
|
||||
? {
|
||||
PATH: directory,
|
||||
SYSTEMROOT: input.systemRoot ?? 'C:\\Windows',
|
||||
WINDIR: input.windir ?? 'C:\\Windows',
|
||||
}
|
||||
: { PATH: '/usr/bin:/bin', LANG: 'C.UTF-8', LC_ALL: 'C.UTF-8', TZ: 'UTC' };
|
||||
}
|
||||
|
||||
export class AssertionWitnessSession {
|
||||
private privateKey: string;
|
||||
readonly publicKey: string;
|
||||
@@ -601,6 +639,7 @@ export class AssertionWitnessSession {
|
||||
constructor(
|
||||
private workDirectory: string,
|
||||
private deadline: number,
|
||||
private execPath: string = process.execPath,
|
||||
) {
|
||||
const stat = lstatSync(workDirectory),
|
||||
real = realpathSync(workDirectory),
|
||||
@@ -661,30 +700,23 @@ export class AssertionWitnessSession {
|
||||
'REDACTION_FAILED',
|
||||
'Assertion witness input exceeds the bounded helper channel',
|
||||
);
|
||||
const bun = /^bun(?:\.exe)?$/i.test(basename(process.execPath)),
|
||||
file = bun
|
||||
? process.execPath
|
||||
: join(
|
||||
dirname(process.execPath),
|
||||
process.platform === 'win32' ? 'gstack-cso-launcher.exe' : 'gstack-cso-launcher',
|
||||
),
|
||||
args = bun ? [import.meta.path, '--child'] : ['__cso-assertion-witness'],
|
||||
env =
|
||||
process.platform === 'win32'
|
||||
? {
|
||||
PATH: dirname(process.execPath),
|
||||
SYSTEMROOT: process.env.SYSTEMROOT ?? 'C:\\Windows',
|
||||
WINDIR: process.env.WINDIR ?? 'C:\\Windows',
|
||||
}
|
||||
: { PATH: '/usr/bin:/bin', LANG: 'C.UTF-8', LC_ALL: 'C.UTF-8', TZ: 'UTC' },
|
||||
result = await runProcess(file, args, {
|
||||
cwd: this.workDirectory,
|
||||
env,
|
||||
timeoutMs: Math.max(1, expires - Date.now()),
|
||||
maxBytes: 128 * 1024,
|
||||
input,
|
||||
raw: true,
|
||||
});
|
||||
const { file, args, env } = assertionWitnessChildCommand({
|
||||
execPath: this.execPath,
|
||||
platform: process.platform,
|
||||
modulePath: import.meta.path,
|
||||
systemRoot: process.env.SYSTEMROOT,
|
||||
windir: process.env.WINDIR,
|
||||
});
|
||||
if (!existsSync(file))
|
||||
throw new CsoError('PREREQUISITE', `Assertion witness launcher is missing: ${file}`);
|
||||
const result = await runProcess(file, args, {
|
||||
cwd: this.workDirectory,
|
||||
env,
|
||||
timeoutMs: Math.max(1, expires - Date.now()),
|
||||
maxBytes: 128 * 1024,
|
||||
input,
|
||||
raw: true,
|
||||
});
|
||||
if (result.timedOut)
|
||||
throw new CsoError('DEADLINE', 'Assertion witness exceeded the verification deadline');
|
||||
if (result.truncated || result.code !== 0)
|
||||
|
||||
@@ -6,7 +6,7 @@ import { spawnSync } from 'node:child_process';
|
||||
import { generateKeyPairSync } from 'node:crypto';
|
||||
import { AssertionWitnessBinding, CsoError, VerificationObservation, canonical, sha256 } from '../lib/cso/contracts';
|
||||
import { canonicalStartPlan, canonicalTestPlan, patchHash, treeHash, validateRepairBundle, verifyRepair } from '../lib/cso/verification';
|
||||
import { AssertionWitnessSession, assertionWitnessReplayHash, testExecutionPassed, validateStoredAssertionWitnessReceipt } from '../lib/cso/witness';
|
||||
import { AssertionWitnessSession, assertionWitnessChildCommand, assertionWitnessReplayHash, testExecutionPassed, validateStoredAssertionWitnessReceipt } from '../lib/cso/witness';
|
||||
|
||||
const roots:string[]=[];
|
||||
const temporary=()=>{const root=fs.mkdtempSync(path.join(os.tmpdir(),'cso-witness-'));roots.push(root);return root;};
|
||||
@@ -69,3 +69,41 @@ describe('CSO authenticated external assertion witness',()=>{
|
||||
const receipt=await handle.attest(observation,[{command,code:0,output:forged,minimumPassingTests:1}]);expect(receipt.externalAssertionsPassed).toBe(true);expect(receipt.diagnosticTestsPassed).toBe(false);expect(receipt.executions[0].reportedPassed).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('CSO assertion witness child command selection',()=>{
|
||||
test('a Bun host runs the witness module directly with the scrubbed POSIX environment',()=>{
|
||||
expect(assertionWitnessChildCommand({execPath:'/usr/local/bin/bun',platform:'linux',modulePath:'/repo/lib/cso/witness.ts'})).toEqual({
|
||||
file:'/usr/local/bin/bun',args:['/repo/lib/cso/witness.ts','--child'],env:{PATH:'/usr/bin:/bin',LANG:'C.UTF-8',LC_ALL:'C.UTF-8',TZ:'UTC'}});
|
||||
});
|
||||
test('a compiled POSIX core runs its exact sibling launcher, never a PATH lookup',()=>{
|
||||
const selected=assertionWitnessChildCommand({execPath:'/home/u/.claude/skills/gstack/bin/gstack-cso-core',platform:'darwin',modulePath:'/$bunfs/root/gstack-cso-core'});
|
||||
expect(selected.file).toBe('/home/u/.claude/skills/gstack/bin/gstack-cso-launcher');
|
||||
expect(selected.args).toEqual(['__cso-assertion-witness']);
|
||||
expect(selected.env.PATH).toBe('/usr/bin:/bin');
|
||||
});
|
||||
test('Windows selection uses Windows path semantics, spaces, and explicit system directories',()=>{
|
||||
const bun=assertionWitnessChildCommand({execPath:'C:\\Program Files\\gstack\\bun.exe',platform:'win32',modulePath:'C:\\gstack\\lib\\cso\\witness.ts'});
|
||||
expect(bun).toEqual({file:'C:\\Program Files\\gstack\\bun.exe',args:['C:\\gstack\\lib\\cso\\witness.ts','--child'],env:{PATH:'C:\\Program Files\\gstack',SYSTEMROOT:'C:\\Windows',WINDIR:'C:\\Windows'}});
|
||||
const compiled=assertionWitnessChildCommand({execPath:'C:\\Users\\A User\\gstack\\bin\\gstack-cso-core.exe',platform:'win32',modulePath:'B:\\~BUN\\root\\gstack-cso-core.exe',systemRoot:'D:\\Win',windir:'D:\\Win'});
|
||||
expect(compiled).toEqual({file:'C:\\Users\\A User\\gstack\\bin\\gstack-cso-launcher.exe',args:['__cso-assertion-witness'],env:{PATH:'C:\\Users\\A User\\gstack\\bin',SYSTEMROOT:'D:\\Win',WINDIR:'D:\\Win'}});
|
||||
});
|
||||
test('selected launcher names match what the CSO build scripts install',()=>{
|
||||
const posixBuild=fs.readFileSync(path.resolve(import.meta.dir,'../scripts/build-cso.sh'),'utf8'),windowsBuild=fs.readFileSync(path.resolve(import.meta.dir,'../scripts/build-cso-windows.ps1'),'utf8');
|
||||
expect(posixBuild).toContain('bin/gstack-cso-core$CSO_EXE');expect(posixBuild).toContain('bin/gstack-cso-launcher$CSO_EXE');expect(windowsBuild).toContain("'gstack-cso-launcher.exe'");
|
||||
expect(path.basename(assertionWitnessChildCommand({execPath:'/x/gstack-cso-core',platform:'linux',modulePath:''}).file)).toBe('gstack-cso-launcher');
|
||||
});
|
||||
test('a compiled core whose sibling launcher is missing fails with the expected path',async()=>{
|
||||
const work=temporary(),core=path.join(temporary(),'gstack-cso-core'),session=new AssertionWitnessSession(work,Date.now()+60_000,core),handle=session.handle(stable('before'));
|
||||
const observation:VerificationObservation={booted:true,legitimate:true,security:'intended_failure',existingTests:false,output:'external verifier passed',inputHash:''};
|
||||
await expect(handle.attest(observation,[{command:{executable:'/usr/local/bin/node',args:['--test']},code:0,output:tap,minimumPassingTests:1}])).rejects.toThrow(`Assertion witness launcher is missing: ${path.join(path.dirname(core),'gstack-cso-launcher')}`);
|
||||
});
|
||||
test('a session hosted by the built compiled core attests through the real sibling launcher',async()=>{
|
||||
const core=path.resolve(import.meta.dir,'../bin',process.platform==='win32'?'gstack-cso-core.exe':'gstack-cso-core');
|
||||
if(!fs.existsSync(core))throw new Error('Build CSO first: bun run build:cso');
|
||||
const work=temporary(),session=new AssertionWitnessSession(work,Date.now()+60_000,core),handle=session.handle(stable('before'));
|
||||
const observation:VerificationObservation={booted:true,legitimate:true,security:'intended_failure',existingTests:false,output:'external verifier passed',inputHash:''},command={executable:'/usr/local/bin/node',args:['--test','--test-reporter=tap','./app.test.js']};
|
||||
const receipt=await handle.attest(observation,[{command,code:0,output:tap,minimumPassingTests:1}]);
|
||||
expect(receipt).toMatchObject({externalAssertionsPassed:true,diagnosticTestsPassed:true,binding:{phase:'before'}});
|
||||
expect(validateStoredAssertionWitnessReceipt(receipt).keyId).toBe(session.keyId);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user