mirror of
https://github.com/stablyai/orca.git
synced 2026-10-18 08:03:22 +00:00
build(ssh): complete runtime artifact provenance
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -43,6 +43,8 @@ jobs:
|
||||
node_archive: node-v24.18.0-darwin-arm64.tar.xz
|
||||
runs-on: ${{ matrix.runner }}
|
||||
timeout-minutes: 20
|
||||
env:
|
||||
ORCA_RUNTIME_REQUESTED_RUNNER: ${{ matrix.runner }}
|
||||
|
||||
steps:
|
||||
- name: Checkout exact source revision
|
||||
@@ -109,6 +111,11 @@ jobs:
|
||||
node --check config/scripts/ssh-relay-runtime-build.test.mjs
|
||||
node --check config/scripts/ssh-relay-runtime-closure.mjs
|
||||
node --check config/scripts/ssh-relay-runtime-closure.test.mjs
|
||||
node --check config/scripts/ssh-relay-runtime-sbom.mjs
|
||||
node --check config/scripts/ssh-relay-runtime-sbom.test.mjs
|
||||
node --check config/scripts/ssh-relay-runtime-provenance.test.mjs
|
||||
node --check config/scripts/ssh-relay-runtime-toolchain.mjs
|
||||
node --check config/scripts/ssh-relay-runtime-toolchain.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 \
|
||||
@@ -122,6 +129,9 @@ jobs:
|
||||
config/scripts/ssh-relay-runtime-pty-smoke.test.mjs \
|
||||
config/scripts/ssh-relay-runtime-reproducibility.test.mjs \
|
||||
config/scripts/ssh-relay-runtime-resource-diagnostics.test.mjs \
|
||||
config/scripts/ssh-relay-runtime-provenance.test.mjs \
|
||||
config/scripts/ssh-relay-runtime-sbom.test.mjs \
|
||||
config/scripts/ssh-relay-runtime-toolchain.test.mjs \
|
||||
config/scripts/ssh-relay-runtime-windows-tree.test.mjs \
|
||||
config/scripts/ssh-relay-runtime-zip.test.mjs \
|
||||
config/scripts/ssh-relay-runtime-workflow.test.mjs
|
||||
@@ -208,6 +218,8 @@ jobs:
|
||||
msvc_arch: arm64
|
||||
runs-on: ${{ matrix.runner }}
|
||||
timeout-minutes: 30
|
||||
env:
|
||||
ORCA_RUNTIME_REQUESTED_RUNNER: ${{ matrix.runner }}
|
||||
|
||||
steps:
|
||||
- name: Checkout exact source revision
|
||||
@@ -286,6 +298,16 @@ jobs:
|
||||
if ($LASTEXITCODE -ne 0) { throw 'runtime closure script syntax check failed' }
|
||||
node --check config/scripts/ssh-relay-runtime-closure.test.mjs
|
||||
if ($LASTEXITCODE -ne 0) { throw 'runtime closure test syntax check failed' }
|
||||
node --check config/scripts/ssh-relay-runtime-sbom.mjs
|
||||
if ($LASTEXITCODE -ne 0) { throw 'runtime SBOM script syntax check failed' }
|
||||
node --check config/scripts/ssh-relay-runtime-sbom.test.mjs
|
||||
if ($LASTEXITCODE -ne 0) { throw 'runtime SBOM test syntax check failed' }
|
||||
node --check config/scripts/ssh-relay-runtime-provenance.test.mjs
|
||||
if ($LASTEXITCODE -ne 0) { throw 'runtime provenance test syntax check failed' }
|
||||
node --check config/scripts/ssh-relay-runtime-toolchain.mjs
|
||||
if ($LASTEXITCODE -ne 0) { throw 'runtime toolchain script syntax check failed' }
|
||||
node --check config/scripts/ssh-relay-runtime-toolchain.test.mjs
|
||||
if ($LASTEXITCODE -ne 0) { throw 'runtime toolchain 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 `
|
||||
@@ -299,6 +321,9 @@ jobs:
|
||||
config/scripts/ssh-relay-runtime-pty-smoke.test.mjs `
|
||||
config/scripts/ssh-relay-runtime-reproducibility.test.mjs `
|
||||
config/scripts/ssh-relay-runtime-resource-diagnostics.test.mjs `
|
||||
config/scripts/ssh-relay-runtime-provenance.test.mjs `
|
||||
config/scripts/ssh-relay-runtime-sbom.test.mjs `
|
||||
config/scripts/ssh-relay-runtime-toolchain.test.mjs `
|
||||
config/scripts/ssh-relay-runtime-windows-pe-diagnostic.test.mjs `
|
||||
config/scripts/ssh-relay-runtime-windows-tree.test.mjs `
|
||||
config/scripts/ssh-relay-runtime-zip.test.mjs `
|
||||
|
||||
@@ -11,6 +11,11 @@ import {
|
||||
inspectSshRelayRuntimeArchive
|
||||
} from './ssh-relay-runtime-archive.mjs'
|
||||
import { writeSshRelayRuntimeMetadata } from './ssh-relay-runtime-provenance.mjs'
|
||||
import {
|
||||
collectSshRelayRuntimeToolchain,
|
||||
sshRelayRuntimeBuilderIdentity,
|
||||
sshRelayRuntimeRunnerIdentity
|
||||
} from './ssh-relay-runtime-toolchain.mjs'
|
||||
import { assembleSshRelayRuntimeTree } from './ssh-relay-runtime-tree.mjs'
|
||||
import { validateSshRelayNodeReleaseContract } from './ssh-relay-node-release-verification.mjs'
|
||||
import { verifyNodeReleaseInputs } from './verify-ssh-relay-node-release-inputs.mjs'
|
||||
@@ -130,36 +135,6 @@ async function run(command, args, options = {}) {
|
||||
return result.stdout.trim()
|
||||
}
|
||||
|
||||
async function toolchainDetails(nodePath) {
|
||||
const commands =
|
||||
process.platform === 'win32'
|
||||
? [
|
||||
['bundledNode', nodePath, ['--version']],
|
||||
['compiler', 'cl.exe', []],
|
||||
['python', 'python.exe', ['--version']]
|
||||
]
|
||||
: [
|
||||
['bundledNode', nodePath, ['--version']],
|
||||
['xz', 'xz', ['--version']],
|
||||
['compiler', 'c++', ['--version']],
|
||||
[
|
||||
'strip',
|
||||
process.platform === 'darwin' ? 'xcrun' : 'strip',
|
||||
process.platform === 'darwin' ? ['--find', 'strip'] : ['--version']
|
||||
],
|
||||
['python', 'python3', ['--version']]
|
||||
]
|
||||
const details = {}
|
||||
for (const [name, command, args] of commands) {
|
||||
details[name] = (await run(command, args)).split(/\r?\n/)[0]
|
||||
}
|
||||
if (process.platform === 'win32') {
|
||||
details.zip = 'yazl 3.3.1'
|
||||
}
|
||||
details.buildNode = process.version
|
||||
return details
|
||||
}
|
||||
|
||||
function relayPlatform(tuple) {
|
||||
return tuple.replace('-glibc', '')
|
||||
}
|
||||
@@ -241,7 +216,7 @@ export async function buildSshRelayRuntime(options) {
|
||||
sourceDateEpoch: options.sourceDateEpoch
|
||||
})
|
||||
const inspection = await inspectSshRelayRuntimeArchive(archive.path, identity)
|
||||
const toolchain = await toolchainDetails(extracted.nodePath)
|
||||
const toolchain = await collectSshRelayRuntimeToolchain(extracted.nodePath)
|
||||
const metadata = await writeSshRelayRuntimeMetadata({
|
||||
outputDirectory: options.outputDirectory,
|
||||
identity,
|
||||
@@ -249,7 +224,8 @@ export async function buildSshRelayRuntime(options) {
|
||||
nodeRelease: release,
|
||||
sourceDateEpoch: options.sourceDateEpoch,
|
||||
gitCommit: options.gitCommit,
|
||||
builder: process.env.GITHUB_WORKFLOW_REF ?? `local://${process.platform}/${process.arch}`,
|
||||
builder: sshRelayRuntimeBuilderIdentity({ gitCommit: options.gitCommit }),
|
||||
runner: sshRelayRuntimeRunnerIdentity(),
|
||||
toolchain
|
||||
})
|
||||
return {
|
||||
|
||||
@@ -1,43 +1,14 @@
|
||||
import { createHash } from 'node:crypto'
|
||||
import { readFile, stat, writeFile } from 'node:fs/promises'
|
||||
import { createRequire } from 'node:module'
|
||||
import { join } from 'node:path'
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
import { createSshRelayRuntimeSbom } from './ssh-relay-runtime-sbom.mjs'
|
||||
import { assertSshRelayRuntimeToolchain } from './ssh-relay-runtime-toolchain.mjs'
|
||||
|
||||
function sha256(bytes) {
|
||||
return `sha256:${createHash('sha256').update(bytes).digest('hex')}`
|
||||
}
|
||||
|
||||
function spdxId(value) {
|
||||
return `SPDXRef-${value.replace(/[^A-Za-z0-9.-]/g, '-')}`
|
||||
}
|
||||
|
||||
function packageRecord(name, version, license) {
|
||||
return {
|
||||
name,
|
||||
SPDXID: spdxId(`Package-${name}`),
|
||||
versionInfo: version,
|
||||
downloadLocation: 'NOASSERTION',
|
||||
filesAnalyzed: false,
|
||||
licenseConcluded: license,
|
||||
licenseDeclared: license,
|
||||
copyrightText: 'NOASSERTION'
|
||||
}
|
||||
}
|
||||
|
||||
function installedPackage(name) {
|
||||
const parsed = require(`${name}/package.json`)
|
||||
return packageRecord(parsed.name, parsed.version, parsed.license ?? 'NOASSERTION')
|
||||
}
|
||||
|
||||
function watcherNativePackage(tuple) {
|
||||
if (tuple.startsWith('linux-')) {
|
||||
return `@parcel/watcher-linux-${tuple.slice('linux-'.length)}`
|
||||
}
|
||||
return `@parcel/watcher-${tuple}`
|
||||
}
|
||||
|
||||
async function writeJson(path, value) {
|
||||
const bytes = Buffer.from(`${JSON.stringify(value, null, 2)}\n`, 'utf8')
|
||||
await writeFile(path, bytes, { flag: 'wx', mode: 0o600 })
|
||||
@@ -52,48 +23,14 @@ export async function writeSshRelayRuntimeMetadata({
|
||||
sourceDateEpoch,
|
||||
gitCommit,
|
||||
builder,
|
||||
runner,
|
||||
toolchain
|
||||
}) {
|
||||
const createdAt = new Date(sourceDateEpoch * 1000).toISOString()
|
||||
assertSshRelayRuntimeToolchain(toolchain, identity.tupleId)
|
||||
const sbomName = `orca-ssh-relay-runtime-${identity.tupleId}.spdx.json`
|
||||
const provenanceName = `orca-ssh-relay-runtime-${identity.tupleId}.provenance.json`
|
||||
const identityName = `orca-ssh-relay-runtime-${identity.tupleId}.identity.json`
|
||||
const files = identity.entries
|
||||
.filter((entry) => entry.type === 'file')
|
||||
.map((entry, index) => ({
|
||||
fileName: entry.path,
|
||||
SPDXID: `SPDXRef-File-${index + 1}`,
|
||||
checksums: [{ algorithm: 'SHA256', checksumValue: entry.sha256.slice('sha256:'.length) }],
|
||||
licenseConcluded: 'NOASSERTION',
|
||||
copyrightText: 'NOASSERTION'
|
||||
}))
|
||||
const packages = [
|
||||
packageRecord('node', identity.nodeVersion, 'NOASSERTION'),
|
||||
packageRecord('orca-ssh-relay', '0.1.0', 'NOASSERTION'),
|
||||
installedPackage('node-pty'),
|
||||
installedPackage('@parcel/watcher'),
|
||||
installedPackage(watcherNativePackage(identity.tupleId)),
|
||||
installedPackage('detect-libc'),
|
||||
installedPackage('is-glob'),
|
||||
installedPackage('is-extglob'),
|
||||
installedPackage('picomatch')
|
||||
]
|
||||
const documentId = identity.contentId.slice('sha256:'.length)
|
||||
const sbom = {
|
||||
spdxVersion: 'SPDX-2.3',
|
||||
dataLicense: 'CC0-1.0',
|
||||
SPDXID: 'SPDXRef-DOCUMENT',
|
||||
name: `Orca SSH relay runtime ${identity.tupleId}`,
|
||||
documentNamespace: `https://github.com/stablyai/orca/ssh-relay-runtime/${documentId}`,
|
||||
creationInfo: { created: createdAt, creators: ['Organization: Stably AI'] },
|
||||
packages,
|
||||
files,
|
||||
relationships: packages.map((component) => ({
|
||||
spdxElementId: 'SPDXRef-DOCUMENT',
|
||||
relationshipType: 'DESCRIBES',
|
||||
relatedSpdxElement: component.SPDXID
|
||||
}))
|
||||
}
|
||||
const sbom = createSshRelayRuntimeSbom({ identity, archive, sourceDateEpoch })
|
||||
const nativeFiles = identity.entries
|
||||
.filter(
|
||||
(entry) =>
|
||||
@@ -139,7 +76,10 @@ export async function writeSshRelayRuntimeMetadata({
|
||||
},
|
||||
runDetails: {
|
||||
builder: { id: builder },
|
||||
metadata: { invocationId: process.env.GITHUB_RUN_ID ?? 'local-unpublished-build' },
|
||||
metadata: {
|
||||
invocationId: process.env.GITHUB_RUN_ID ?? 'local-unpublished-build',
|
||||
runner
|
||||
},
|
||||
byproducts: [{ name: 'native-files', content: nativeFiles }]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
import { mkdtemp, readFile, rm } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
|
||||
import { writeSshRelayRuntimeMetadata } from './ssh-relay-runtime-provenance.mjs'
|
||||
|
||||
const temporaryDirectories = []
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all(temporaryDirectories.splice(0).map((path) => rm(path, { recursive: true })))
|
||||
})
|
||||
|
||||
const sha256 = (character) => `sha256:${character.repeat(64)}`
|
||||
|
||||
function file(path, role, character) {
|
||||
return { path, type: 'file', role, size: 1, mode: 0o644, sha256: sha256(character) }
|
||||
}
|
||||
|
||||
function fixture() {
|
||||
const tuple = 'linux-x64-glibc'
|
||||
const identity = {
|
||||
tupleId: tuple,
|
||||
os: 'linux',
|
||||
architecture: 'x64',
|
||||
nodeVersion: '24.18.0',
|
||||
contentId: sha256('f'),
|
||||
fileCount: 5,
|
||||
expandedSize: 5,
|
||||
entries: [
|
||||
file('bin/node', 'node', '1'),
|
||||
file('relay.js', 'relay', '2'),
|
||||
file('node_modules/node-pty/build/Release/pty.node', 'node-pty-native', '3'),
|
||||
file(
|
||||
'node_modules/@parcel/watcher-linux-x64-glibc/watcher.node',
|
||||
'parcel-watcher-native',
|
||||
'4'
|
||||
),
|
||||
file('THIRD_PARTY_LICENSES.txt', 'license', '5')
|
||||
]
|
||||
}
|
||||
return {
|
||||
tuple,
|
||||
identity,
|
||||
archive: {
|
||||
name: `orca-ssh-relay-runtime-v1-${tuple}-${'f'.repeat(64)}.tar.xz`,
|
||||
size: 123,
|
||||
sha256: sha256('a')
|
||||
},
|
||||
nodeRelease: {
|
||||
baseUrl: 'https://nodejs.org/dist/v24.18.0',
|
||||
archives: {
|
||||
[tuple]: { name: 'node-v24.18.0-linux-x64.tar.xz', sha256: 'b'.repeat(64) }
|
||||
},
|
||||
signature: {
|
||||
key: {
|
||||
sourceUrl: 'https://github.com/nodejs/release-keys/raw/commit/gpg/key.asc',
|
||||
sha256: 'c'.repeat(64)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe('SSH relay runtime published metadata', () => {
|
||||
it('binds SPDX, SLSA provenance, and identity to the exact archive and runner', async () => {
|
||||
const outputDirectory = await mkdtemp(join(tmpdir(), 'orca-runtime-metadata-'))
|
||||
temporaryDirectories.push(outputDirectory)
|
||||
const input = fixture()
|
||||
const runner = {
|
||||
os: 'Linux',
|
||||
architecture: 'X64',
|
||||
environment: 'github-hosted',
|
||||
requestedLabel: 'ubuntu-24.04',
|
||||
image: { os: 'ubuntu24', version: '20260705.232.1' }
|
||||
}
|
||||
const toolchain = Object.fromEntries(
|
||||
[
|
||||
'buildNode',
|
||||
'bundledNode',
|
||||
'compiler',
|
||||
'buildSystem',
|
||||
'python',
|
||||
'archive',
|
||||
'nodeAddonApi',
|
||||
'nodeGyp',
|
||||
'strip'
|
||||
].map((name, index) => [
|
||||
name,
|
||||
{ version: `${name} version`, sha256: sha256((index + 6).toString(16)) }
|
||||
])
|
||||
)
|
||||
const builder = `https://github.com/stablyai/orca/blob/${'1'.repeat(40)}/.github/workflows/ssh-relay-runtime-artifacts.yml`
|
||||
|
||||
const assets = await writeSshRelayRuntimeMetadata({
|
||||
outputDirectory,
|
||||
identity: input.identity,
|
||||
archive: input.archive,
|
||||
nodeRelease: input.nodeRelease,
|
||||
sourceDateEpoch: 1_752_710_400,
|
||||
gitCommit: '1'.repeat(40),
|
||||
builder,
|
||||
runner,
|
||||
toolchain
|
||||
})
|
||||
const sbom = JSON.parse(await readFile(join(outputDirectory, assets.sbom.name), 'utf8'))
|
||||
const provenance = JSON.parse(
|
||||
await readFile(join(outputDirectory, assets.provenance.name), 'utf8')
|
||||
)
|
||||
const identity = JSON.parse(await readFile(join(outputDirectory, assets.identity.name), 'utf8'))
|
||||
|
||||
expect(sbom.files).toHaveLength(input.identity.fileCount)
|
||||
expect(
|
||||
sbom.relationships.filter((entry) => entry.relationshipType === 'CONTAINS')
|
||||
).toHaveLength(input.identity.fileCount)
|
||||
expect(provenance.subject).toEqual([
|
||||
{ name: input.archive.name, digest: { sha256: 'a'.repeat(64) } }
|
||||
])
|
||||
expect(provenance.predicate.runDetails).toMatchObject({
|
||||
builder: { id: builder },
|
||||
metadata: { runner }
|
||||
})
|
||||
expect(provenance.predicate.buildDefinition).toMatchObject({
|
||||
internalParameters: { toolchain },
|
||||
resolvedDependencies: expect.arrayContaining([
|
||||
{
|
||||
uri: 'https://nodejs.org/dist/v24.18.0/node-v24.18.0-linux-x64.tar.xz',
|
||||
digest: { sha256: 'b'.repeat(64) }
|
||||
},
|
||||
{
|
||||
uri: `git+https://github.com/stablyai/orca@${'1'.repeat(40)}`,
|
||||
digest: { gitCommit: '1'.repeat(40) }
|
||||
}
|
||||
])
|
||||
})
|
||||
expect(identity.archive).toEqual({
|
||||
name: input.archive.name,
|
||||
size: 123,
|
||||
expandedSize: 5,
|
||||
fileCount: 5,
|
||||
sha256: sha256('a')
|
||||
})
|
||||
await expect(
|
||||
writeSshRelayRuntimeMetadata({
|
||||
outputDirectory,
|
||||
identity: input.identity,
|
||||
archive: input.archive,
|
||||
nodeRelease: input.nodeRelease,
|
||||
sourceDateEpoch: 1_752_710_400,
|
||||
gitCommit: '1'.repeat(40),
|
||||
builder,
|
||||
runner,
|
||||
toolchain
|
||||
})
|
||||
).rejects.toThrow()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,125 @@
|
||||
import { createRequire } from 'node:module'
|
||||
|
||||
import { sshRelayRuntimeWatcherPackage } from './ssh-relay-runtime-closure.mjs'
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
|
||||
function spdxId(value) {
|
||||
return `SPDXRef-${value.replace(/[^A-Za-z0-9.-]/g, '-')}`
|
||||
}
|
||||
|
||||
function packageRecord(name, version, license) {
|
||||
return {
|
||||
name,
|
||||
SPDXID: spdxId(`Package-${name}`),
|
||||
versionInfo: version,
|
||||
downloadLocation: 'NOASSERTION',
|
||||
filesAnalyzed: false,
|
||||
licenseConcluded: license,
|
||||
licenseDeclared: license,
|
||||
copyrightText: 'NOASSERTION'
|
||||
}
|
||||
}
|
||||
|
||||
function installedPackage(name) {
|
||||
const parsed = require(`${name}/package.json`)
|
||||
return packageRecord(parsed.name, parsed.version, parsed.license ?? 'NOASSERTION')
|
||||
}
|
||||
|
||||
function fileType(role) {
|
||||
return ['node', 'node-pty-native', 'parcel-watcher-native', 'native-runtime'].includes(role)
|
||||
? 'BINARY'
|
||||
: role === 'license'
|
||||
? 'TEXT'
|
||||
: 'SOURCE'
|
||||
}
|
||||
|
||||
function ownerPackage(path, packages) {
|
||||
const packagePath = packages.find((entry) => path.startsWith(`node_modules/${entry.name}/`))
|
||||
if (packagePath) {
|
||||
return packagePath
|
||||
}
|
||||
if (path.startsWith('node_modules/')) {
|
||||
return undefined
|
||||
}
|
||||
if (path === 'bin/node' || path === 'bin/node.exe') {
|
||||
return packages.find((entry) => entry.name === 'node')
|
||||
}
|
||||
return packages.find((entry) => entry.name === 'orca-ssh-relay')
|
||||
}
|
||||
|
||||
export function createSshRelayRuntimeSbom({ identity, archive, sourceDateEpoch }) {
|
||||
if (!/^sha256:[0-9a-f]{64}$/.test(archive?.sha256 ?? '')) {
|
||||
throw new Error('Runtime SBOM requires the exact archive SHA-256')
|
||||
}
|
||||
const watcherPackage = sshRelayRuntimeWatcherPackage(identity.tupleId)
|
||||
const packages = [
|
||||
packageRecord('node', identity.nodeVersion, 'MIT'),
|
||||
packageRecord('orca-ssh-relay', identity.contentId, 'NOASSERTION'),
|
||||
installedPackage('node-pty'),
|
||||
installedPackage('@parcel/watcher'),
|
||||
installedPackage(watcherPackage),
|
||||
installedPackage('detect-libc'),
|
||||
installedPackage('is-glob'),
|
||||
installedPackage('is-extglob'),
|
||||
installedPackage('picomatch')
|
||||
]
|
||||
const files = identity.entries
|
||||
.filter((entry) => entry.type === 'file')
|
||||
.map((entry) => ({
|
||||
fileName: entry.path,
|
||||
SPDXID: spdxId(`File-${entry.path}`),
|
||||
fileTypes: [fileType(entry.role)],
|
||||
checksums: [{ algorithm: 'SHA256', checksumValue: entry.sha256.slice('sha256:'.length) }],
|
||||
licenseConcluded: 'NOASSERTION',
|
||||
copyrightText: 'NOASSERTION'
|
||||
}))
|
||||
const identifiers = new Set()
|
||||
for (const element of [...packages, ...files]) {
|
||||
if (identifiers.has(element.SPDXID)) {
|
||||
throw new Error(`Runtime SBOM contains a duplicate SPDX identifier: ${element.SPDXID}`)
|
||||
}
|
||||
identifiers.add(element.SPDXID)
|
||||
}
|
||||
const contains = files.map((file) => {
|
||||
const owner = ownerPackage(file.fileName, packages)
|
||||
if (!owner) {
|
||||
throw new Error(`Runtime SBOM cannot assign a package owner: ${file.fileName}`)
|
||||
}
|
||||
return {
|
||||
spdxElementId: owner.SPDXID,
|
||||
relationshipType: 'CONTAINS',
|
||||
relatedSpdxElement: file.SPDXID
|
||||
}
|
||||
})
|
||||
const relay = packages.find((entry) => entry.name === 'orca-ssh-relay')
|
||||
const relationships = [
|
||||
...packages.map((component) => ({
|
||||
spdxElementId: 'SPDXRef-DOCUMENT',
|
||||
relationshipType: 'DESCRIBES',
|
||||
relatedSpdxElement: component.SPDXID
|
||||
})),
|
||||
...packages
|
||||
.filter((component) => component !== relay)
|
||||
.map((component) => ({
|
||||
spdxElementId: relay.SPDXID,
|
||||
relationshipType: 'DEPENDS_ON',
|
||||
relatedSpdxElement: component.SPDXID
|
||||
})),
|
||||
...contains
|
||||
]
|
||||
return {
|
||||
spdxVersion: 'SPDX-2.3',
|
||||
dataLicense: 'CC0-1.0',
|
||||
SPDXID: 'SPDXRef-DOCUMENT',
|
||||
name: `Orca SSH relay runtime ${identity.tupleId}`,
|
||||
documentNamespace: `https://github.com/stablyai/orca/ssh-relay-runtime/spdx/${archive.sha256.slice('sha256:'.length)}`,
|
||||
creationInfo: {
|
||||
created: new Date(sourceDateEpoch * 1000).toISOString(),
|
||||
creators: ['Organization: Stably AI']
|
||||
},
|
||||
packages,
|
||||
files,
|
||||
relationships
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { createSshRelayRuntimeSbom } from './ssh-relay-runtime-sbom.mjs'
|
||||
|
||||
const sha256 = (character) => `sha256:${character.repeat(64)}`
|
||||
|
||||
function file(path, role, character) {
|
||||
return { path, type: 'file', role, size: 1, mode: 0o644, sha256: sha256(character) }
|
||||
}
|
||||
|
||||
function identity(tuple = 'linux-arm64-glibc') {
|
||||
return {
|
||||
tupleId: tuple,
|
||||
nodeVersion: '24.18.0',
|
||||
contentId: sha256('f'),
|
||||
entries: [
|
||||
file('bin/node', 'node', '1'),
|
||||
file('relay.js', 'relay', '2'),
|
||||
file('THIRD_PARTY_LICENSES.txt', 'license', '3'),
|
||||
file('node_modules/node-pty/lib/index.js', 'runtime-javascript', '4'),
|
||||
file('node_modules/@parcel/watcher/index.js', 'runtime-javascript', '5'),
|
||||
file(`node_modules/@parcel/watcher-${tuple}/watcher.node`, 'parcel-watcher-native', '6'),
|
||||
file('node_modules/detect-libc/lib/detect-libc.js', 'runtime-javascript', '7'),
|
||||
file('node_modules/is-glob/index.js', 'runtime-javascript', '8'),
|
||||
file('node_modules/is-extglob/index.js', 'runtime-javascript', '9'),
|
||||
file('node_modules/picomatch/index.js', 'runtime-javascript', 'a')
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
const archive = { sha256: sha256('e') }
|
||||
|
||||
function ownerName(sbom, fileName) {
|
||||
const fileId = sbom.files.find((entry) => entry.fileName === fileName).SPDXID
|
||||
const owners = sbom.relationships.filter(
|
||||
(entry) => entry.relationshipType === 'CONTAINS' && entry.relatedSpdxElement === fileId
|
||||
)
|
||||
expect(owners).toHaveLength(1)
|
||||
return sbom.packages.find((entry) => entry.SPDXID === owners[0].spdxElementId).name
|
||||
}
|
||||
|
||||
describe('SSH relay runtime SPDX SBOM', () => {
|
||||
it('inventories every runtime file under exactly one package', () => {
|
||||
const fixture = identity()
|
||||
const sbom = createSshRelayRuntimeSbom({
|
||||
identity: fixture,
|
||||
archive,
|
||||
sourceDateEpoch: 1_752_710_400
|
||||
})
|
||||
|
||||
expect(sbom.spdxVersion).toBe('SPDX-2.3')
|
||||
expect(sbom.documentNamespace).toBe(
|
||||
`https://github.com/stablyai/orca/ssh-relay-runtime/spdx/${'e'.repeat(64)}`
|
||||
)
|
||||
expect(sbom.creationInfo.created).toBe('2025-07-17T00:00:00.000Z')
|
||||
expect(sbom.packages.map((entry) => entry.name)).toEqual([
|
||||
'node',
|
||||
'orca-ssh-relay',
|
||||
'node-pty',
|
||||
'@parcel/watcher',
|
||||
'@parcel/watcher-linux-arm64-glibc',
|
||||
'detect-libc',
|
||||
'is-glob',
|
||||
'is-extglob',
|
||||
'picomatch'
|
||||
])
|
||||
expect(sbom.packages.find((entry) => entry.name === 'node')).toMatchObject({
|
||||
versionInfo: '24.18.0',
|
||||
licenseDeclared: 'MIT'
|
||||
})
|
||||
expect(sbom.packages.find((entry) => entry.name === 'orca-ssh-relay')).toMatchObject({
|
||||
versionInfo: fixture.contentId,
|
||||
licenseDeclared: 'NOASSERTION'
|
||||
})
|
||||
expect(sbom.files).toHaveLength(fixture.entries.length)
|
||||
expect(new Set([...sbom.packages, ...sbom.files].map((entry) => entry.SPDXID)).size).toBe(
|
||||
sbom.packages.length + sbom.files.length
|
||||
)
|
||||
expect(ownerName(sbom, 'bin/node')).toBe('node')
|
||||
expect(ownerName(sbom, 'relay.js')).toBe('orca-ssh-relay')
|
||||
expect(ownerName(sbom, 'THIRD_PARTY_LICENSES.txt')).toBe('orca-ssh-relay')
|
||||
expect(ownerName(sbom, 'node_modules/node-pty/lib/index.js')).toBe('node-pty')
|
||||
expect(ownerName(sbom, 'node_modules/@parcel/watcher/index.js')).toBe('@parcel/watcher')
|
||||
expect(ownerName(sbom, 'node_modules/@parcel/watcher-linux-arm64-glibc/watcher.node')).toBe(
|
||||
'@parcel/watcher-linux-arm64-glibc'
|
||||
)
|
||||
expect(ownerName(sbom, 'node_modules/detect-libc/lib/detect-libc.js')).toBe('detect-libc')
|
||||
expect(ownerName(sbom, 'node_modules/is-glob/index.js')).toBe('is-glob')
|
||||
expect(ownerName(sbom, 'node_modules/is-extglob/index.js')).toBe('is-extglob')
|
||||
expect(ownerName(sbom, 'node_modules/picomatch/index.js')).toBe('picomatch')
|
||||
expect(
|
||||
sbom.relationships.filter((entry) => entry.relationshipType === 'DEPENDS_ON')
|
||||
).toHaveLength(8)
|
||||
})
|
||||
|
||||
it('rejects an unowned node_modules path and colliding SPDX identifiers', () => {
|
||||
const unowned = identity()
|
||||
unowned.entries.push(file('node_modules/unreviewed/index.js', 'runtime-javascript', 'b'))
|
||||
expect(() =>
|
||||
createSshRelayRuntimeSbom({ identity: unowned, archive, sourceDateEpoch: 1_752_710_400 })
|
||||
).toThrow(/cannot assign a package owner/i)
|
||||
|
||||
const collision = identity()
|
||||
collision.entries.push(
|
||||
file('relay/a+b.js', 'runtime-javascript', 'b'),
|
||||
file('relay/a@b.js', 'runtime-javascript', 'c')
|
||||
)
|
||||
expect(() =>
|
||||
createSshRelayRuntimeSbom({ identity: collision, archive, sourceDateEpoch: 1_752_710_400 })
|
||||
).toThrow(/duplicate SPDX identifier/i)
|
||||
})
|
||||
|
||||
it('requires the exact archive digest to make the document namespace immutable', () => {
|
||||
expect(() =>
|
||||
createSshRelayRuntimeSbom({
|
||||
identity: identity(),
|
||||
archive: { sha256: 'missing-prefix' },
|
||||
sourceDateEpoch: 1_752_710_400
|
||||
})
|
||||
).toThrow(/archive SHA-256/i)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,252 @@
|
||||
import { execFile } from 'node:child_process'
|
||||
import { createHash } from 'node:crypto'
|
||||
import { createReadStream } from 'node:fs'
|
||||
import { readdir, realpath, stat } from 'node:fs/promises'
|
||||
import { createRequire } from 'node:module'
|
||||
import { dirname, isAbsolute, join, relative } from 'node:path'
|
||||
import { isDeepStrictEqual, promisify } from 'node:util'
|
||||
|
||||
const require = createRequire(import.meta.url)
|
||||
const execFileAsync = promisify(execFile)
|
||||
const MAX_TOOL_BYTES = 300 * 1024 * 1024
|
||||
const TOOL_TIMEOUT_MS = 60_000
|
||||
const TOOL_OUTPUT_BYTES = 1024 * 1024
|
||||
const MAX_PACKAGE_FILES = 500
|
||||
const MAX_PACKAGE_BYTES = 32 * 1024 * 1024
|
||||
|
||||
async function sha256File(path) {
|
||||
const metadata = await stat(path)
|
||||
if (!metadata.isFile() || metadata.size > MAX_TOOL_BYTES) {
|
||||
throw new Error(`Runtime build tool is not a bounded regular file: ${path}`)
|
||||
}
|
||||
const hash = createHash('sha256')
|
||||
for await (const chunk of createReadStream(path)) {
|
||||
hash.update(chunk)
|
||||
}
|
||||
return `sha256:${hash.digest('hex')}`
|
||||
}
|
||||
|
||||
async function capture(command, args) {
|
||||
try {
|
||||
return await execFileAsync(command, args, {
|
||||
encoding: 'utf8',
|
||||
maxBuffer: TOOL_OUTPUT_BYTES,
|
||||
timeout: TOOL_TIMEOUT_MS,
|
||||
windowsHide: true
|
||||
})
|
||||
} catch (error) {
|
||||
if (typeof error?.stdout === 'string' || typeof error?.stderr === 'string') {
|
||||
return { stdout: error.stdout ?? '', stderr: error.stderr ?? '' }
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export function selectSshRelayRuntimeToolVersion({ stdout = '', stderr = '' }, pattern) {
|
||||
const lines = `${stderr}\n${stdout}`
|
||||
.split(/\r?\n/)
|
||||
.map((line) => line.trim())
|
||||
.filter(Boolean)
|
||||
const version = pattern ? lines.find((line) => pattern.test(line)) : lines[0]
|
||||
if (!version || Buffer.byteLength(version) > 512) {
|
||||
throw new Error('Runtime build tool did not report a bounded version line')
|
||||
}
|
||||
return version
|
||||
}
|
||||
|
||||
async function locateExecutable(command, { xcrun = false } = {}) {
|
||||
if (isAbsolute(command)) {
|
||||
return realpath(command)
|
||||
}
|
||||
const locator = xcrun
|
||||
? ['xcrun', ['--find', command]]
|
||||
: process.platform === 'win32'
|
||||
? ['where.exe', [command]]
|
||||
: ['which', [command]]
|
||||
const result = await capture(...locator)
|
||||
const path = result.stdout
|
||||
.split(/\r?\n/)
|
||||
.map((line) => line.trim())
|
||||
.find(Boolean)
|
||||
if (!path || !isAbsolute(path)) {
|
||||
throw new Error(`Runtime build tool executable could not be resolved: ${command}`)
|
||||
}
|
||||
return realpath(path)
|
||||
}
|
||||
|
||||
async function executableRecord({
|
||||
command,
|
||||
args,
|
||||
versionCommand,
|
||||
versionArgs,
|
||||
versionPattern,
|
||||
xcrun = false
|
||||
}) {
|
||||
const path = await locateExecutable(command, { xcrun })
|
||||
const result = await capture(versionCommand ?? path, versionArgs ?? args)
|
||||
return {
|
||||
version: selectSshRelayRuntimeToolVersion(result, versionPattern),
|
||||
sha256: await sha256File(path)
|
||||
}
|
||||
}
|
||||
|
||||
async function packageTreeRecord(name) {
|
||||
const parsed = require(`${name}/package.json`)
|
||||
const root = dirname(require.resolve(`${name}/package.json`))
|
||||
const files = []
|
||||
let totalBytes = 0
|
||||
async function visit(directory) {
|
||||
const entries = await readdir(directory, { withFileTypes: true })
|
||||
entries.sort((left, right) => (left.name < right.name ? -1 : left.name > right.name ? 1 : 0))
|
||||
for (const entry of entries) {
|
||||
const path = join(directory, entry.name)
|
||||
if (entry.isDirectory()) {
|
||||
await visit(path)
|
||||
} else if (entry.isFile()) {
|
||||
files.push(path)
|
||||
totalBytes += (await stat(path)).size
|
||||
} else {
|
||||
throw new Error(`Runtime build package contains a special entry: ${name}`)
|
||||
}
|
||||
if (files.length > MAX_PACKAGE_FILES) {
|
||||
throw new Error(`Runtime build package exceeds the file-count bound: ${name}`)
|
||||
}
|
||||
if (totalBytes > MAX_PACKAGE_BYTES) {
|
||||
throw new Error(`Runtime build package exceeds the byte bound: ${name}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
await visit(root)
|
||||
const hash = createHash('sha256')
|
||||
for (const path of files) {
|
||||
hash.update(relative(root, path).replaceAll('\\', '/'))
|
||||
hash.update('\0')
|
||||
hash.update(await sha256File(path))
|
||||
hash.update('\n')
|
||||
}
|
||||
return { version: parsed.version, sha256: `sha256:${hash.digest('hex')}`, files: files.length }
|
||||
}
|
||||
|
||||
export function sshRelayRuntimeBuilderIdentity({ gitCommit, env = process.env }) {
|
||||
if (env.GITHUB_ACTIONS !== 'true') {
|
||||
return `local://${process.platform}/${process.arch}`
|
||||
}
|
||||
if (
|
||||
!/^[0-9a-f]{40}$/.test(gitCommit) ||
|
||||
!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(env.GITHUB_REPOSITORY ?? '')
|
||||
) {
|
||||
throw new Error('GitHub runtime builder identity is incomplete')
|
||||
}
|
||||
const workflow = (env.GITHUB_WORKFLOW_REF ?? '').split('@')[0]
|
||||
const prefix = `${env.GITHUB_REPOSITORY}/`
|
||||
if (
|
||||
!workflow.startsWith(prefix) ||
|
||||
workflow.slice(prefix.length) !== '.github/workflows/ssh-relay-runtime-artifacts.yml'
|
||||
) {
|
||||
throw new Error('GitHub runtime builder workflow identity is malformed')
|
||||
}
|
||||
return `https://github.com/${env.GITHUB_REPOSITORY}/blob/${gitCommit}/${workflow.slice(prefix.length)}`
|
||||
}
|
||||
|
||||
export function sshRelayRuntimeRunnerIdentity({ env = process.env } = {}) {
|
||||
const github = env.GITHUB_ACTIONS === 'true'
|
||||
const identity = {
|
||||
os: env.RUNNER_OS ?? process.platform,
|
||||
architecture: env.RUNNER_ARCH ?? process.arch,
|
||||
environment: env.RUNNER_ENVIRONMENT ?? (github ? undefined : 'local'),
|
||||
requestedLabel: env.ORCA_RUNTIME_REQUESTED_RUNNER ?? (github ? undefined : 'local'),
|
||||
image: {
|
||||
os: env.ImageOS ?? (github ? undefined : process.platform),
|
||||
version: env.ImageVersion ?? (github ? undefined : 'local')
|
||||
}
|
||||
}
|
||||
if (
|
||||
!identity.os ||
|
||||
!identity.architecture ||
|
||||
!identity.environment ||
|
||||
!identity.requestedLabel ||
|
||||
!identity.image.os ||
|
||||
!identity.image.version
|
||||
) {
|
||||
throw new Error('Runtime runner identity is incomplete')
|
||||
}
|
||||
return identity
|
||||
}
|
||||
|
||||
export function assertSshRelayRuntimeToolchain(toolchain, tuple) {
|
||||
const common = ['buildNode', 'bundledNode', 'compiler', 'buildSystem', 'python', 'archive']
|
||||
const expected = tuple.startsWith('win32-')
|
||||
? [...common, 'linker', 'nodeAddonApi', 'nodeGyp']
|
||||
: [...common, 'nodeAddonApi', 'nodeGyp', 'strip']
|
||||
if (!isDeepStrictEqual(Object.keys(toolchain).sort(), expected.sort())) {
|
||||
throw new Error(`Runtime toolchain record is incomplete for ${tuple}`)
|
||||
}
|
||||
for (const [name, record] of Object.entries(toolchain)) {
|
||||
const keys = Object.keys(record).sort()
|
||||
const expectedKeys =
|
||||
record.files === undefined ? ['sha256', 'version'] : ['files', 'sha256', 'version']
|
||||
if (
|
||||
!isDeepStrictEqual(keys, expectedKeys) ||
|
||||
typeof record.version !== 'string' ||
|
||||
record.version.length === 0 ||
|
||||
Buffer.byteLength(record.version) > 512 ||
|
||||
record.version.includes('\n') ||
|
||||
!/^sha256:[0-9a-f]{64}$/.test(record.sha256) ||
|
||||
(record.files !== undefined && (!Number.isSafeInteger(record.files) || record.files <= 0))
|
||||
) {
|
||||
throw new Error(`Runtime toolchain record is malformed: ${name}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function collectSshRelayRuntimeToolchain(nodePath) {
|
||||
const common = {
|
||||
bundledNode: await executableRecord({ command: nodePath, args: ['--version'] }),
|
||||
buildNode: await executableRecord({ command: process.execPath, args: ['--version'] }),
|
||||
nodeGyp: await packageTreeRecord('node-gyp'),
|
||||
nodeAddonApi: await packageTreeRecord('node-addon-api')
|
||||
}
|
||||
if (process.platform === 'win32') {
|
||||
const toolchain = {
|
||||
...common,
|
||||
compiler: await executableRecord({
|
||||
command: 'cl.exe',
|
||||
args: [],
|
||||
versionPattern: /Compiler Version/i
|
||||
}),
|
||||
linker: await executableRecord({
|
||||
command: 'link.exe',
|
||||
args: [],
|
||||
versionPattern: /Linker Version/i
|
||||
}),
|
||||
buildSystem: await executableRecord({
|
||||
command: 'msbuild.exe',
|
||||
args: ['-version', '-nologo']
|
||||
}),
|
||||
python: await executableRecord({ command: 'python.exe', args: ['--version'] }),
|
||||
archive: await packageTreeRecord('yazl')
|
||||
}
|
||||
assertSshRelayRuntimeToolchain(toolchain, 'win32-x64')
|
||||
return toolchain
|
||||
}
|
||||
const toolchain = {
|
||||
...common,
|
||||
compiler: await executableRecord({
|
||||
command: process.platform === 'darwin' ? 'clang++' : 'c++',
|
||||
args: ['--version'],
|
||||
xcrun: process.platform === 'darwin'
|
||||
}),
|
||||
buildSystem: await executableRecord({ command: 'make', args: ['--version'] }),
|
||||
python: await executableRecord({ command: 'python3', args: ['--version'] }),
|
||||
archive: await executableRecord({ command: 'xz', args: ['--version'] }),
|
||||
strip: await executableRecord({
|
||||
command: 'strip',
|
||||
args: [],
|
||||
versionCommand: process.platform === 'darwin' ? 'xcodebuild' : undefined,
|
||||
versionArgs: process.platform === 'darwin' ? ['-version'] : undefined,
|
||||
xcrun: process.platform === 'darwin'
|
||||
})
|
||||
}
|
||||
assertSshRelayRuntimeToolchain(toolchain, `${process.platform}-x64`)
|
||||
return toolchain
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import {
|
||||
assertSshRelayRuntimeToolchain,
|
||||
collectSshRelayRuntimeToolchain,
|
||||
selectSshRelayRuntimeToolVersion,
|
||||
sshRelayRuntimeBuilderIdentity,
|
||||
sshRelayRuntimeRunnerIdentity
|
||||
} from './ssh-relay-runtime-toolchain.mjs'
|
||||
|
||||
const commit = 'a'.repeat(40)
|
||||
|
||||
describe('SSH relay runtime build provenance', () => {
|
||||
it('selects the real Windows compiler version instead of its stdout usage line', () => {
|
||||
expect(
|
||||
selectSshRelayRuntimeToolVersion(
|
||||
{
|
||||
stdout: 'usage: cl [ option... ] filename... [ /link linkoption... ]',
|
||||
stderr: [
|
||||
'Microsoft (R) C/C++ Optimizing Compiler Version 19.44.35228 for ARM64',
|
||||
'Copyright (C) Microsoft Corporation.'
|
||||
].join('\r\n')
|
||||
},
|
||||
/Compiler Version/i
|
||||
)
|
||||
).toBe('Microsoft (R) C/C++ Optimizing Compiler Version 19.44.35228 for ARM64')
|
||||
})
|
||||
|
||||
it('pins GitHub builder identity to the exact source commit', () => {
|
||||
expect(
|
||||
sshRelayRuntimeBuilderIdentity({
|
||||
gitCommit: commit,
|
||||
env: {
|
||||
GITHUB_ACTIONS: 'true',
|
||||
GITHUB_REPOSITORY: 'stablyai/orca',
|
||||
GITHUB_WORKFLOW_REF:
|
||||
'stablyai/orca/.github/workflows/ssh-relay-runtime-artifacts.yml@refs/pull/8741/merge'
|
||||
}
|
||||
})
|
||||
).toBe(
|
||||
`https://github.com/stablyai/orca/blob/${commit}/.github/workflows/ssh-relay-runtime-artifacts.yml`
|
||||
)
|
||||
expect(() =>
|
||||
sshRelayRuntimeBuilderIdentity({
|
||||
gitCommit: commit,
|
||||
env: {
|
||||
GITHUB_ACTIONS: 'true',
|
||||
GITHUB_REPOSITORY: 'stablyai/orca',
|
||||
GITHUB_WORKFLOW_REF: 'other/repo/.github/workflows/untrusted.yml@refs/heads/main'
|
||||
}
|
||||
})
|
||||
).toThrow(/workflow identity/i)
|
||||
})
|
||||
|
||||
it('requires the resolved runner label, architecture, environment, and image identity', () => {
|
||||
expect(
|
||||
sshRelayRuntimeRunnerIdentity({
|
||||
env: {
|
||||
GITHUB_ACTIONS: 'true',
|
||||
RUNNER_OS: 'Windows',
|
||||
RUNNER_ARCH: 'ARM64',
|
||||
RUNNER_ENVIRONMENT: 'github-hosted',
|
||||
ORCA_RUNTIME_REQUESTED_RUNNER: 'windows-11-arm',
|
||||
ImageOS: 'win11-arm64',
|
||||
ImageVersion: '20260706.102.1'
|
||||
}
|
||||
})
|
||||
).toEqual({
|
||||
os: 'Windows',
|
||||
architecture: 'ARM64',
|
||||
environment: 'github-hosted',
|
||||
requestedLabel: 'windows-11-arm',
|
||||
image: { os: 'win11-arm64', version: '20260706.102.1' }
|
||||
})
|
||||
expect(() =>
|
||||
sshRelayRuntimeRunnerIdentity({
|
||||
env: {
|
||||
GITHUB_ACTIONS: 'true',
|
||||
RUNNER_OS: 'Windows',
|
||||
RUNNER_ARCH: 'ARM64'
|
||||
}
|
||||
})
|
||||
).toThrow(/runner identity/i)
|
||||
})
|
||||
|
||||
it('records bounded native tool versions and SHA-256 executable or code digests', async () => {
|
||||
const toolchain = await collectSshRelayRuntimeToolchain(process.execPath)
|
||||
const tuple = process.platform === 'win32' ? 'win32-x64' : 'linux-x64-glibc'
|
||||
expect(() => assertSshRelayRuntimeToolchain(toolchain, tuple)).not.toThrow()
|
||||
expect(toolchain).toMatchObject({
|
||||
bundledNode: { version: process.version },
|
||||
buildNode: { version: process.version },
|
||||
nodeGyp: { version: expect.any(String) },
|
||||
nodeAddonApi: { version: expect.any(String) },
|
||||
compiler: { version: expect.any(String) },
|
||||
buildSystem: { version: expect.any(String) },
|
||||
python: { version: expect.any(String) },
|
||||
archive: { version: expect.any(String) }
|
||||
})
|
||||
for (const record of Object.values(toolchain)) {
|
||||
expect(record.sha256).toMatch(/^sha256:[0-9a-f]{64}$/)
|
||||
expect(Buffer.byteLength(record.version)).toBeGreaterThan(0)
|
||||
expect(Buffer.byteLength(record.version)).toBeLessThanOrEqual(512)
|
||||
}
|
||||
expect(() =>
|
||||
assertSshRelayRuntimeToolchain(
|
||||
{ ...toolchain, compiler: { version: toolchain.compiler.version } },
|
||||
tuple
|
||||
)
|
||||
).toThrow(/compiler/i)
|
||||
})
|
||||
})
|
||||
@@ -28,6 +28,7 @@ describe('SSH relay runtime artifact workflow', () => {
|
||||
expect(posixJob['timeout-minutes']).toBe(20)
|
||||
expect(windowsJob['timeout-minutes']).toBe(30)
|
||||
for (const job of [posixJob, windowsJob]) {
|
||||
expect(job.env.ORCA_RUNTIME_REQUESTED_RUNNER).toBe('${{ matrix.runner }}')
|
||||
expect(job.steps[0].with.ref).toBe('${{ github.event.pull_request.head.sha || github.sha }}')
|
||||
for (const step of job.steps.filter((candidate) => candidate.uses)) {
|
||||
expect(step.uses).toMatch(/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+@[0-9a-f]{40}$/)
|
||||
@@ -62,6 +63,9 @@ describe('SSH relay runtime artifact workflow', () => {
|
||||
expect(source).toContain('ssh-relay-runtime-reproducibility.mjs')
|
||||
expect(source).toContain('ssh-relay-runtime-reproducibility.test.mjs')
|
||||
expect(source.match(/ssh-relay-runtime-closure\.test\.mjs/g)).toHaveLength(4)
|
||||
expect(source.match(/ssh-relay-runtime-sbom\.test\.mjs/g)).toHaveLength(4)
|
||||
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(/node --check config\/scripts\/ssh-relay-runtime-closure\.mjs/g)
|
||||
).toHaveLength(2)
|
||||
|
||||
Reference in New Issue
Block a user