mirror of
https://github.com/stablyai/orca.git
synced 2026-10-18 00:03:20 +00:00
259 lines
8.6 KiB
JavaScript
259 lines
8.6 KiB
JavaScript
import { execFile } from 'node:child_process'
|
|
import { readdir, realpath } from 'node:fs/promises'
|
|
import { release } from 'node:os'
|
|
import { basename, join, resolve } from 'node:path'
|
|
import { promisify } from 'node:util'
|
|
|
|
import { sshRelayRuntimeCompatibility } from './ssh-relay-runtime-compatibility.mjs'
|
|
|
|
const execFileAsync = promisify(execFile)
|
|
const COMMAND_TIMEOUT_MS = 30_000
|
|
const COMMAND_OUTPUT_BYTES = 1024 * 1024
|
|
const MAX_RUNTIME_ENTRIES = 100
|
|
const MAX_RUNTIME_DEPTH = 8
|
|
|
|
const BASELINES = Object.freeze({
|
|
'linux-x64-glibc': {
|
|
platform: 'linux',
|
|
architecture: 'x64',
|
|
glibc: sshRelayRuntimeCompatibility['linux-x64-glibc'].libc.minimumVersion,
|
|
libstdcxx: sshRelayRuntimeCompatibility['linux-x64-glibc'].libc.minimumLibstdcxxVersion,
|
|
kernel: sshRelayRuntimeCompatibility['linux-x64-glibc'].minimumKernelVersion
|
|
},
|
|
'linux-arm64-glibc': {
|
|
platform: 'linux',
|
|
architecture: 'arm64',
|
|
glibc: sshRelayRuntimeCompatibility['linux-arm64-glibc'].libc.minimumVersion,
|
|
libstdcxx: sshRelayRuntimeCompatibility['linux-arm64-glibc'].libc.minimumLibstdcxxVersion,
|
|
kernel: sshRelayRuntimeCompatibility['linux-arm64-glibc'].minimumKernelVersion
|
|
},
|
|
'darwin-x64': {
|
|
platform: 'darwin',
|
|
architecture: 'x64',
|
|
osVersion: sshRelayRuntimeCompatibility['darwin-x64'].minimumVersion
|
|
},
|
|
'darwin-arm64': {
|
|
platform: 'darwin',
|
|
architecture: 'arm64',
|
|
osVersion: sshRelayRuntimeCompatibility['darwin-arm64'].minimumVersion
|
|
},
|
|
'win32-x64': {
|
|
platform: 'win32',
|
|
architecture: 'x64',
|
|
// Why: the reviewed x64 floor permits either Windows 10 22H2 or Server 2022.
|
|
osBuilds: [sshRelayRuntimeCompatibility['win32-x64'].minimumBuild, 20348]
|
|
},
|
|
'win32-arm64': {
|
|
platform: 'win32',
|
|
architecture: 'arm64',
|
|
osBuilds: [sshRelayRuntimeCompatibility['win32-arm64'].minimumBuild]
|
|
}
|
|
})
|
|
|
|
function majorMinor(value) {
|
|
const match = /^(\d+)\.(\d+)(?:\.|$)/.exec(value ?? '')
|
|
return match ? `${Number(match[1])}.${Number(match[2])}` : null
|
|
}
|
|
|
|
function windowsBuild(value) {
|
|
const match = /^\d+\.\d+\.(\d+)(?:\.|$)/.exec(value ?? '')
|
|
return match ? Number(match[1]) : null
|
|
}
|
|
|
|
export function evaluateSshRelayRuntimeBaseline({
|
|
tuple,
|
|
scope = 'full',
|
|
platform,
|
|
architecture,
|
|
osVersion,
|
|
kernelVersion,
|
|
glibcVersion,
|
|
libstdcxxVersion
|
|
}) {
|
|
const contract = BASELINES[tuple]
|
|
if (!contract) {
|
|
throw new Error(`Unknown SSH relay runtime baseline tuple: ${tuple}`)
|
|
}
|
|
if (scope !== 'full' && !(scope === 'linux-userland' && contract.platform === 'linux')) {
|
|
throw new Error(`Unsupported SSH relay runtime baseline scope: ${scope}`)
|
|
}
|
|
const checks = {
|
|
platform: platform === contract.platform,
|
|
architecture: architecture === contract.architecture
|
|
}
|
|
// Why: execution on a newer host does not prove the declared oldest floor can run these bytes.
|
|
if (contract.platform === 'linux') {
|
|
checks.glibc = majorMinor(glibcVersion) === contract.glibc
|
|
checks.libstdcxx = libstdcxxVersion === contract.libstdcxx
|
|
checks.kernel = majorMinor(kernelVersion) === contract.kernel
|
|
} else if (contract.platform === 'darwin') {
|
|
checks.osVersion = majorMinor(osVersion) === contract.osVersion
|
|
} else {
|
|
checks.osBuild = contract.osBuilds.includes(windowsBuild(osVersion))
|
|
}
|
|
const requiredChecks =
|
|
scope === 'linux-userland'
|
|
? ['platform', 'architecture', 'glibc', 'libstdcxx']
|
|
: Object.keys(checks)
|
|
const residualGaps = Object.keys(checks).filter((name) => !requiredChecks.includes(name))
|
|
return {
|
|
tuple,
|
|
scope,
|
|
qualified: requiredChecks.every((name) => checks[name]),
|
|
residualGaps,
|
|
contract,
|
|
observed: {
|
|
platform,
|
|
architecture,
|
|
osVersion: osVersion ?? null,
|
|
kernelVersion: kernelVersion ?? null,
|
|
glibcVersion: glibcVersion ?? null,
|
|
libstdcxxVersion: libstdcxxVersion ?? null
|
|
},
|
|
checks
|
|
}
|
|
}
|
|
|
|
export function parseSshRelayRuntimeLddLibstdcxxPaths(output) {
|
|
if (typeof output !== 'string' || Buffer.byteLength(output) > COMMAND_OUTPUT_BYTES) {
|
|
throw new Error('Runtime baseline ldd output is missing or oversized')
|
|
}
|
|
if (/=>\s+not found|version\s+[`'][^`']+['`]\s+not found/i.test(output)) {
|
|
// Why: an exact userland version is irrelevant when the staged native module cannot load.
|
|
throw new Error('Runtime baseline ldd output contains an unresolved native dependency')
|
|
}
|
|
return output
|
|
.split(/\r?\n/)
|
|
.map((line) => /libstdc\+\+\.so\.6\s+=>\s+(\/\S+)/.exec(line)?.[1])
|
|
.filter(Boolean)
|
|
}
|
|
|
|
export function parseSshRelayRuntimeLibstdcxxVersion(libraryPaths) {
|
|
if (!Array.isArray(libraryPaths) || libraryPaths.length === 0) {
|
|
throw new Error('Runtime baseline did not resolve a libstdc++ ABI library')
|
|
}
|
|
const versions = new Set(
|
|
libraryPaths.map((path) => /^libstdc\+\+\.so\.(\d+\.\d+\.\d+)$/.exec(basename(path))?.[1])
|
|
)
|
|
if (versions.has(undefined) || versions.size !== 1) {
|
|
throw new Error('Runtime baseline did not resolve one bounded libstdc++ ABI library')
|
|
}
|
|
return versions.values().next().value
|
|
}
|
|
|
|
async function runtimeNativeModules(runtimeDirectory) {
|
|
const modules = []
|
|
let entries = 0
|
|
async function visit(directory, depth) {
|
|
if (depth > MAX_RUNTIME_DEPTH) {
|
|
throw new Error('Runtime baseline tree exceeds the depth bound')
|
|
}
|
|
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
|
entries += 1
|
|
if (entries > MAX_RUNTIME_ENTRIES) {
|
|
throw new Error('Runtime baseline tree exceeds the entry bound')
|
|
}
|
|
const path = join(directory, entry.name)
|
|
if (entry.isDirectory()) {
|
|
await visit(path, depth + 1)
|
|
} else if (entry.isFile() && entry.name.endsWith('.node')) {
|
|
modules.push(path)
|
|
} else if (!entry.isFile()) {
|
|
throw new Error('Runtime baseline tree contains a special entry')
|
|
}
|
|
}
|
|
}
|
|
await visit(runtimeDirectory, 0)
|
|
if (modules.length === 0) {
|
|
throw new Error('Runtime baseline tree contains no native modules')
|
|
}
|
|
return modules
|
|
}
|
|
|
|
async function linuxLibstdcxxVersion(runtimeDirectory) {
|
|
const libraryPaths = new Set()
|
|
for (const modulePath of await runtimeNativeModules(runtimeDirectory)) {
|
|
const { stdout, stderr } = await execFileAsync('ldd', [modulePath], {
|
|
encoding: 'utf8',
|
|
maxBuffer: COMMAND_OUTPUT_BYTES,
|
|
timeout: COMMAND_TIMEOUT_MS
|
|
})
|
|
for (const path of parseSshRelayRuntimeLddLibstdcxxPaths(`${stderr}\n${stdout}`)) {
|
|
libraryPaths.add(await realpath(path))
|
|
}
|
|
}
|
|
return parseSshRelayRuntimeLibstdcxxVersion([...libraryPaths])
|
|
}
|
|
|
|
async function collectObservedBaseline({ tuple, runtimeDirectory }) {
|
|
const contract = BASELINES[tuple]
|
|
if (!contract) {
|
|
throw new Error(`Unknown SSH relay runtime baseline tuple: ${tuple}`)
|
|
}
|
|
const observed = {
|
|
platform: process.platform,
|
|
architecture: process.arch,
|
|
kernelVersion: release()
|
|
}
|
|
if (contract.platform === 'linux') {
|
|
if (!runtimeDirectory) {
|
|
throw new Error('--runtime-directory is required for a Linux baseline')
|
|
}
|
|
observed.glibcVersion = process.report.getReport().header.glibcVersionRuntime
|
|
observed.libstdcxxVersion = await linuxLibstdcxxVersion(runtimeDirectory)
|
|
} else if (contract.platform === 'darwin') {
|
|
const { stdout } = await execFileAsync('sw_vers', ['-productVersion'], {
|
|
encoding: 'utf8',
|
|
maxBuffer: COMMAND_OUTPUT_BYTES,
|
|
timeout: COMMAND_TIMEOUT_MS
|
|
})
|
|
observed.osVersion = stdout.trim()
|
|
} else {
|
|
observed.osVersion = release()
|
|
}
|
|
return observed
|
|
}
|
|
|
|
function parseArguments(argv) {
|
|
const result = { scope: 'full' }
|
|
for (let index = 0; index < argv.length; index += 2) {
|
|
const flag = argv[index]
|
|
const value = argv[index + 1]
|
|
if (!value || value.startsWith('--')) {
|
|
throw new Error(`${flag} requires a value`)
|
|
}
|
|
if (flag === '--tuple') {
|
|
result.tuple = value
|
|
} else if (flag === '--scope') {
|
|
result.scope = value
|
|
} else if (flag === '--runtime-directory') {
|
|
result.runtimeDirectory = resolve(value)
|
|
} else {
|
|
throw new Error(`Unknown argument: ${flag}`)
|
|
}
|
|
}
|
|
if (!result.tuple) {
|
|
throw new Error('--tuple is required')
|
|
}
|
|
return result
|
|
}
|
|
|
|
async function main() {
|
|
const options = parseArguments(process.argv.slice(2))
|
|
const result = evaluateSshRelayRuntimeBaseline({
|
|
...options,
|
|
...(await collectObservedBaseline(options))
|
|
})
|
|
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`)
|
|
if (!result.qualified) {
|
|
process.exitCode = 1
|
|
}
|
|
}
|
|
|
|
if (process.argv[1] && resolve(process.argv[1]) === import.meta.filename) {
|
|
main().catch((error) => {
|
|
process.stderr.write(`SSH relay runtime baseline verification failed: ${error.message}\n`)
|
|
process.exitCode = 1
|
|
})
|
|
}
|