Files
goodbuddy/src/agent-daemon/runtime-bundle-verifier.test.ts
T
mesalogo 1698d9fd3e fix: keep valid older agent packages usable after desktop updates
Accept package-declared OpenCode, Node and koffi versions instead of desktop release pins. Preserve signature, hash, protocol and package-internal version consistency checks.

Validate both cached 0.11.18 packages and Linux x64 installation, bootstrap, health and stop. Related regression suite: 326 passed, 1 skipped; unrelated full-workspace check failures are recorded in the remote Host design.

Release note: Desktop updates no longer reject intact older Agent packages solely because their bundled runtime versions differ.
2026-09-16 19:04:54 +08:00

284 lines
8.2 KiB
TypeScript

import {
readFileSync,
rmSync,
writeFileSync
} from 'node:fs'
import { dirname, join } from 'node:path'
import { afterEach, describe, expect, it, vi } from 'vitest'
import {
type RemoteRuntimeBundleManifest
} from '../shared/remote-runtime-launch-contracts'
import {
verifyPublishedRuntimeBundle,
verifyRuntimeBundle
} from './runtime-bundle-verifier'
import {
RuntimeBundleRegistry,
createVerifiedRuntimeCapabilitySource
} from './runtime-bundle-registry'
import {
createRuntimeBundleTestFixture,
TEST_REMOTE_RUNTIME_LOCK
} from './runtime-bundle-test-fixture'
const temporaryPaths: string[] = []
const lock = TEST_REMOTE_RUNTIME_LOCK
afterEach(() => {
for (const path of temporaryPaths.splice(0)) {
rmSync(path, { recursive: true, force: true })
}
})
describe('Remote Runtime bundle verifier', () => {
it.each(['1.18.29', '1.18.9'])('verifies an authentic digest-addressed OpenCode %s bundle with a matching lock', async (runtimeVersion) => {
const fixture = await createBundle({ runtimeVersion })
await expect(
verifyRuntimeBundle(fixture.bundleDirectory, {
architecture: 'x64',
releaseKeyRegistry: fixture.registry,
runtimeLock: {
...lock,
runtimes: {
...lock.runtimes,
opencode: { ...lock.runtimes.opencode, version: runtimeVersion }
}
},
verificationEnvironment: 'test',
filesystemPlatform: 'win32'
})
).resolves.toMatchObject({
bundleDirectory: fixture.bundleDirectory,
executablePath: join(
fixture.bundleDirectory,
'bin',
'opencode'
),
manifest: fixture.manifest,
manifestDigest: expect.stringMatching(/^sha256:[a-f0-9]{64}$/u)
})
})
it('fails closed on test signing keys in production verification', async () => {
const fixture = await createBundle()
await expect(
verifyRuntimeBundle(fixture.bundleDirectory, {
architecture: 'x64',
releaseKeyRegistry: fixture.registry,
runtimeLock: lock,
filesystemPlatform: 'win32'
})
).rejects.toThrow(/production key registry is empty/iu)
})
it('rejects a forged signature and payload mutation', async () => {
const forged = await createBundle()
const signaturePath = join(
forged.bundleDirectory,
'manifest.sig'
)
const signature = Buffer.from(
readFileSync(signaturePath, 'utf8').trim(),
'base64'
)
signature[0] ^= 0xff
writeFileSync(
signaturePath,
`${signature.toString('base64')}\n`
)
await expect(
verifyRuntimeBundle(forged.bundleDirectory, {
architecture: 'x64',
releaseKeyRegistry: forged.registry,
runtimeLock: lock,
verificationEnvironment: 'test',
filesystemPlatform: 'win32'
})
).rejects.toThrow(/signature verification failed/iu)
const mutated = await createBundle()
writeFileSync(
join(mutated.bundleDirectory, 'licenses', 'opencode.txt'),
'mutated'
)
await expect(
verifyRuntimeBundle(mutated.bundleDirectory, {
architecture: 'x64',
releaseKeyRegistry: mutated.registry,
runtimeLock: lock,
verificationEnvironment: 'test',
filesystemPlatform: 'win32'
})
).rejects.toThrow(/payload (?:size|hash) mismatch/iu)
})
it.each([
{
runtimeVersion: '1.18.9'
},
{
sourcePackage: {
name: 'opencode-linux-x64',
integrity:
'sha512-VrvzV5Agrj0T2ZPvr5gzmh8xc4zqQ5pW8UeNgTzt5cJ/9Cbdxw6oFywgv7nfJqlpSfQqTWYJYH+LIHt3QdCS5g=='
}
}
])('rejects a validly signed bundle outside the locked profile: %j', async (overrides) => {
const fixture = await createBundle(overrides)
await expect(
verifyRuntimeBundle(fixture.bundleDirectory, {
architecture: 'x64',
releaseKeyRegistry: fixture.registry,
runtimeLock: lock,
verificationEnvironment: 'test',
filesystemPlatform: 'win32'
})
).rejects.toThrow(/locked OpenCode profile/iu)
})
it('rejects undeclared files and a mismatched digest directory', async () => {
const undeclared = await createBundle()
writeFileSync(
join(undeclared.bundleDirectory, 'unexpected.txt'),
'unexpected'
)
await expect(
verifyRuntimeBundle(undeclared.bundleDirectory, {
architecture: 'x64',
releaseKeyRegistry: undeclared.registry,
runtimeLock: lock,
verificationEnvironment: 'test',
filesystemPlatform: 'win32'
})
).rejects.toThrow(/undeclared or missing/iu)
const mismatched = await createBundle({
directoryDigest: `sha256:${'f'.repeat(64)}`
})
await expect(
verifyRuntimeBundle(mismatched.bundleDirectory, {
architecture: 'x64',
releaseKeyRegistry: mismatched.registry,
runtimeLock: lock,
verificationEnvironment: 'test',
filesystemPlatform: 'win32'
})
).rejects.toThrow(/managed directory/iu)
})
it('uses registered metadata without rereading Runtime payload files', async () => {
const fixture = await createBundle()
const runtimeRoot = dirname(dirname(fixture.bundleDirectory))
const registry = new RuntimeBundleRegistry({
runtimeRoot
})
const verified = await verifyRuntimeBundle(
fixture.bundleDirectory,
{
architecture: 'x64',
releaseKeyRegistry: fixture.registry,
runtimeLock: lock,
verificationEnvironment: 'test',
filesystemPlatform: 'win32'
}
)
new RuntimeBundleRegistry({ runtimeRoot }).register(verified)
const reportError = vi.fn()
const capabilities = createVerifiedRuntimeCapabilitySource({
registry,
architecture: 'x64',
releaseKeyRegistry: fixture.registry,
runtimeLock: lock,
verificationEnvironment: 'test',
filesystemPlatform: 'win32',
reportError
})
await expect(capabilities()).resolves.toEqual([
{
runtimeId: 'opencode',
version: '1.18.29',
bundleDigest: fixture.manifest.bundleDigest,
acpCapabilitiesDigest:
fixture.manifest.acpCapabilitiesDigest,
sessionLoad: true,
sessionResume: true
}
])
writeFileSync(
join(fixture.bundleDirectory, 'licenses', 'opencode.txt'),
'mutated'
)
await expect(capabilities()).resolves.toHaveLength(1)
expect(reportError).not.toHaveBeenCalled()
writeFileSync(
join(fixture.bundleDirectory, 'bin', 'opencode'),
Buffer.concat([
readFileSync(
join(fixture.bundleDirectory, 'bin', 'opencode')
),
Buffer.from('changed')
])
)
await expect(capabilities()).resolves.toHaveLength(1)
expect(reportError).not.toHaveBeenCalled()
})
it('adopts signed published metadata without rehashing unrelated payload', async () => {
const fixture = await createBundle()
const options = {
architecture: 'x64' as const,
releaseKeyRegistry: fixture.registry,
runtimeLock: lock,
verificationEnvironment: 'test' as const,
filesystemPlatform: 'win32' as const
}
writeFileSync(
join(fixture.bundleDirectory, 'licenses', 'opencode.txt'),
'changed after extraction'
)
await expect(
verifyPublishedRuntimeBundle(
fixture.bundleDirectory,
options
)
).resolves.toMatchObject({
manifest: { runtimeVersion: '1.18.29' }
})
writeFileSync(
join(fixture.bundleDirectory, 'bin', 'opencode'),
Buffer.concat([
readFileSync(
join(fixture.bundleDirectory, 'bin', 'opencode')
),
Buffer.from('changed')
])
)
await expect(
verifyPublishedRuntimeBundle(
fixture.bundleDirectory,
options
)
).rejects.toThrow('entrypoint metadata changed')
})
})
async function createBundle(
overrides: {
runtimeVersion?: RemoteRuntimeBundleManifest['runtimeVersion']
sourcePackage?: RemoteRuntimeBundleManifest['sourcePackage']
directoryDigest?: string
} = {}
): Promise<Awaited<
ReturnType<typeof createRuntimeBundleTestFixture>
>> {
const fixture = await createRuntimeBundleTestFixture(overrides)
temporaryPaths.push(fixture.root)
return fixture
}