build(ssh): validate native signing payloads

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Jinwoo-H
2026-07-14 21:51:45 -04:00
co-authored by Orca
parent 30496bacf1
commit c847c4a11d
6 changed files with 951 additions and 0 deletions
@@ -127,6 +127,10 @@ jobs:
node --check config/scripts/ssh-relay-runtime-toolchain.test.mjs
node --check config/scripts/ssh-relay-runtime-native-signing-plan.mjs
node --check config/scripts/ssh-relay-runtime-native-signing-plan.test.mjs
node --check config/scripts/ssh-relay-runtime-native-signing-selection.mjs
node --check config/scripts/ssh-relay-runtime-native-signing-selection.test.mjs
node --check config/scripts/ssh-relay-runtime-native-signing-payload.mjs
node --check config/scripts/ssh-relay-runtime-native-signing-payload.test.mjs
pnpm exec vitest run --config config/vitest.config.ts \
config/scripts/ssh-relay-node-release-verification.test.mjs \
config/scripts/ssh-relay-node-tar-inspection.test.mjs \
@@ -140,6 +144,8 @@ jobs:
config/scripts/ssh-relay-runtime-build.test.mjs \
config/scripts/ssh-relay-runtime-linux-build-evidence.test.mjs \
config/scripts/ssh-relay-runtime-native-signing-plan.test.mjs \
config/scripts/ssh-relay-runtime-native-signing-selection.test.mjs \
config/scripts/ssh-relay-runtime-native-signing-payload.test.mjs \
config/scripts/ssh-relay-runtime-closure.test.mjs \
config/scripts/ssh-relay-runtime-pty-smoke.test.mjs \
config/scripts/ssh-relay-runtime-reproducibility.test.mjs \
@@ -386,6 +392,14 @@ jobs:
if ($LASTEXITCODE -ne 0) { throw 'runtime native signing plan script syntax check failed' }
node --check config/scripts/ssh-relay-runtime-native-signing-plan.test.mjs
if ($LASTEXITCODE -ne 0) { throw 'runtime native signing plan test syntax check failed' }
node --check config/scripts/ssh-relay-runtime-native-signing-selection.mjs
if ($LASTEXITCODE -ne 0) { throw 'runtime native signing selection script syntax check failed' }
node --check config/scripts/ssh-relay-runtime-native-signing-selection.test.mjs
if ($LASTEXITCODE -ne 0) { throw 'runtime native signing selection test syntax check failed' }
node --check config/scripts/ssh-relay-runtime-native-signing-payload.mjs
if ($LASTEXITCODE -ne 0) { throw 'runtime native signing payload script syntax check failed' }
node --check config/scripts/ssh-relay-runtime-native-signing-payload.test.mjs
if ($LASTEXITCODE -ne 0) { throw 'runtime native signing payload test syntax check failed' }
pnpm exec vitest run --config config/vitest.config.ts `
config/scripts/ssh-relay-node-release-verification.test.mjs `
config/scripts/ssh-relay-node-tar-inspection.test.mjs `
@@ -399,6 +413,8 @@ jobs:
config/scripts/ssh-relay-runtime-build.test.mjs `
config/scripts/ssh-relay-runtime-linux-build-evidence.test.mjs `
config/scripts/ssh-relay-runtime-native-signing-plan.test.mjs `
config/scripts/ssh-relay-runtime-native-signing-selection.test.mjs `
config/scripts/ssh-relay-runtime-native-signing-payload.test.mjs `
config/scripts/ssh-relay-runtime-closure.test.mjs `
config/scripts/ssh-relay-runtime-pty-smoke.test.mjs `
config/scripts/ssh-relay-runtime-reproducibility.test.mjs `
@@ -0,0 +1,240 @@
import { createHash } from 'node:crypto'
import { constants, createReadStream } from 'node:fs'
import { copyFile, lstat, mkdir, readdir, realpath, rm } from 'node:fs/promises'
import { basename, dirname, isAbsolute, relative, resolve } from 'node:path'
const MAX_SIGNED_FILE_GROWTH_BYTES = 4 * 1024 * 1024
const MAX_RETURNED_PAYLOAD_BYTES = 64 * 1024 * 1024
function localPath(root, portablePath) {
if (
typeof portablePath !== 'string' ||
portablePath.length === 0 ||
portablePath.includes('\\') ||
portablePath.startsWith('/') ||
portablePath.split('/').some((segment) => !segment || segment === '.' || segment === '..')
) {
throw new Error(`Runtime native signing rejects non-portable path: ${portablePath}`)
}
return resolve(root, ...portablePath.split('/'))
}
async function sha256File(path) {
const hash = createHash('sha256')
for await (const chunk of createReadStream(path)) {
hash.update(chunk)
}
return `sha256:${hash.digest('hex')}`
}
async function assertRegularFile(path, label) {
const metadata = await lstat(path)
if (metadata.isSymbolicLink()) {
throw new Error(`Runtime native signing ${label} is a symbolic link: ${path}`)
}
if (!metadata.isFile()) {
throw new Error(`Runtime native signing ${label} is not a regular file: ${path}`)
}
return metadata
}
async function verifySourceFile(runtimeRoot, entry) {
const path = localPath(runtimeRoot, entry.path)
const metadata = await assertRegularFile(path, 'source')
if (metadata.size !== entry.sourceSize) {
throw new Error(`Runtime native signing source has wrong authenticated size: ${entry.path}`)
}
if ((await sha256File(path)) !== entry.sourceSha256) {
throw new Error(`Runtime native signing source has wrong authenticated hash: ${entry.path}`)
}
}
function assertExclusiveStageLocation(runtimeRoot, stagingRoot) {
const relativeStage = relative(runtimeRoot, stagingRoot)
if (relativeStage === '' || (!relativeStage.startsWith('..') && !isAbsolute(relativeStage))) {
throw new Error('Runtime native signing staging root must be outside the runtime')
}
}
async function resolveSigningRoots(runtimeRoot, stagingRoot) {
const runtimeMetadata = await lstat(runtimeRoot)
if (runtimeMetadata.isSymbolicLink() || !runtimeMetadata.isDirectory()) {
throw new Error('Runtime native signing source root must be a real directory')
}
const physicalRuntimeRoot = await realpath(runtimeRoot)
const physicalStagingParent = await realpath(dirname(stagingRoot))
const physicalStagingRoot = resolve(physicalStagingParent, basename(stagingRoot))
assertExclusiveStageLocation(physicalRuntimeRoot, physicalStagingRoot)
return { physicalRuntimeRoot, physicalStagingRoot }
}
async function assertPathAbsent(path) {
try {
await lstat(path)
} catch (error) {
if (error.code === 'ENOENT') {
return
}
throw error
}
throw new Error('Runtime native signing requires an exclusive staging root')
}
async function copySigningFile(runtimeRoot, stagingRoot, entry) {
const source = localPath(runtimeRoot, entry.path)
const destination = localPath(stagingRoot, entry.path)
await mkdir(dirname(destination), { recursive: true })
await copyFile(source, destination, constants.COPYFILE_EXCL)
const metadata = await assertRegularFile(destination, 'staged file')
if (
metadata.size !== entry.sourceSize ||
(await sha256File(destination)) !== entry.sourceSha256
) {
throw new Error(`Runtime native signing staged bytes changed during copy: ${entry.path}`)
}
return { path: entry.path, sourceSha256: entry.sourceSha256, sourceSize: entry.sourceSize }
}
export async function stageSshRelayRuntimeNativeSigningPayload({
runtimeRoot,
stagingRoot,
selection
}) {
const absoluteRuntimeRoot = resolve(runtimeRoot)
const absoluteStagingRoot = resolve(stagingRoot)
const { physicalRuntimeRoot, physicalStagingRoot } = await resolveSigningRoots(
absoluteRuntimeRoot,
absoluteStagingRoot
)
await assertPathAbsent(physicalStagingRoot)
await Promise.all(
selection.verificationFiles.map((entry) => verifySourceFile(physicalRuntimeRoot, entry))
)
if (selection.signingFiles.length === 0) {
return { tupleId: selection.tupleId, stagingRequired: false, stagedFiles: [], stagedSize: 0 }
}
await mkdir(physicalStagingRoot)
try {
const stagedFiles = []
for (const entry of selection.signingFiles) {
stagedFiles.push(await copySigningFile(physicalRuntimeRoot, physicalStagingRoot, entry))
}
return {
tupleId: selection.tupleId,
stagingRequired: true,
stagedFiles,
stagedSize: stagedFiles.reduce((total, entry) => total + entry.sourceSize, 0)
}
} catch (error) {
// Why: a partial signing payload must never be mistaken for a complete retryable input.
await rm(physicalStagingRoot, { recursive: true, force: true })
throw error
}
}
function expectedDirectories(files) {
const directories = new Set()
for (const file of files) {
const segments = file.path.split('/')
for (let depth = 1; depth < segments.length; depth += 1) {
directories.add(segments.slice(0, depth).join('/'))
}
}
return directories
}
async function walkReturnedTree(
root,
segments = [],
result = { directories: new Set(), files: new Map() }
) {
for (const directoryEntry of await readdir(root, { withFileTypes: true })) {
const childSegments = [...segments, directoryEntry.name]
const portablePath = childSegments.join('/')
const path = resolve(root, directoryEntry.name)
const metadata = await lstat(path)
if (metadata.isSymbolicLink()) {
throw new Error(
`Runtime native signing returned tree contains a symbolic link: ${portablePath}`
)
}
if (metadata.isDirectory()) {
result.directories.add(portablePath)
await walkReturnedTree(path, childSegments, result)
} else if (metadata.isFile()) {
result.files.set(portablePath, { path, metadata })
} else {
throw new Error(
`Runtime native signing returned tree contains a special entry: ${portablePath}`
)
}
}
return result
}
function assertExactReturnedClosure(actual, expectedFiles) {
const expectedFilePaths = new Set(expectedFiles.map((entry) => entry.path))
for (const entry of expectedFiles) {
if (!actual.files.has(entry.path)) {
throw new Error(`Runtime native signing is missing returned file: ${entry.path}`)
}
}
for (const path of actual.files.keys()) {
if (!expectedFilePaths.has(path)) {
throw new Error(`Runtime native signing has unexpected returned file: ${path}`)
}
}
const directories = expectedDirectories(expectedFiles)
for (const path of actual.directories) {
if (!directories.has(path)) {
throw new Error(`Runtime native signing has unexpected returned directory: ${path}`)
}
}
for (const path of directories) {
if (!actual.directories.has(path)) {
throw new Error(`Runtime native signing is missing returned directory: ${path}`)
}
}
}
export async function verifySshRelayRuntimeNativeSigningReturn({ returnedRoot, selection }) {
if (selection.signingFiles.length === 0) {
throw new Error('Runtime native signing selection has no returned payload')
}
const absoluteReturnedRoot = resolve(returnedRoot)
const rootMetadata = await lstat(absoluteReturnedRoot)
if (rootMetadata.isSymbolicLink() || !rootMetadata.isDirectory()) {
throw new Error('Runtime native signing returned root must be a real directory')
}
const actual = await walkReturnedTree(absoluteReturnedRoot)
assertExactReturnedClosure(actual, selection.signingFiles)
let returnedSize = 0
const returnedFiles = []
for (const entry of selection.signingFiles) {
const returned = actual.files.get(entry.path)
if (
returned.metadata.size <= 0 ||
returned.metadata.size > entry.sourceSize + MAX_SIGNED_FILE_GROWTH_BYTES
) {
throw new Error(`Runtime native signing returned file exceeds size bound: ${entry.path}`)
}
returnedSize += returned.metadata.size
if (returnedSize > MAX_RETURNED_PAYLOAD_BYTES) {
throw new Error('Runtime native signing returned payload exceeds total size bound')
}
const signedSha256 = await sha256File(returned.path)
if (signedSha256 === entry.sourceSha256) {
throw new Error(`Runtime native signing returned file did not change: ${entry.path}`)
}
returnedFiles.push({
path: entry.path,
role: entry.role,
sourceSha256: entry.sourceSha256,
signedSha256,
signedSize: returned.metadata.size
})
}
return { tupleId: selection.tupleId, returnedFiles, returnedSize }
}
@@ -0,0 +1,360 @@
import { createHash } from 'node:crypto'
import {
appendFile,
cp,
lstat,
mkdir,
mkdtemp,
readFile,
rm,
symlink,
truncate,
writeFile
} from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { dirname, join } from 'node:path'
import { describe, expect, it } from 'vitest'
import { expectedSshRelayRuntimeClosureEntries } from './ssh-relay-runtime-closure.mjs'
import {
stageSshRelayRuntimeNativeSigningPayload,
verifySshRelayRuntimeNativeSigningReturn
} from './ssh-relay-runtime-native-signing-payload.mjs'
import { buildSshRelayRuntimeNativeSigningSelection } from './ssh-relay-runtime-native-signing-selection.mjs'
function digest(bytes) {
return `sha256:${createHash('sha256').update(bytes).digest('hex')}`
}
function platformForTuple(tupleId) {
return tupleId.startsWith('linux-') ? 'linux' : tupleId.startsWith('darwin-') ? 'darwin' : 'win32'
}
async function runtimeFixture(tupleId) {
const root = await mkdtemp(join(tmpdir(), 'ssh-relay-native-signing-payload-'))
const runtimeRoot = join(root, 'runtime')
await mkdir(runtimeRoot)
const entries = []
for (const entry of expectedSshRelayRuntimeClosureEntries(tupleId)) {
if (entry.type === 'directory') {
await mkdir(join(runtimeRoot, ...entry.path.split('/')), { recursive: true })
entries.push(entry)
continue
}
const bytes = Buffer.from(`fixture:${tupleId}:${entry.path}`)
const filePath = join(runtimeRoot, ...entry.path.split('/'))
await mkdir(dirname(filePath), { recursive: true })
await writeFile(filePath, bytes, { mode: entry.mode })
entries.push({ ...entry, size: bytes.length, sha256: digest(bytes) })
}
return {
root,
runtimeRoot,
identity: {
tupleId,
os: platformForTuple(tupleId),
nodeVersion: '24.18.0',
dependencies: { nodePtyVersion: '1.1.0', parcelWatcherVersion: '2.5.6' },
entries
}
}
}
function windowsAssessments(identity, preservedPath) {
const nativeEntries = identity.entries.filter(
(entry) =>
entry.type === 'file' &&
['node-pty-native', 'parcel-watcher-native', 'native-runtime'].includes(entry.role)
)
return nativeEntries.map((entry) =>
entry.path === preservedPath
? {
path: entry.path,
sourceSha256: entry.sha256,
status: 'valid-upstream',
signerSubject: 'CN=Microsoft Corporation',
signerThumbprint: 'B'.repeat(40)
}
: { path: entry.path, sourceSha256: entry.sha256, status: 'unsigned' }
)
}
async function createReturnedTree(stagingRoot, returnedRoot, selection) {
await cp(stagingRoot, returnedRoot, { recursive: true, errorOnExist: true, force: false })
for (const entry of selection.signingFiles) {
await appendFile(join(returnedRoot, ...entry.path.split('/')), ':signed')
}
}
describe('SSH relay runtime native signing payload', () => {
it('authenticates Linux native bytes without creating a signing payload', async () => {
const fixture = await runtimeFixture('linux-x64-glibc')
try {
const selection = buildSshRelayRuntimeNativeSigningSelection(fixture.identity, [])
const stagingRoot = join(fixture.root, 'stage')
await expect(
stageSshRelayRuntimeNativeSigningPayload({
runtimeRoot: fixture.runtimeRoot,
stagingRoot,
selection
})
).resolves.toEqual({
tupleId: 'linux-x64-glibc',
stagingRequired: false,
stagedFiles: [],
stagedSize: 0
})
await expect(lstat(stagingRoot)).rejects.toMatchObject({ code: 'ENOENT' })
} finally {
await rm(fixture.root, { recursive: true, force: true })
}
})
it('verifies all native bytes before exclusively staging only macOS signing files', async () => {
const fixture = await runtimeFixture('darwin-arm64')
try {
const selection = buildSshRelayRuntimeNativeSigningSelection(fixture.identity, [])
const stagingRoot = join(fixture.root, 'stage')
const report = await stageSshRelayRuntimeNativeSigningPayload({
runtimeRoot: fixture.runtimeRoot,
stagingRoot,
selection
})
expect(report.stagedFiles.map((entry) => entry.path)).toEqual(
selection.signingFiles.map((entry) => entry.path)
)
await expect(lstat(join(stagingRoot, 'bin', 'node'))).rejects.toMatchObject({
code: 'ENOENT'
})
for (const entry of selection.signingFiles) {
await expect(readFile(join(stagingRoot, ...entry.path.split('/')))).resolves.toEqual(
await readFile(join(fixture.runtimeRoot, ...entry.path.split('/')))
)
}
} finally {
await rm(fixture.root, { recursive: true, force: true })
}
})
it('does not stage a Windows file whose valid upstream signature is preserved', async () => {
const fixture = await runtimeFixture('win32-x64')
try {
const preservedPath = 'node_modules/node-pty/build/Release/conpty/OpenConsole.exe'
const selection = buildSshRelayRuntimeNativeSigningSelection(
fixture.identity,
windowsAssessments(fixture.identity, preservedPath)
)
const stagingRoot = join(fixture.root, 'stage')
const report = await stageSshRelayRuntimeNativeSigningPayload({
runtimeRoot: fixture.runtimeRoot,
stagingRoot,
selection
})
expect(report.stagedFiles).toHaveLength(4)
await expect(lstat(join(stagingRoot, ...preservedPath.split('/')))).rejects.toMatchObject({
code: 'ENOENT'
})
} finally {
await rm(fixture.root, { recursive: true, force: true })
}
})
it('rejects source mutation before creating a stage', async () => {
const fixture = await runtimeFixture('darwin-x64')
try {
const selection = buildSshRelayRuntimeNativeSigningSelection(fixture.identity, [])
const stagingRoot = join(fixture.root, 'stage')
await appendFile(join(fixture.runtimeRoot, 'bin', 'node'), ':mutated')
await expect(
stageSshRelayRuntimeNativeSigningPayload({
runtimeRoot: fixture.runtimeRoot,
stagingRoot,
selection
})
).rejects.toThrow(/authenticated size/i)
await expect(lstat(stagingRoot)).rejects.toMatchObject({ code: 'ENOENT' })
} finally {
await rm(fixture.root, { recursive: true, force: true })
}
})
it('rejects an existing or source-nested staging root', async () => {
const fixture = await runtimeFixture('darwin-arm64')
try {
const selection = buildSshRelayRuntimeNativeSigningSelection(fixture.identity, [])
const existing = join(fixture.root, 'existing')
await mkdir(existing)
await expect(
stageSshRelayRuntimeNativeSigningPayload({
runtimeRoot: fixture.runtimeRoot,
stagingRoot: existing,
selection
})
).rejects.toThrow(/exclusive/i)
await expect(
stageSshRelayRuntimeNativeSigningPayload({
runtimeRoot: fixture.runtimeRoot,
stagingRoot: join(fixture.runtimeRoot, 'nested-stage'),
selection
})
).rejects.toThrow(/outside the runtime/i)
} finally {
await rm(fixture.root, { recursive: true, force: true })
}
})
it('removes a partial stage when authenticated signing-file copy fails', async () => {
const fixture = await runtimeFixture('darwin-arm64')
try {
const selection = buildSshRelayRuntimeNativeSigningSelection(fixture.identity, [])
selection.signingFiles[1].sourceSha256 = `sha256:${'f'.repeat(64)}`
const stagingRoot = join(fixture.root, 'stage')
await expect(
stageSshRelayRuntimeNativeSigningPayload({
runtimeRoot: fixture.runtimeRoot,
stagingRoot,
selection
})
).rejects.toThrow(/changed during copy/i)
await expect(lstat(stagingRoot)).rejects.toMatchObject({ code: 'ENOENT' })
} finally {
await rm(fixture.root, { recursive: true, force: true })
}
})
it('accepts an exact returned tree only after every staged hash changes', async () => {
const fixture = await runtimeFixture('darwin-arm64')
try {
const selection = buildSshRelayRuntimeNativeSigningSelection(fixture.identity, [])
const stagingRoot = join(fixture.root, 'stage')
const returnedRoot = join(fixture.root, 'returned')
await stageSshRelayRuntimeNativeSigningPayload({
runtimeRoot: fixture.runtimeRoot,
stagingRoot,
selection
})
await createReturnedTree(stagingRoot, returnedRoot, selection)
const report = await verifySshRelayRuntimeNativeSigningReturn({ returnedRoot, selection })
expect(report.returnedFiles).toHaveLength(3)
expect(report.returnedFiles.every((entry) => entry.signedSha256 !== entry.sourceSha256)).toBe(
true
)
} finally {
await rm(fixture.root, { recursive: true, force: true })
}
})
it('rejects unchanged, missing, extra, and oversized returned bytes', async () => {
const fixture = await runtimeFixture('darwin-x64')
try {
const selection = buildSshRelayRuntimeNativeSigningSelection(fixture.identity, [])
const stagingRoot = join(fixture.root, 'stage')
await stageSshRelayRuntimeNativeSigningPayload({
runtimeRoot: fixture.runtimeRoot,
stagingRoot,
selection
})
const unchangedRoot = join(fixture.root, 'unchanged')
await cp(stagingRoot, unchangedRoot, { recursive: true })
await expect(
verifySshRelayRuntimeNativeSigningReturn({ returnedRoot: unchangedRoot, selection })
).rejects.toThrow(/did not change/i)
const missingRoot = join(fixture.root, 'missing')
await createReturnedTree(stagingRoot, missingRoot, selection)
await rm(join(missingRoot, ...selection.signingFiles[0].path.split('/')))
await expect(
verifySshRelayRuntimeNativeSigningReturn({ returnedRoot: missingRoot, selection })
).rejects.toThrow(/missing returned file/i)
const extraRoot = join(fixture.root, 'extra')
await createReturnedTree(stagingRoot, extraRoot, selection)
await writeFile(join(extraRoot, 'extra.node'), 'extra')
await expect(
verifySshRelayRuntimeNativeSigningReturn({ returnedRoot: extraRoot, selection })
).rejects.toThrow(/unexpected returned file/i)
const oversizedRoot = join(fixture.root, 'oversized')
await createReturnedTree(stagingRoot, oversizedRoot, selection)
const first = selection.signingFiles[0]
await truncate(
join(oversizedRoot, ...first.path.split('/')),
first.sourceSize + 4 * 1024 * 1024 + 1
)
await expect(
verifySshRelayRuntimeNativeSigningReturn({ returnedRoot: oversizedRoot, selection })
).rejects.toThrow(/size bound/i)
} finally {
await rm(fixture.root, { recursive: true, force: true })
}
})
it.skipIf(process.platform === 'win32')(
'rejects a symlink in returned signing bytes',
async () => {
const fixture = await runtimeFixture('darwin-arm64')
try {
const selection = buildSshRelayRuntimeNativeSigningSelection(fixture.identity, [])
const returnedRoot = join(fixture.root, 'returned')
const target = join(fixture.root, 'target')
const first = selection.signingFiles[0]
await writeFile(target, 'signed')
await mkdir(dirname(join(returnedRoot, ...first.path.split('/'))), { recursive: true })
await symlink(target, join(returnedRoot, ...first.path.split('/')))
await expect(
verifySshRelayRuntimeNativeSigningReturn({ returnedRoot, selection })
).rejects.toThrow(/symbolic link/i)
} finally {
await rm(fixture.root, { recursive: true, force: true })
}
}
)
it.skipIf(process.platform === 'win32')('rejects a symlinked native source file', async () => {
const fixture = await runtimeFixture('darwin-x64')
try {
const selection = buildSshRelayRuntimeNativeSigningSelection(fixture.identity, [])
const first = selection.signingFiles[0]
const sourcePath = join(fixture.runtimeRoot, ...first.path.split('/'))
const target = join(fixture.root, 'native-target')
await cp(sourcePath, target)
await rm(sourcePath)
await symlink(target, sourcePath)
await expect(
stageSshRelayRuntimeNativeSigningPayload({
runtimeRoot: fixture.runtimeRoot,
stagingRoot: join(fixture.root, 'stage'),
selection
})
).rejects.toThrow(/source is a symbolic link/i)
} finally {
await rm(fixture.root, { recursive: true, force: true })
}
})
it.skipIf(process.platform === 'win32')(
'rejects a symlinked staging parent that resolves inside the runtime',
async () => {
const fixture = await runtimeFixture('darwin-arm64')
try {
const selection = buildSshRelayRuntimeNativeSigningSelection(fixture.identity, [])
const redirectedParent = join(fixture.root, 'redirected-parent')
await symlink(fixture.runtimeRoot, redirectedParent)
await expect(
stageSshRelayRuntimeNativeSigningPayload({
runtimeRoot: fixture.runtimeRoot,
stagingRoot: join(redirectedParent, 'stage'),
selection
})
).rejects.toThrow(/outside the runtime/i)
} finally {
await rm(fixture.root, { recursive: true, force: true })
}
}
)
})
@@ -0,0 +1,160 @@
import { buildSshRelayRuntimeNativeSigningPlan } from './ssh-relay-runtime-native-signing-plan.mjs'
const SHA256_PATTERN = /^sha256:[0-9a-f]{64}$/
const WINDOWS_UNSIGNED_FIELDS = ['path', 'sourceSha256', 'status']
const WINDOWS_VALID_FIELDS = ['path', 'signerSubject', 'signerThumbprint', 'sourceSha256', 'status']
function assertExactFields(value, expected, label) {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw new Error(`Runtime native signing ${label} must be an object`)
}
const actual = Object.keys(value).sort()
if (
actual.length !== expected.length ||
actual.some((field, index) => field !== expected[index])
) {
throw new Error(`Runtime native signing ${label} has unexpected fields`)
}
}
function authenticatedFile(identityEntries, planEntry) {
const entry = identityEntries.get(planEntry.path)
if (!entry || entry.type !== 'file') {
throw new Error(`Runtime native signing identity is missing file: ${planEntry.path}`)
}
if (!Number.isSafeInteger(entry.size) || entry.size <= 0) {
throw new Error(`Runtime native signing identity has invalid size: ${planEntry.path}`)
}
if (!SHA256_PATTERN.test(entry.sha256)) {
throw new Error(`Runtime native signing identity has invalid digest: ${planEntry.path}`)
}
if (entry.sha256 !== planEntry.sourceSha256 || entry.role !== planEntry.role) {
throw new Error(`Runtime native signing identity disagrees with plan: ${planEntry.path}`)
}
return {
path: entry.path,
role: entry.role,
sourceSha256: entry.sha256,
sourceSize: entry.size
}
}
function indexIdentityFiles(identity) {
const entries = new Map()
for (const entry of identity.entries) {
if (entry.type === 'file') {
entries.set(entry.path, entry)
}
}
return entries
}
function containsAsciiControl(value) {
return [...value].some((character) => {
const codePoint = character.codePointAt(0)
return codePoint <= 0x1f || codePoint === 0x7f
})
}
function assertSignerIdentity(assessment) {
if (
typeof assessment.signerSubject !== 'string' ||
assessment.signerSubject.length === 0 ||
assessment.signerSubject.length > 1024 ||
containsAsciiControl(assessment.signerSubject)
) {
throw new Error(
`Runtime native signing assessment has invalid signer subject: ${assessment.path}`
)
}
if (!/^[0-9a-f]{40}$/i.test(assessment.signerThumbprint)) {
throw new Error(
`Runtime native signing assessment has invalid signer thumbprint: ${assessment.path}`
)
}
}
function windowsSelection(plan, identityEntries, assessments) {
if (!Array.isArray(assessments)) {
throw new Error('Runtime native signing Windows assessments must be an array')
}
const candidates = new Map(plan.signingCandidates.map((entry) => [entry.path, entry]))
const indexed = new Map()
for (const assessment of assessments) {
if (!assessment || typeof assessment.path !== 'string' || !candidates.has(assessment.path)) {
throw new Error(`Runtime native signing has unexpected assessment: ${assessment?.path}`)
}
if (indexed.has(assessment.path)) {
throw new Error(`Runtime native signing has duplicate assessment: ${assessment.path}`)
}
if (!['unsigned', 'valid-upstream'].includes(assessment.status)) {
throw new Error(`Runtime native signing rejects signature status: ${assessment.status}`)
}
assertExactFields(
assessment,
assessment.status === 'unsigned' ? WINDOWS_UNSIGNED_FIELDS : WINDOWS_VALID_FIELDS,
'assessment'
)
const candidate = candidates.get(assessment.path)
if (assessment.sourceSha256 !== candidate.sourceSha256) {
throw new Error(`Runtime native signing assessment has wrong source hash: ${assessment.path}`)
}
if (assessment.status === 'valid-upstream') {
assertSignerIdentity(assessment)
}
indexed.set(assessment.path, assessment)
}
return plan.signingCandidates.map((candidate) => {
const assessment = indexed.get(candidate.path)
if (!assessment) {
throw new Error(`Runtime native signing is missing assessment: ${candidate.path}`)
}
const file = authenticatedFile(identityEntries, candidate)
return assessment.status === 'unsigned'
? { ...file, action: 'signpath-required' }
: {
...file,
action: 'preserve-valid-upstream',
signerSubject: assessment.signerSubject,
signerThumbprint: assessment.signerThumbprint.toUpperCase()
}
})
}
export function buildSshRelayRuntimeNativeSigningSelection(identity, assessments) {
const plan = buildSshRelayRuntimeNativeSigningPlan(identity)
const identityEntries = indexIdentityFiles(identity)
const immutableVendorFiles = plan.immutableVendorFiles.map((entry) => ({
...authenticatedFile(identityEntries, entry),
action: entry.action
}))
const verificationFiles = plan.verificationFiles.map((entry) =>
authenticatedFile(identityEntries, entry)
)
let candidateFiles
if (plan.platform === 'win32') {
candidateFiles = windowsSelection(plan, identityEntries, assessments)
} else {
if (!Array.isArray(assessments) || assessments.length !== 0) {
throw new Error(`Runtime native signing ${plan.platform} does not accept assessments`)
}
candidateFiles = plan.signingCandidates.map((entry) => ({
...authenticatedFile(identityEntries, entry),
action: entry.action
}))
}
return {
tupleId: plan.tupleId,
platform: plan.platform,
policy: plan.policy,
immutableVendorFiles,
signingFiles: candidateFiles.filter((entry) => entry.action !== 'preserve-valid-upstream'),
preservedUpstreamFiles: candidateFiles.filter(
(entry) => entry.action === 'preserve-valid-upstream'
),
verificationFiles
}
}
@@ -0,0 +1,167 @@
import { describe, expect, it } from 'vitest'
import { expectedSshRelayRuntimeClosureEntries } from './ssh-relay-runtime-closure.mjs'
import { buildSshRelayRuntimeNativeSigningPlan } from './ssh-relay-runtime-native-signing-plan.mjs'
import { buildSshRelayRuntimeNativeSigningSelection } from './ssh-relay-runtime-native-signing-selection.mjs'
const DIGEST = `sha256:${'a'.repeat(64)}`
function identityFor(tupleId) {
const os = tupleId.startsWith('linux-')
? 'linux'
: tupleId.startsWith('darwin-')
? 'darwin'
: 'win32'
return {
tupleId,
os,
nodeVersion: '24.18.0',
dependencies: { nodePtyVersion: '1.1.0', parcelWatcherVersion: '2.5.6' },
entries: expectedSshRelayRuntimeClosureEntries(tupleId).map((entry) =>
entry.type === 'file' ? { ...entry, size: 100, sha256: DIGEST } : entry
)
}
}
function windowsAssessments(identity, preservedPath) {
return buildSshRelayRuntimeNativeSigningPlan(identity).signingCandidates.map((entry) =>
entry.path === preservedPath
? {
path: entry.path,
sourceSha256: entry.sourceSha256,
status: 'valid-upstream',
signerSubject: 'CN=Microsoft Corporation',
signerThumbprint: 'A'.repeat(40)
}
: { path: entry.path, sourceSha256: entry.sourceSha256, status: 'unsigned' }
)
}
describe('SSH relay runtime native signing selection', () => {
it('keeps Linux hash-only and official Node verification exact', () => {
const selection = buildSshRelayRuntimeNativeSigningSelection(identityFor('linux-x64-glibc'), [])
expect(selection.signingFiles).toEqual([])
expect(selection.immutableVendorFiles).toEqual([
expect.objectContaining({ path: 'bin/node', action: 'preserve-exact-bytes', sourceSize: 100 })
])
expect(selection.verificationFiles).toHaveLength(3)
})
it('requires every macOS candidate and ignores no assessment input', () => {
const identity = identityFor('darwin-arm64')
const selection = buildSshRelayRuntimeNativeSigningSelection(identity, [])
expect(selection.signingFiles.map((entry) => [entry.path, entry.action])).toEqual([
['node_modules/@parcel/watcher-darwin-arm64/watcher.node', 'developer-id-required'],
['node_modules/node-pty/build/Release/pty.node', 'developer-id-required'],
['node_modules/node-pty/build/Release/spawn-helper', 'developer-id-required']
])
expect(() =>
buildSshRelayRuntimeNativeSigningSelection(identity, [
{ path: selection.signingFiles[0].path, status: 'unsigned', sourceSha256: DIGEST }
])
).toThrow(/does not accept assessments/i)
})
it('stages only unsigned Windows candidates and preserves valid upstream identity', () => {
const identity = identityFor('win32-x64')
const preservedPath = 'node_modules/node-pty/build/Release/conpty/OpenConsole.exe'
const selection = buildSshRelayRuntimeNativeSigningSelection(
identity,
windowsAssessments(identity, preservedPath)
)
expect(selection.signingFiles).toHaveLength(4)
expect(selection.preservedUpstreamFiles).toEqual([
expect.objectContaining({
path: preservedPath,
action: 'preserve-valid-upstream',
signerSubject: 'CN=Microsoft Corporation',
signerThumbprint: 'A'.repeat(40)
})
])
expect(selection.verificationFiles).toHaveLength(6)
})
it('rejects invalid Windows signature states and malformed signer identity', () => {
const identity = identityFor('win32-arm64')
const assessments = windowsAssessments(identity)
assessments[0].status = 'hash-mismatch'
expect(() => buildSshRelayRuntimeNativeSigningSelection(identity, assessments)).toThrow(
/signature status/i
)
const malformedSigner = windowsAssessments(
identity,
'node_modules/node-pty/build/Release/conpty/OpenConsole.exe'
)
malformedSigner.find((entry) => entry.status === 'valid-upstream').signerThumbprint = 'bad'
expect(() => buildSshRelayRuntimeNativeSigningSelection(identity, malformedSigner)).toThrow(
/thumbprint/i
)
const controlSubject = windowsAssessments(
identity,
'node_modules/node-pty/build/Release/conpty/OpenConsole.exe'
)
controlSubject.find((entry) => entry.status === 'valid-upstream').signerSubject =
'CN=Microsoft\nCorporation'
expect(() => buildSshRelayRuntimeNativeSigningSelection(identity, controlSubject)).toThrow(
/signer subject/i
)
})
it('rejects assessment fields outside the exact status-qualified schema', () => {
const identity = identityFor('win32-x64')
const unexpected = windowsAssessments(identity)
unexpected[0].signerSubject = 'CN=Unexpected'
expect(() => buildSshRelayRuntimeNativeSigningSelection(identity, unexpected)).toThrow(
/unexpected fields/i
)
const preserved = windowsAssessments(
identity,
'node_modules/node-pty/build/Release/conpty/OpenConsole.exe'
)
preserved.find((entry) => entry.status === 'valid-upstream').unexpected = true
expect(() => buildSshRelayRuntimeNativeSigningSelection(identity, preserved)).toThrow(
/unexpected fields/i
)
})
it('rejects missing, duplicate, extra, and hash-mismatched assessments', () => {
const identity = identityFor('win32-x64')
const assessments = windowsAssessments(identity)
expect(() =>
buildSshRelayRuntimeNativeSigningSelection(identity, assessments.slice(1))
).toThrow(/missing assessment/i)
expect(() =>
buildSshRelayRuntimeNativeSigningSelection(identity, [assessments[0], ...assessments])
).toThrow(/duplicate assessment/i)
expect(() =>
buildSshRelayRuntimeNativeSigningSelection(identity, [
...assessments,
{ path: 'extra.exe', sourceSha256: DIGEST, status: 'unsigned' }
])
).toThrow(/unexpected assessment/i)
const mismatched = structuredClone(assessments)
mismatched[0].sourceSha256 = `sha256:${'b'.repeat(64)}`
expect(() => buildSshRelayRuntimeNativeSigningSelection(identity, mismatched)).toThrow(
/source hash/i
)
})
it('rejects malformed authenticated size and digest metadata', () => {
const invalidSize = identityFor('darwin-x64')
invalidSize.entries.find((entry) => entry.role === 'node').size = -1
expect(() => buildSshRelayRuntimeNativeSigningSelection(invalidSize, [])).toThrow(/size/i)
const invalidDigest = identityFor('darwin-x64')
invalidDigest.entries.find((entry) => entry.role === 'node').sha256 = 'sha256:nope'
expect(() => buildSshRelayRuntimeNativeSigningSelection(invalidDigest, [])).toThrow(/digest/i)
})
})
@@ -82,9 +82,17 @@ describe('SSH relay runtime artifact workflow', () => {
expect(source.match(/ssh-relay-runtime-provenance\.test\.mjs/g)).toHaveLength(4)
expect(source.match(/ssh-relay-runtime-toolchain\.test\.mjs/g)).toHaveLength(4)
expect(source.match(/ssh-relay-runtime-native-signing-plan\.test\.mjs/g)).toHaveLength(4)
expect(source.match(/ssh-relay-runtime-native-signing-selection\.test\.mjs/g)).toHaveLength(4)
expect(source.match(/ssh-relay-runtime-native-signing-payload\.test\.mjs/g)).toHaveLength(4)
expect(
source.match(/node --check config\/scripts\/ssh-relay-runtime-native-signing-plan\.mjs/g)
).toHaveLength(2)
expect(
source.match(/node --check config\/scripts\/ssh-relay-runtime-native-signing-selection\.mjs/g)
).toHaveLength(2)
expect(
source.match(/node --check config\/scripts\/ssh-relay-runtime-native-signing-payload\.mjs/g)
).toHaveLength(2)
expect(
source.match(/node --check config\/scripts\/ssh-relay-runtime-closure\.mjs/g)
).toHaveLength(2)