| Detected local or remote cache corruption |
@@ -1552,6 +1655,15 @@ Prove production behavior, failure behavior, and rollback behavior.
runner class or a dedicated runner when hosted-runner variance would make the agreed
threshold inconclusive.
+
+ Qualifying jobs pin explicit native labels: ubuntu-24.04 and
+ ubuntu-24.04-arm, macos-15-intel and macos-15,
+ plus windows-2022 and windows-11-arm. GitHub refreshes these
+ images weekly, so each job logs the resolved image version; *-latest and
+ third-party runner labels do not qualify a tuple merely because another repository
+ workflow uses them. Hosted capacity is unreserved, and no approved self-hosted SSH
+ target pool or cross-family endpoint is assumed.
+
Release gates
@@ -1709,8 +1821,11 @@ Resolve these before default-on.
OS-native trust controls
- Preserve official Node signatures where possible, sign Orca-built native code, and
- test macOS Gatekeeper plus Windows Authenticode, WDAC, and antivirus behavior.
+ Test quarantined bytes on clean macOS 13.5 x64/arm64 snapshots with Gatekeeper and
+ strict codesign. Test Server 2022 x64 and Windows 11 24H2 arm64 with current
+ Defender intelligence, then Windows 11 24H2 x64 with the release WDAC policy in
+ audit and enforced modes. Record snapshot, policy, and security-intelligence IDs;
+ any denial blocks release rather than weakening the endpoint control.
diff --git a/src/main/ssh/ssh-relay-artifact-consistency.ts b/src/main/ssh/ssh-relay-artifact-consistency.ts
new file mode 100644
index 00000000000..4272beef7f1
--- /dev/null
+++ b/src/main/ssh/ssh-relay-artifact-consistency.ts
@@ -0,0 +1,168 @@
+import type { SshRelayRuntimeTuple } from './ssh-relay-artifact-schema'
+import {
+ assertSafeSshRelayArtifactPath,
+ foldSshRelayArtifactPath
+} from './ssh-relay-artifact-path-policy'
+import { sshRelayRuntimeArchiveName } from './ssh-relay-release-asset'
+import { computeSshRelayRuntimeContentId, type SshRelayDigest } from './ssh-relay-runtime-identity'
+
+function expectedTupleId(tuple: SshRelayRuntimeTuple): string {
+ if (tuple.os === 'linux' && tuple.compatibility.kind === 'linux') {
+ return `linux-${tuple.architecture}-${tuple.compatibility.libc.family}`
+ }
+ return `${tuple.os}-${tuple.architecture}`
+}
+
+function assertRequiredRuntimeEntries(tuple: SshRelayRuntimeTuple): void {
+ const files = tuple.entries.filter((entry) => entry.type === 'file')
+ const expectedPaths = new Map([
+ ['node', tuple.os === 'win32' ? 'node.exe' : 'bin/node'],
+ ['relay', 'relay.js'],
+ ['relay-watcher', 'relay-watcher.js']
+ ])
+ for (const role of [
+ 'node',
+ 'relay',
+ 'relay-watcher',
+ 'node-pty-native',
+ 'parcel-watcher-native'
+ ]) {
+ const matching = files.filter((file) => file.role === role)
+ if (matching.length !== 1) {
+ throw new Error(`Runtime tuple ${tuple.tupleId} requires exactly one ${role} entry`)
+ }
+ const expectedPath = expectedPaths.get(role)
+ if (expectedPath && matching[0].path !== expectedPath) {
+ throw new Error(`Runtime tuple ${tuple.tupleId} has invalid ${role} path`)
+ }
+ }
+ for (const requiredPath of [
+ 'node_modules/node-pty/package.json',
+ 'node_modules/@parcel/watcher/package.json'
+ ]) {
+ if (!files.some((file) => file.path === requiredPath)) {
+ throw new Error(`Runtime tuple ${tuple.tupleId} is missing ${requiredPath}`)
+ }
+ }
+ if (!files.some((file) => file.role === 'license')) {
+ throw new Error(`Runtime tuple ${tuple.tupleId} requires bundled license metadata`)
+ }
+ const node = files.find((file) => file.role === 'node')
+ if (node?.mode !== 0o755) {
+ throw new Error(`Runtime tuple ${tuple.tupleId} requires executable bundled Node mode`)
+ }
+}
+
+function assertEntryTree(tuple: SshRelayRuntimeTuple): void {
+ const paths = new Set()
+ const foldedPaths = new Set()
+ const directories = new Set(
+ tuple.entries.filter((entry) => entry.type === 'directory').map((entry) => entry.path)
+ )
+ for (const entry of tuple.entries) {
+ assertSafeSshRelayArtifactPath(entry.path)
+ const folded = foldSshRelayArtifactPath(entry.path)
+ if (paths.has(entry.path) || foldedPaths.has(folded)) {
+ throw new Error(`Runtime tuple ${tuple.tupleId} has colliding path: ${entry.path}`)
+ }
+ paths.add(entry.path)
+ foldedPaths.add(folded)
+ const separator = entry.path.lastIndexOf('/')
+ if (separator > 0 && !directories.has(entry.path.slice(0, separator))) {
+ throw new Error(`Runtime tuple ${tuple.tupleId} has undeclared parent for ${entry.path}`)
+ }
+ }
+}
+
+function expectedWatcherPackage(tuple: SshRelayRuntimeTuple): string {
+ if (tuple.os === 'linux' && tuple.compatibility.kind === 'linux') {
+ return `node_modules/@parcel/watcher-linux-${tuple.architecture}-${tuple.compatibility.libc.family}`
+ }
+ return `node_modules/@parcel/watcher-${tuple.os}-${tuple.architecture}`
+}
+
+function assertNativePackages(tuple: SshRelayRuntimeTuple): void {
+ const watcherPackage = expectedWatcherPackage(tuple)
+ for (const entry of tuple.entries) {
+ if (
+ entry.path.startsWith('node_modules/@parcel/watcher-') &&
+ entry.path !== watcherPackage &&
+ !entry.path.startsWith(`${watcherPackage}/`)
+ ) {
+ throw new Error(`Runtime tuple ${tuple.tupleId} contains an extra native watcher package`)
+ }
+ }
+ const expectedPolicy =
+ tuple.os === 'linux'
+ ? 'linux-hash-only-v1'
+ : tuple.os === 'darwin'
+ ? 'apple-developer-id-v1'
+ : 'signpath-authenticode-v1'
+ if (tuple.nativeVerification.policy !== expectedPolicy) {
+ throw new Error(`Runtime tuple ${tuple.tupleId} has the wrong native verification policy`)
+ }
+}
+
+function assertNativeAttestation(tuple: SshRelayRuntimeTuple): void {
+ const attestations = new Map()
+ for (const attestation of tuple.nativeVerification.files) {
+ if (attestations.has(attestation.path)) {
+ throw new Error(`Runtime tuple ${tuple.tupleId} has duplicate native attestation path`)
+ }
+ attestations.set(attestation.path, attestation.sha256)
+ }
+ const requiredRoles = new Set([
+ 'node',
+ 'node-pty-native',
+ 'parcel-watcher-native',
+ 'native-runtime'
+ ])
+ for (const entry of tuple.entries) {
+ if (entry.type !== 'file' || !requiredRoles.has(entry.role)) {
+ continue
+ }
+ if (attestations.get(entry.path) !== entry.sha256) {
+ throw new Error(
+ `Runtime tuple ${tuple.tupleId} has an invalid attested hash for ${entry.path}`
+ )
+ }
+ }
+ for (const [path, hash] of attestations) {
+ const entry = tuple.entries.find((candidate) => candidate.path === path)
+ if (!entry || entry.type !== 'file' || entry.sha256 !== hash) {
+ throw new Error(
+ `Runtime tuple ${tuple.tupleId} attests a missing or mismatched file: ${path}`
+ )
+ }
+ }
+}
+
+export function assertSshRelayRuntimeTupleConsistency(tuple: SshRelayRuntimeTuple): void {
+ const expectedCompatibilityKind = tuple.os === 'win32' ? 'windows' : tuple.os
+ if (
+ expectedTupleId(tuple) !== tuple.tupleId ||
+ tuple.compatibility.kind !== expectedCompatibilityKind
+ ) {
+ throw new Error(`Runtime tuple identity does not match its platform fields: ${tuple.tupleId}`)
+ }
+ assertEntryTree(tuple)
+ assertRequiredRuntimeEntries(tuple)
+ assertNativePackages(tuple)
+ assertNativeAttestation(tuple)
+
+ const files = tuple.entries.filter((entry) => entry.type === 'file')
+ const expandedSize = files.reduce((total, file) => total + file.size, 0)
+ if (tuple.archive.fileCount !== files.length) {
+ throw new Error(`Runtime tuple ${tuple.tupleId} archive file count is inconsistent`)
+ }
+ if (tuple.archive.expandedSize !== expandedSize) {
+ throw new Error(`Runtime tuple ${tuple.tupleId} archive expanded size is inconsistent`)
+ }
+ const contentId = computeSshRelayRuntimeContentId(tuple)
+ if (tuple.contentId !== contentId) {
+ throw new Error(`Runtime tuple ${tuple.tupleId} content identity is inconsistent`)
+ }
+ if (tuple.archive.name !== sshRelayRuntimeArchiveName(tuple.tupleId, tuple.contentId)) {
+ throw new Error(`Runtime tuple ${tuple.tupleId} archive name is inconsistent`)
+ }
+}
diff --git a/src/main/ssh/ssh-relay-artifact-path-policy.ts b/src/main/ssh/ssh-relay-artifact-path-policy.ts
new file mode 100644
index 00000000000..504206de3cb
--- /dev/null
+++ b/src/main/ssh/ssh-relay-artifact-path-policy.ts
@@ -0,0 +1,44 @@
+export const SSH_RELAY_MAX_RELATIVE_PATH_BYTES = 240
+export const SSH_RELAY_MAX_PATH_DEPTH = 32
+
+const PORTABLE_PATH = /^[A-Za-z0-9._@+/-]+$/
+const WINDOWS_DEVICE_NAME = /^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/i
+
+export function assertSafeSshRelayArtifactPath(relativePath: string): void {
+ const fail = (reason: string): never => {
+ throw new Error(`Unsafe artifact path "${relativePath}": ${reason}`)
+ }
+
+ if (
+ !relativePath ||
+ Buffer.byteLength(relativePath, 'utf8') > SSH_RELAY_MAX_RELATIVE_PATH_BYTES
+ ) {
+ fail('empty or over the byte limit')
+ }
+ if (!PORTABLE_PATH.test(relativePath)) {
+ fail('contains non-portable characters or separators')
+ }
+ if (relativePath.startsWith('/') || relativePath.startsWith('//')) {
+ fail('absolute and UNC paths are forbidden')
+ }
+
+ const segments = relativePath.split('/')
+ if (segments.length > SSH_RELAY_MAX_PATH_DEPTH) {
+ fail('nesting depth exceeds the limit')
+ }
+ for (const segment of segments) {
+ if (!segment || segment === '.' || segment === '..') {
+ fail('empty and dot segments are forbidden')
+ }
+ if (segment.endsWith('.') || segment.endsWith(' ')) {
+ fail('Windows-trimmed suffix is forbidden')
+ }
+ if (WINDOWS_DEVICE_NAME.test(segment)) {
+ fail('Windows device name is forbidden')
+ }
+ }
+}
+
+export function foldSshRelayArtifactPath(relativePath: string): string {
+ return relativePath.toLowerCase()
+}
diff --git a/src/main/ssh/ssh-relay-artifact-schema.test.ts b/src/main/ssh/ssh-relay-artifact-schema.test.ts
new file mode 100644
index 00000000000..c057c627818
--- /dev/null
+++ b/src/main/ssh/ssh-relay-artifact-schema.test.ts
@@ -0,0 +1,218 @@
+import { describe, expect, it } from 'vitest'
+
+import { createSshRelayArtifactTestManifest } from './ssh-relay-artifact-test-manifest'
+import { parseSshRelayArtifactManifest } from './ssh-relay-artifact-schema'
+
+describe('SSH relay artifact manifest schema', () => {
+ it('accepts a complete internally consistent manifest', () => {
+ const parsed = parseSshRelayArtifactManifest(createSshRelayArtifactTestManifest())
+
+ expect(parsed.schemaVersion).toBe(1)
+ expect(parsed.tuples[0].tupleId).toBe('linux-x64-glibc')
+ })
+
+ it('rejects unsupported schema versions, extra fields, and non-canonical timestamps', () => {
+ const unsupported = createSshRelayArtifactTestManifest() as unknown as Record
+ unsupported.schemaVersion = 2
+ expect(() => parseSshRelayArtifactManifest(unsupported)).toThrow()
+
+ const extra = createSshRelayArtifactTestManifest() as unknown as Record
+ extra.latest = true
+ expect(() => parseSshRelayArtifactManifest(extra)).toThrow()
+
+ const invalidDate = createSshRelayArtifactTestManifest()
+ invalidDate.createdAt = '2026-02-31T00:00:00.000Z'
+ expect(() => parseSshRelayArtifactManifest(invalidDate)).toThrow(/timestamp/i)
+ })
+
+ it('rejects build identity that disagrees with the exact tag', () => {
+ const manifest = createSshRelayArtifactTestManifest()
+ manifest.build.channel = 'stable'
+
+ expect(() => parseSshRelayArtifactManifest(manifest)).toThrow(/build identity/i)
+ })
+
+ it('rejects duplicate tuple identities', () => {
+ const manifest = createSshRelayArtifactTestManifest()
+ manifest.tuples.push(structuredClone(manifest.tuples[0]))
+
+ expect(() => parseSshRelayArtifactManifest(manifest)).toThrow(/duplicate tuple/i)
+ })
+
+ it('rejects exact and case-folded entry collisions', () => {
+ for (const path of ['relay.js', 'RELAY.JS']) {
+ const manifest = createSshRelayArtifactTestManifest()
+ manifest.tuples[0].entries.push({
+ path,
+ type: 'file',
+ role: 'runtime-javascript',
+ size: 1,
+ mode: 0o644,
+ sha256: `sha256:${'e'.repeat(64)}`
+ })
+ expect(() => parseSshRelayArtifactManifest(manifest), path).toThrow(/colliding path/i)
+ }
+ })
+
+ it.each([
+ '/absolute',
+ '../escape',
+ 'nested/../escape',
+ 'C:/drive',
+ '//server/share',
+ 'node_modules\\escape',
+ 'relay.js:stream',
+ 'CON',
+ 'aux.txt',
+ 'trailing.',
+ 'white space',
+ `${'a/'.repeat(32)}file`,
+ 'a'.repeat(241)
+ ])('rejects unsafe portable path %s', (path) => {
+ const manifest = createSshRelayArtifactTestManifest()
+ manifest.tuples[0].entries[1].path = path
+
+ expect(() => parseSshRelayArtifactManifest(manifest)).toThrow(/unsafe artifact path/i)
+ })
+
+ it('rejects inconsistent aggregate counts and sizes', () => {
+ const countMismatch = createSshRelayArtifactTestManifest()
+ countMismatch.tuples[0].archive.fileCount += 1
+ expect(() => parseSshRelayArtifactManifest(countMismatch)).toThrow(/file count/i)
+
+ const sizeMismatch = createSshRelayArtifactTestManifest()
+ sizeMismatch.tuples[0].archive.expandedSize += 1
+ expect(() => parseSshRelayArtifactManifest(sizeMismatch)).toThrow(/expanded size/i)
+ })
+
+ it('rejects archive, expanded-tree, per-file, and entry-count limit violations', () => {
+ const archive = createSshRelayArtifactTestManifest()
+ archive.tuples[0].archive.size = 100 * 1024 * 1024 + 1
+ expect(() => parseSshRelayArtifactManifest(archive)).toThrow()
+
+ const expanded = createSshRelayArtifactTestManifest()
+ expanded.tuples[0].archive.expandedSize = 350 * 1024 * 1024 + 1
+ expect(() => parseSshRelayArtifactManifest(expanded)).toThrow()
+
+ const file = createSshRelayArtifactTestManifest()
+ const relay = file.tuples[0].entries.find(
+ (entry) => entry.type === 'file' && entry.role === 'relay'
+ )
+ if (!relay || relay.type !== 'file') {
+ throw new Error('test fixture missing relay file')
+ }
+ relay.size = 250 * 1024 * 1024 + 1
+ expect(() => parseSshRelayArtifactManifest(file)).toThrow()
+
+ const entries = createSshRelayArtifactTestManifest()
+ entries.tuples[0].entries = Array.from({ length: 5_001 }, (_, index) => ({
+ path: `entry-${index}`,
+ type: 'directory' as const,
+ mode: 0o755 as const
+ }))
+ expect(() => parseSshRelayArtifactManifest(entries)).toThrow()
+ })
+
+ it('rejects undeclared parents and non-file archive entry types', () => {
+ const missingParent = createSshRelayArtifactTestManifest()
+ missingParent.tuples[0].entries[1].path = 'missing/node'
+ expect(() => parseSshRelayArtifactManifest(missingParent)).toThrow(/undeclared parent/i)
+
+ const symlink = createSshRelayArtifactTestManifest()
+ symlink.tuples[0].entries.push({
+ path: 'node-link',
+ type: 'symlink',
+ target: 'bin/node'
+ } as never)
+ expect(() => parseSshRelayArtifactManifest(symlink)).toThrow()
+ })
+
+ it('rejects a missing required executable role', () => {
+ const manifest = createSshRelayArtifactTestManifest()
+ manifest.tuples[0].entries = manifest.tuples[0].entries.filter(
+ (entry) => entry.type !== 'file' || entry.role !== 'node-pty-native'
+ )
+
+ expect(() => parseSshRelayArtifactManifest(manifest)).toThrow(/node-pty-native/i)
+ })
+
+ it('requires bundled license metadata and executable bundled Node mode', () => {
+ const missingLicense = createSshRelayArtifactTestManifest()
+ missingLicense.tuples[0].entries = missingLicense.tuples[0].entries.filter(
+ (entry) => entry.type !== 'file' || entry.role !== 'license'
+ )
+ expect(() => parseSshRelayArtifactManifest(missingLicense)).toThrow(/license/i)
+
+ const nonExecutableNode = createSshRelayArtifactTestManifest()
+ const node = nonExecutableNode.tuples[0].entries.find(
+ (entry) => entry.type === 'file' && entry.role === 'node'
+ )
+ if (!node || node.type !== 'file') {
+ throw new Error('test fixture missing Node')
+ }
+ node.mode = 0o644
+ expect(() => parseSshRelayArtifactManifest(nonExecutableNode)).toThrow(/executable.*Node/i)
+ })
+
+ it('rejects a native optional package for another tuple', () => {
+ const manifest = createSshRelayArtifactTestManifest()
+ for (const entry of manifest.tuples[0].entries) {
+ entry.path = entry.path.replace('watcher-linux-x64-glibc', 'watcher-linux-arm64-glibc')
+ }
+
+ expect(() => parseSshRelayArtifactManifest(manifest)).toThrow(/native watcher package/i)
+ })
+
+ it('rejects native attestation hashes that do not match runtime bytes', () => {
+ const manifest = createSshRelayArtifactTestManifest()
+ manifest.tuples[0].nativeVerification.files[0].sha256 = `sha256:${'f'.repeat(64)}`
+
+ expect(() => parseSshRelayArtifactManifest(manifest)).toThrow(/attested hash/i)
+ })
+
+ it('rejects duplicate native attestation paths and the wrong platform policy', () => {
+ const duplicate = createSshRelayArtifactTestManifest()
+ duplicate.tuples[0].nativeVerification.files.push(
+ structuredClone(duplicate.tuples[0].nativeVerification.files[0])
+ )
+ expect(() => parseSshRelayArtifactManifest(duplicate)).toThrow(/duplicate native attestation/i)
+
+ const wrongPolicy = createSshRelayArtifactTestManifest()
+ wrongPolicy.tuples[0].nativeVerification.policy = 'apple-developer-id-v1'
+ expect(() => parseSshRelayArtifactManifest(wrongPolicy)).toThrow(/verification policy/i)
+ })
+
+ it('rejects duplicate signature keys and malformed signature bytes', () => {
+ const duplicate = createSshRelayArtifactTestManifest()
+ duplicate.signatures.push(structuredClone(duplicate.signatures[0]))
+ expect(() => parseSshRelayArtifactManifest(duplicate)).toThrow(/duplicate signature/i)
+
+ const malformed = createSshRelayArtifactTestManifest()
+ malformed.signatures[0].signature = Buffer.alloc(63).toString('base64')
+ expect(() => parseSshRelayArtifactManifest(malformed)).toThrow(/64 bytes/i)
+
+ const algorithm = createSshRelayArtifactTestManifest()
+ algorithm.signatures[0].algorithm = 'rsa-v1' as never
+ expect(() => parseSshRelayArtifactManifest(algorithm)).toThrow()
+ })
+
+ it('rejects a content identity or archive name that is not derived from the runtime', () => {
+ const identityMismatch = createSshRelayArtifactTestManifest()
+ identityMismatch.tuples[0].contentId = `sha256:${'f'.repeat(64)}`
+ expect(() => parseSshRelayArtifactManifest(identityMismatch)).toThrow(/content identity/i)
+
+ const nameMismatch = createSshRelayArtifactTestManifest()
+ nameMismatch.tuples[0].archive.name = 'latest.tar.xz'
+ expect(() => parseSshRelayArtifactManifest(nameMismatch)).toThrow(/archive name/i)
+ })
+
+ it('rejects platform-field conflicts and non-canonical digests', () => {
+ const conflict = createSshRelayArtifactTestManifest()
+ conflict.tuples[0].architecture = 'arm64'
+ expect(() => parseSshRelayArtifactManifest(conflict)).toThrow(/platform fields/i)
+
+ const digest = createSshRelayArtifactTestManifest()
+ digest.tuples[0].archive.sha256 = `sha256:${'A'.repeat(64)}`
+ expect(() => parseSshRelayArtifactManifest(digest)).toThrow()
+ })
+})
diff --git a/src/main/ssh/ssh-relay-artifact-schema.ts b/src/main/ssh/ssh-relay-artifact-schema.ts
new file mode 100644
index 00000000000..c9c699aa92a
--- /dev/null
+++ b/src/main/ssh/ssh-relay-artifact-schema.ts
@@ -0,0 +1,268 @@
+import { z } from 'zod'
+
+import { assertSshRelayRuntimeTupleConsistency } from './ssh-relay-artifact-consistency'
+import { parseSshRelayReleaseTag } from './ssh-relay-release-asset'
+import type { SshRelayDigest, SshRelayRuntimeIdentityInput } from './ssh-relay-runtime-identity'
+
+const MAX_ARCHIVE_SIZE = 100 * 1024 * 1024
+const MAX_EXPANDED_SIZE = 350 * 1024 * 1024
+const MAX_FILE_SIZE = 250 * 1024 * 1024
+const MAX_ENTRIES = 5_000
+const VERSION = /^\d+\.\d+(?:\.\d+)?(?:[-+][0-9A-Za-z.-]+)?$/
+const NUMERIC_VERSION = /^\d+\.\d+(?:\.\d+)?$/
+const OPENSSH_VERSION = /^\d+\.\d+p\d+$/
+const ASSET_NAME = /^[A-Za-z0-9._-]+$/
+const TIMESTAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/
+
+const digestSchema = z.string().regex(/^sha256:[0-9a-f]{64}$/)
+const safeSizeSchema = z.number().int().min(0).max(Number.MAX_SAFE_INTEGER)
+const versionSchema = z.string().regex(VERSION).max(64)
+const numericVersionSchema = z.string().regex(NUMERIC_VERSION).max(64)
+const timestampSchema = z
+ .string()
+ .regex(TIMESTAMP)
+ .refine(
+ (value) => {
+ const milliseconds = Date.parse(value)
+ return !Number.isNaN(milliseconds) && new Date(milliseconds).toISOString() === value
+ },
+ { message: 'invalid UTC timestamp' }
+ )
+
+const directoryEntrySchema = z
+ .object({ path: z.string(), type: z.literal('directory'), mode: z.literal(0o755) })
+ .strict()
+const fileEntrySchema = z
+ .object({
+ path: z.string(),
+ type: z.literal('file'),
+ role: z.enum([
+ 'node',
+ 'relay',
+ 'relay-watcher',
+ 'node-pty-native',
+ 'parcel-watcher-native',
+ 'native-runtime',
+ 'runtime-javascript',
+ 'license'
+ ]),
+ size: safeSizeSchema.max(MAX_FILE_SIZE),
+ mode: z.union([z.literal(0o644), z.literal(0o755)]),
+ sha256: digestSchema
+ })
+ .strict()
+const entrySchema = z.discriminatedUnion('type', [directoryEntrySchema, fileEntrySchema])
+
+const glibcSchema = z
+ .object({
+ family: z.literal('glibc'),
+ minimumVersion: numericVersionSchema,
+ minimumLibstdcxxVersion: numericVersionSchema,
+ minimumGlibcxxVersion: numericVersionSchema
+ })
+ .strict()
+const muslSchema = z
+ .object({
+ family: z.literal('musl'),
+ minimumVersion: numericVersionSchema,
+ minimumLibstdcxxVersion: z.null(),
+ minimumGlibcxxVersion: z.null()
+ })
+ .strict()
+const compatibilitySchema = z.discriminatedUnion('kind', [
+ z
+ .object({
+ kind: z.literal('linux'),
+ minimumKernelVersion: numericVersionSchema,
+ libc: z.discriminatedUnion('family', [glibcSchema, muslSchema])
+ })
+ .strict(),
+ z.object({ kind: z.literal('darwin'), minimumVersion: numericVersionSchema }).strict(),
+ z
+ .object({
+ kind: z.literal('windows'),
+ minimumBuild: safeSizeSchema,
+ minimumOpenSshVersion: z.string().regex(OPENSSH_VERSION).max(64),
+ minimumPowerShellVersion: numericVersionSchema,
+ minimumDotNetFrameworkRelease: safeSizeSchema
+ })
+ .strict()
+])
+
+const metadataAssetSchema = z
+ .object({ name: z.string().regex(ASSET_NAME), size: safeSizeSchema, sha256: digestSchema })
+ .strict()
+const nativeVerificationSchema = z
+ .object({
+ policy: z.enum(['linux-hash-only-v1', 'apple-developer-id-v1', 'signpath-authenticode-v1']),
+ tool: z
+ .object({ name: z.string().regex(ASSET_NAME), version: z.string().min(1).max(64) })
+ .strict(),
+ verifiedAt: timestampSchema,
+ files: z
+ .array(z.object({ path: z.string(), sha256: digestSchema }).strict())
+ .min(1)
+ .max(MAX_ENTRIES)
+ })
+ .strict()
+
+const tupleSchema = z
+ .object({
+ tupleId: z.enum([
+ 'linux-x64-glibc',
+ 'linux-arm64-glibc',
+ 'linux-x64-musl',
+ 'linux-arm64-musl',
+ 'darwin-x64',
+ 'darwin-arm64',
+ 'win32-x64',
+ 'win32-arm64'
+ ]),
+ os: z.enum(['linux', 'darwin', 'win32']),
+ architecture: z.enum(['x64', 'arm64']),
+ compatibility: compatibilitySchema,
+ nodeVersion: versionSchema,
+ dependencies: z
+ .object({ nodePtyVersion: versionSchema, parcelWatcherVersion: versionSchema })
+ .strict(),
+ entries: z.array(entrySchema).min(1).max(MAX_ENTRIES),
+ contentId: digestSchema,
+ archive: z
+ .object({
+ name: z.string().regex(ASSET_NAME),
+ size: safeSizeSchema.max(MAX_ARCHIVE_SIZE),
+ expandedSize: safeSizeSchema.max(MAX_EXPANDED_SIZE),
+ fileCount: safeSizeSchema.max(MAX_ENTRIES),
+ sha256: digestSchema
+ })
+ .strict(),
+ metadataAssets: z
+ .object({ sbom: metadataAssetSchema, provenance: metadataAssetSchema })
+ .strict(),
+ nativeVerification: nativeVerificationSchema
+ })
+ .strict()
+
+const signatureSchema = z
+ .object({
+ algorithm: z.literal('ed25519-v1'),
+ keyId: digestSchema,
+ signature: z
+ .string()
+ .regex(/^[A-Za-z0-9+/]+={0,2}$/)
+ .refine(
+ (value) => {
+ const decoded = Buffer.from(value, 'base64')
+ return decoded.length === 64 && decoded.toString('base64') === value
+ },
+ { message: 'Ed25519 signature must be canonical base64 encoding of 64 bytes' }
+ )
+ })
+ .strict()
+
+const unsignedManifestSchema = z
+ .object({
+ schemaVersion: z.literal(1),
+ build: z
+ .object({
+ tag: z.string(),
+ channel: z.enum(['stable', 'rc', 'perf']),
+ version: z.string(),
+ relayProtocolVersion: z.number().int().min(1).max(Number.MAX_SAFE_INTEGER)
+ })
+ .strict(),
+ createdAt: timestampSchema,
+ tuples: z.array(tupleSchema).min(1).max(8)
+ })
+ .strict()
+const manifestSchema = unsignedManifestSchema
+ .extend({ signatures: z.array(signatureSchema).min(1).max(4) })
+ .strict()
+
+export type SshRelayManifestSignature = {
+ algorithm: 'ed25519-v1'
+ keyId: SshRelayDigest
+ signature: string
+}
+export type SshRelayRuntimeTuple = SshRelayRuntimeIdentityInput & {
+ contentId: SshRelayDigest
+ archive: {
+ name: string
+ size: number
+ expandedSize: number
+ fileCount: number
+ sha256: SshRelayDigest
+ }
+ metadataAssets: {
+ sbom: { name: string; size: number; sha256: SshRelayDigest }
+ provenance: { name: string; size: number; sha256: SshRelayDigest }
+ }
+ nativeVerification: {
+ policy: 'linux-hash-only-v1' | 'apple-developer-id-v1' | 'signpath-authenticode-v1'
+ tool: { name: string; version: string }
+ verifiedAt: string
+ files: { path: string; sha256: SshRelayDigest }[]
+ }
+}
+export type SshRelayUnsignedArtifactManifest = {
+ schemaVersion: 1
+ build: {
+ tag: string
+ channel: 'stable' | 'rc' | 'perf'
+ version: string
+ relayProtocolVersion: number
+ }
+ createdAt: string
+ tuples: SshRelayRuntimeTuple[]
+}
+export type SshRelayArtifactManifest = SshRelayUnsignedArtifactManifest & {
+ signatures: SshRelayManifestSignature[]
+}
+
+function validateUnsignedManifest(
+ parsed: SshRelayUnsignedArtifactManifest
+): SshRelayUnsignedArtifactManifest {
+ const release = parseSshRelayReleaseTag(parsed.build.tag)
+ if (release.channel !== parsed.build.channel || release.version !== parsed.build.version) {
+ throw new Error('SSH relay manifest build identity does not match its exact release tag')
+ }
+ const tupleIds = new Set()
+ for (const tuple of parsed.tuples) {
+ if (tupleIds.has(tuple.tupleId)) {
+ throw new Error(`SSH relay manifest has duplicate tuple: ${tuple.tupleId}`)
+ }
+ tupleIds.add(tuple.tupleId)
+ assertSshRelayRuntimeTupleConsistency(tuple)
+ }
+ return parsed
+}
+
+function withoutSignatures(input: unknown): unknown {
+ if (!input || typeof input !== 'object' || Array.isArray(input)) {
+ return input
+ }
+ const { signatures: _signatures, ...unsigned } = input as Record
+ return unsigned
+}
+
+export function parseSshRelayUnsignedArtifactManifest(
+ input: unknown
+): SshRelayUnsignedArtifactManifest {
+ const parsed = unsignedManifestSchema.parse(
+ withoutSignatures(input)
+ ) as unknown as SshRelayUnsignedArtifactManifest
+ return validateUnsignedManifest(parsed)
+}
+
+export function parseSshRelayArtifactManifest(input: unknown): SshRelayArtifactManifest {
+ const parsed = manifestSchema.parse(input) as unknown as SshRelayArtifactManifest
+ validateUnsignedManifest(parsed)
+ const signatureKeys = new Set()
+ for (const signature of parsed.signatures) {
+ if (signatureKeys.has(signature.keyId)) {
+ throw new Error(`SSH relay manifest has duplicate signature key: ${signature.keyId}`)
+ }
+ signatureKeys.add(signature.keyId)
+ }
+ return parsed
+}
diff --git a/src/main/ssh/ssh-relay-artifact-selector.test.ts b/src/main/ssh/ssh-relay-artifact-selector.test.ts
new file mode 100644
index 00000000000..b63a095143f
--- /dev/null
+++ b/src/main/ssh/ssh-relay-artifact-selector.test.ts
@@ -0,0 +1,193 @@
+import { describe, expect, it } from 'vitest'
+
+import { createSshRelayArtifactTestManifest } from './ssh-relay-artifact-test-manifest'
+import { parseSshRelayArtifactManifest } from './ssh-relay-artifact-schema'
+import { selectSshRelayArtifact } from './ssh-relay-artifact-selector'
+import { computeSshRelayRuntimeContentId } from './ssh-relay-runtime-identity'
+import { sshRelayRuntimeArchiveName } from './ssh-relay-release-asset'
+
+const compatibleLinuxHost = {
+ os: 'linux' as const,
+ architecture: 'x64' as const,
+ processTranslated: false,
+ kernelVersion: '6.8.0',
+ libc: { family: 'glibc' as const, version: '2.39' },
+ libstdcxxVersion: '6.0.33',
+ glibcxxVersion: '3.4.33'
+}
+
+function windowsManifest() {
+ const manifest = createSshRelayArtifactTestManifest()
+ const tuple = structuredClone(manifest.tuples[0]) as unknown as Record
+ Object.assign(tuple, {
+ tupleId: 'win32-x64',
+ os: 'win32',
+ architecture: 'x64',
+ compatibility: {
+ kind: 'windows',
+ minimumBuild: 20348,
+ minimumOpenSshVersion: '8.1p1',
+ minimumPowerShellVersion: '5.1',
+ minimumDotNetFrameworkRelease: 528040
+ }
+ })
+ manifest.tuples = [tuple as never]
+ return manifest
+}
+
+const compatibleWindowsHost = {
+ os: 'win32' as const,
+ architecture: 'x64' as const,
+ processTranslated: false,
+ build: 20348,
+ openSshVersion: '8.1p1',
+ powerShellVersion: '5.1',
+ dotNetFrameworkRelease: 528040
+}
+
+describe('SSH relay artifact selector', () => {
+ it('selects the single compatible glibc tuple', () => {
+ const result = selectSshRelayArtifact(createSshRelayArtifactTestManifest(), compatibleLinuxHost)
+
+ expect(result).toMatchObject({ kind: 'selected', tupleId: 'linux-x64-glibc' })
+ })
+
+ it('accepts every Linux compatibility value at its exact minimum', () => {
+ expect(
+ selectSshRelayArtifact(createSshRelayArtifactTestManifest(), {
+ ...compatibleLinuxHost,
+ kernelVersion: '4.18',
+ libc: { family: 'glibc', version: '2.28' },
+ libstdcxxVersion: '6.0.25',
+ glibcxxVersion: '3.4.25'
+ }).kind
+ ).toBe('selected')
+ })
+
+ it('selects the detected libc family when both Linux variants exist', () => {
+ const manifest = createSshRelayArtifactTestManifest()
+ const musl = structuredClone(manifest.tuples[0])
+ musl.tupleId = 'linux-x64-musl'
+ musl.compatibility = {
+ kind: 'linux',
+ minimumKernelVersion: '4.18',
+ libc: {
+ family: 'musl',
+ minimumVersion: '1.2.5',
+ minimumLibstdcxxVersion: null,
+ minimumGlibcxxVersion: null
+ }
+ }
+ for (const entry of musl.entries) {
+ entry.path = entry.path.replace('watcher-linux-x64-glibc', 'watcher-linux-x64-musl')
+ }
+ for (const attestation of musl.nativeVerification.files) {
+ attestation.path = attestation.path.replace(
+ 'watcher-linux-x64-glibc',
+ 'watcher-linux-x64-musl'
+ )
+ }
+ musl.contentId = computeSshRelayRuntimeContentId(musl)
+ musl.archive.name = sshRelayRuntimeArchiveName(musl.tupleId, musl.contentId)
+ manifest.tuples.push(musl)
+ const parsed = parseSshRelayArtifactManifest(manifest)
+
+ expect(selectSshRelayArtifact(parsed, compatibleLinuxHost)).toMatchObject({
+ kind: 'selected',
+ tupleId: 'linux-x64-glibc'
+ })
+ expect(
+ selectSshRelayArtifact(parsed, {
+ ...compatibleLinuxHost,
+ libc: { family: 'musl', version: '1.2.5' }
+ })
+ ).toMatchObject({ kind: 'selected', tupleId: 'linux-x64-musl' })
+ })
+
+ it.each([
+ [{ ...compatibleLinuxHost, kernelVersion: '4.17' }, 'kernel-too-old'],
+ [
+ { ...compatibleLinuxHost, libc: { family: 'glibc' as const, version: '2.27' } },
+ 'libc-too-old'
+ ],
+ [{ ...compatibleLinuxHost, glibcxxVersion: '3.4.24' }, 'libstdcxx-too-old'],
+ [{ ...compatibleLinuxHost, libstdcxxVersion: undefined }, 'unknown-libstdcxx'],
+ [{ ...compatibleLinuxHost, kernelVersion: 'not-a-version' }, 'unknown-kernel'],
+ [{ ...compatibleLinuxHost, libc: { family: 'unknown' as const } }, 'unknown-libc'],
+ [
+ { ...compatibleLinuxHost, libc: { family: 'musl' as const, version: '1.2.5' } },
+ 'tuple-unavailable'
+ ],
+ [{ ...compatibleLinuxHost, processTranslated: true }, 'translated-process'],
+ [{ ...compatibleLinuxHost, architecture: 'arm64' as const }, 'tuple-unavailable']
+ ])('selects legacy for incompatible or unknown host evidence', (host, reason) => {
+ expect(selectSshRelayArtifact(createSshRelayArtifactTestManifest(), host)).toEqual({
+ kind: 'legacy',
+ reason
+ })
+ })
+
+ it('checks Windows bootstrap versions before selection', () => {
+ const manifest = windowsManifest()
+ expect(selectSshRelayArtifact(manifest, compatibleWindowsHost).kind).toBe('selected')
+ expect(selectSshRelayArtifact(manifest, { ...compatibleWindowsHost, build: 20347 })).toEqual({
+ kind: 'legacy',
+ reason: 'os-too-old'
+ })
+ expect(
+ selectSshRelayArtifact(manifest, {
+ ...compatibleWindowsHost,
+ openSshVersion: '8.0p1'
+ })
+ ).toEqual({ kind: 'legacy', reason: 'openssh-too-old' })
+ expect(
+ selectSshRelayArtifact(manifest, {
+ ...compatibleWindowsHost,
+ powerShellVersion: '5.0'
+ })
+ ).toEqual({ kind: 'legacy', reason: 'powershell-too-old' })
+ expect(
+ selectSshRelayArtifact(manifest, {
+ ...compatibleWindowsHost,
+ dotNetFrameworkRelease: 528039
+ })
+ ).toEqual({ kind: 'legacy', reason: 'dotnet-too-old' })
+ })
+
+ it('selects legacy when Windows bootstrap evidence is absent or malformed', () => {
+ const manifest = windowsManifest()
+ expect(
+ selectSshRelayArtifact(manifest, { ...compatibleWindowsHost, openSshVersion: '8.1.0' })
+ ).toEqual({ kind: 'legacy', reason: 'unknown-openssh' })
+ expect(
+ selectSshRelayArtifact(manifest, { ...compatibleWindowsHost, powerShellVersion: undefined })
+ ).toEqual({ kind: 'legacy', reason: 'unknown-powershell' })
+ expect(
+ selectSshRelayArtifact(manifest, {
+ ...compatibleWindowsHost,
+ dotNetFrameworkRelease: undefined
+ })
+ ).toEqual({ kind: 'legacy', reason: 'unknown-dotnet' })
+ })
+
+ it('checks the minimum macOS version before selection', () => {
+ const manifest = createSshRelayArtifactTestManifest()
+ const tuple = structuredClone(manifest.tuples[0]) as unknown as Record
+ Object.assign(tuple, {
+ tupleId: 'darwin-arm64',
+ os: 'darwin',
+ architecture: 'arm64',
+ compatibility: { kind: 'darwin', minimumVersion: '13.5' }
+ })
+ manifest.tuples = [tuple as never]
+
+ expect(
+ selectSshRelayArtifact(manifest, {
+ os: 'darwin',
+ architecture: 'arm64',
+ processTranslated: false,
+ version: '13.4'
+ })
+ ).toEqual({ kind: 'legacy', reason: 'os-too-old' })
+ })
+})
diff --git a/src/main/ssh/ssh-relay-artifact-selector.ts b/src/main/ssh/ssh-relay-artifact-selector.ts
new file mode 100644
index 00000000000..32a118eca75
--- /dev/null
+++ b/src/main/ssh/ssh-relay-artifact-selector.ts
@@ -0,0 +1,258 @@
+import type { SshRelayArtifactManifest, SshRelayRuntimeTuple } from './ssh-relay-artifact-schema'
+
+type SshRelayHostBase = {
+ architecture: 'x64' | 'arm64'
+ processTranslated: boolean
+}
+
+export type SshRelayLinuxHostEvidence = SshRelayHostBase & {
+ os: 'linux'
+ kernelVersion?: string
+ libc: { family: 'glibc' | 'musl'; version?: string } | { family: 'unknown' }
+ libstdcxxVersion?: string
+ glibcxxVersion?: string
+}
+
+export type SshRelayDarwinHostEvidence = SshRelayHostBase & {
+ os: 'darwin'
+ version?: string
+}
+
+export type SshRelayWindowsHostEvidence = SshRelayHostBase & {
+ os: 'win32'
+ build?: number
+ openSshVersion?: string
+ powerShellVersion?: string
+ dotNetFrameworkRelease?: number
+}
+
+export type SshRelayHostEvidence =
+ | SshRelayLinuxHostEvidence
+ | SshRelayDarwinHostEvidence
+ | SshRelayWindowsHostEvidence
+
+export type SshRelayArtifactLegacyReason =
+ | 'tuple-inconsistent'
+ | 'tuple-unavailable'
+ | 'tuple-ambiguous'
+ | 'translated-process'
+ | 'unknown-kernel'
+ | 'kernel-too-old'
+ | 'unknown-libc'
+ | 'libc-too-old'
+ | 'unknown-libstdcxx'
+ | 'libstdcxx-too-old'
+ | 'unknown-os-version'
+ | 'os-too-old'
+ | 'unknown-openssh'
+ | 'openssh-too-old'
+ | 'unknown-powershell'
+ | 'powershell-too-old'
+ | 'unknown-dotnet'
+ | 'dotnet-too-old'
+
+export type SshRelayArtifactSelection =
+ | { kind: 'selected'; tupleId: SshRelayRuntimeTuple['tupleId']; tuple: SshRelayRuntimeTuple }
+ | { kind: 'legacy'; reason: SshRelayArtifactLegacyReason }
+
+function parseNumericVersion(value: string | undefined): number[] | null {
+ if (!value) {
+ return null
+ }
+ const match = /^(\d+(?:\.\d+){1,3})(?:-[0-9A-Za-z.-]+)?$/.exec(value)
+ if (!match) {
+ return null
+ }
+ const components = match[1].split('.').map(Number)
+ return components.every(Number.isSafeInteger) ? components : null
+}
+
+function parseOpenSshVersion(value: string | undefined): number[] | null {
+ if (!value) {
+ return null
+ }
+ const match = /^(\d+)\.(\d+)p(\d+)$/.exec(value)
+ if (!match) {
+ return null
+ }
+ const components = match.slice(1).map(Number)
+ return components.every(Number.isSafeInteger) ? components : null
+}
+
+function compareComponents(left: number[], right: number[]): number {
+ const length = Math.max(left.length, right.length)
+ for (let index = 0; index < length; index += 1) {
+ const difference = (left[index] ?? 0) - (right[index] ?? 0)
+ if (difference !== 0) {
+ return difference
+ }
+ }
+ return 0
+}
+
+function meetsVersion(
+ actual: string | undefined,
+ minimum: string,
+ parser = parseNumericVersion
+): boolean | null {
+ const actualParts = parser(actual)
+ const minimumParts = parser(minimum)
+ if (!actualParts || !minimumParts) {
+ return null
+ }
+ return compareComponents(actualParts, minimumParts) >= 0
+}
+
+function selectLinux(
+ tuple: SshRelayRuntimeTuple,
+ host: SshRelayLinuxHostEvidence
+): SshRelayArtifactSelection {
+ if (tuple.compatibility.kind !== 'linux') {
+ return { kind: 'legacy', reason: 'tuple-inconsistent' }
+ }
+ const kernel = meetsVersion(host.kernelVersion, tuple.compatibility.minimumKernelVersion)
+ if (kernel === null) {
+ return { kind: 'legacy', reason: 'unknown-kernel' }
+ }
+ if (!kernel) {
+ return { kind: 'legacy', reason: 'kernel-too-old' }
+ }
+ if (host.libc.family === 'unknown') {
+ return { kind: 'legacy', reason: 'unknown-libc' }
+ }
+ if (host.libc.family !== tuple.compatibility.libc.family) {
+ return { kind: 'legacy', reason: 'tuple-unavailable' }
+ }
+ const libc = meetsVersion(host.libc.version, tuple.compatibility.libc.minimumVersion)
+ if (libc === null) {
+ return { kind: 'legacy', reason: 'unknown-libc' }
+ }
+ if (!libc) {
+ return { kind: 'legacy', reason: 'libc-too-old' }
+ }
+
+ if (tuple.compatibility.libc.family === 'glibc') {
+ const libstdcxx = meetsVersion(
+ host.libstdcxxVersion,
+ tuple.compatibility.libc.minimumLibstdcxxVersion
+ )
+ const glibcxx = meetsVersion(
+ host.glibcxxVersion,
+ tuple.compatibility.libc.minimumGlibcxxVersion
+ )
+ if (libstdcxx === null || glibcxx === null) {
+ return { kind: 'legacy', reason: 'unknown-libstdcxx' }
+ }
+ if (!libstdcxx || !glibcxx) {
+ return { kind: 'legacy', reason: 'libstdcxx-too-old' }
+ }
+ }
+ return { kind: 'selected', tupleId: tuple.tupleId, tuple }
+}
+
+function selectDarwin(
+ tuple: SshRelayRuntimeTuple,
+ host: SshRelayDarwinHostEvidence
+): SshRelayArtifactSelection {
+ if (tuple.compatibility.kind !== 'darwin') {
+ return { kind: 'legacy', reason: 'tuple-inconsistent' }
+ }
+ const compatible = meetsVersion(host.version, tuple.compatibility.minimumVersion)
+ if (compatible === null) {
+ return { kind: 'legacy', reason: 'unknown-os-version' }
+ }
+ return compatible
+ ? { kind: 'selected', tupleId: tuple.tupleId, tuple }
+ : { kind: 'legacy', reason: 'os-too-old' }
+}
+
+function selectWindows(
+ tuple: SshRelayRuntimeTuple,
+ host: SshRelayWindowsHostEvidence
+): SshRelayArtifactSelection {
+ if (tuple.compatibility.kind !== 'windows') {
+ return { kind: 'legacy', reason: 'tuple-inconsistent' }
+ }
+ if (typeof host.build !== 'number' || !Number.isSafeInteger(host.build)) {
+ return { kind: 'legacy', reason: 'unknown-os-version' }
+ }
+ if (host.build < tuple.compatibility.minimumBuild) {
+ return { kind: 'legacy', reason: 'os-too-old' }
+ }
+ const openSsh = meetsVersion(
+ host.openSshVersion,
+ tuple.compatibility.minimumOpenSshVersion,
+ parseOpenSshVersion
+ )
+ if (openSsh === null) {
+ return { kind: 'legacy', reason: 'unknown-openssh' }
+ }
+ if (!openSsh) {
+ return { kind: 'legacy', reason: 'openssh-too-old' }
+ }
+ const powerShell = meetsVersion(
+ host.powerShellVersion,
+ tuple.compatibility.minimumPowerShellVersion
+ )
+ if (powerShell === null) {
+ return { kind: 'legacy', reason: 'unknown-powershell' }
+ }
+ if (!powerShell) {
+ return { kind: 'legacy', reason: 'powershell-too-old' }
+ }
+ if (
+ typeof host.dotNetFrameworkRelease !== 'number' ||
+ !Number.isSafeInteger(host.dotNetFrameworkRelease)
+ ) {
+ return { kind: 'legacy', reason: 'unknown-dotnet' }
+ }
+ if (host.dotNetFrameworkRelease < tuple.compatibility.minimumDotNetFrameworkRelease) {
+ return { kind: 'legacy', reason: 'dotnet-too-old' }
+ }
+ return { kind: 'selected', tupleId: tuple.tupleId, tuple }
+}
+
+export function selectSshRelayArtifact(
+ manifest: SshRelayArtifactManifest,
+ host: SshRelayHostEvidence
+): SshRelayArtifactSelection {
+ // Why: translated and ambiguous process boundaries stay on the proven legacy path until their
+ // own live SSH evidence exists; selecting a native artifact here would be an unsafe guess.
+ if (host.processTranslated) {
+ return { kind: 'legacy', reason: 'translated-process' }
+ }
+ const platformCandidates = manifest.tuples.filter(
+ (tuple) => tuple.os === host.os && tuple.architecture === host.architecture
+ )
+ if (host.os === 'linux' && host.libc.family === 'unknown') {
+ return { kind: 'legacy', reason: 'unknown-libc' }
+ }
+ const candidates =
+ host.os === 'linux'
+ ? platformCandidates.filter(
+ (tuple) =>
+ tuple.compatibility.kind === 'linux' &&
+ tuple.compatibility.libc.family === host.libc.family
+ )
+ : platformCandidates
+ if (candidates.length === 0) {
+ return {
+ kind: 'legacy',
+ reason: platformCandidates.some((tuple) => tuple.compatibility.kind !== host.os)
+ ? 'tuple-inconsistent'
+ : 'tuple-unavailable'
+ }
+ }
+ if (candidates.length > 1) {
+ return { kind: 'legacy', reason: 'tuple-ambiguous' }
+ }
+
+ const tuple = candidates[0]
+ if (host.os === 'linux') {
+ return selectLinux(tuple, host)
+ }
+ if (host.os === 'darwin') {
+ return selectDarwin(tuple, host)
+ }
+ return selectWindows(tuple, host)
+}
diff --git a/src/main/ssh/ssh-relay-artifact-test-manifest.ts b/src/main/ssh/ssh-relay-artifact-test-manifest.ts
new file mode 100644
index 00000000000..72fc5e3befb
--- /dev/null
+++ b/src/main/ssh/ssh-relay-artifact-test-manifest.ts
@@ -0,0 +1,168 @@
+import type { SshRelayArtifactManifest } from './ssh-relay-artifact-schema'
+import {
+ computeSshRelayRuntimeContentId,
+ type SshRelayRuntimeIdentityInput
+} from './ssh-relay-runtime-identity'
+import { sshRelayRuntimeArchiveName } from './ssh-relay-release-asset'
+
+const digest = (character: string): `sha256:${string}` => `sha256:${character.repeat(64)}`
+
+export function createSshRelayArtifactTestManifest(): SshRelayArtifactManifest {
+ const runtime: SshRelayRuntimeIdentityInput = {
+ tupleId: 'linux-x64-glibc',
+ os: 'linux',
+ architecture: 'x64',
+ compatibility: {
+ kind: 'linux',
+ minimumKernelVersion: '4.18',
+ libc: {
+ family: 'glibc',
+ minimumVersion: '2.28',
+ minimumLibstdcxxVersion: '6.0.25',
+ minimumGlibcxxVersion: '3.4.25'
+ }
+ },
+ nodeVersion: '24.18.0',
+ dependencies: {
+ nodePtyVersion: '1.1.0',
+ parcelWatcherVersion: '2.5.6'
+ },
+ entries: [
+ { path: 'bin', type: 'directory', mode: 0o755 },
+ {
+ path: 'bin/node',
+ type: 'file',
+ role: 'node',
+ size: 40,
+ mode: 0o755,
+ sha256: digest('1')
+ },
+ {
+ path: 'relay.js',
+ type: 'file',
+ role: 'relay',
+ size: 20,
+ mode: 0o644,
+ sha256: digest('2')
+ },
+ {
+ path: 'relay-watcher.js',
+ type: 'file',
+ role: 'relay-watcher',
+ size: 15,
+ mode: 0o644,
+ sha256: digest('3')
+ },
+ { path: 'node_modules', type: 'directory', mode: 0o755 },
+ { path: 'node_modules/node-pty', type: 'directory', mode: 0o755 },
+ {
+ path: 'node_modules/node-pty/package.json',
+ type: 'file',
+ role: 'runtime-javascript',
+ size: 10,
+ mode: 0o644,
+ sha256: digest('4')
+ },
+ { path: 'node_modules/node-pty/build', type: 'directory', mode: 0o755 },
+ { path: 'node_modules/node-pty/build/Release', type: 'directory', mode: 0o755 },
+ {
+ path: 'node_modules/node-pty/build/Release/pty.node',
+ type: 'file',
+ role: 'node-pty-native',
+ size: 30,
+ mode: 0o755,
+ sha256: digest('5')
+ },
+ { path: 'node_modules/@parcel', type: 'directory', mode: 0o755 },
+ { path: 'node_modules/@parcel/watcher', type: 'directory', mode: 0o755 },
+ {
+ path: 'node_modules/@parcel/watcher/package.json',
+ type: 'file',
+ role: 'runtime-javascript',
+ size: 10,
+ mode: 0o644,
+ sha256: digest('6')
+ },
+ {
+ path: 'node_modules/@parcel/watcher-linux-x64-glibc',
+ type: 'directory',
+ mode: 0o755
+ },
+ {
+ path: 'node_modules/@parcel/watcher-linux-x64-glibc/watcher.node',
+ type: 'file',
+ role: 'parcel-watcher-native',
+ size: 25,
+ mode: 0o755,
+ sha256: digest('7')
+ },
+ {
+ path: 'THIRD_PARTY_LICENSES.txt',
+ type: 'file',
+ role: 'license',
+ size: 10,
+ mode: 0o644,
+ sha256: digest('8')
+ }
+ ]
+ }
+ const contentId = computeSshRelayRuntimeContentId(runtime)
+ const fileEntries = runtime.entries.filter((entry) => entry.type === 'file')
+ const expandedSize = fileEntries.reduce((total, entry) => total + entry.size, 0)
+
+ return {
+ schemaVersion: 1,
+ build: {
+ tag: 'v1.4.140-rc.1',
+ channel: 'rc',
+ version: '1.4.140-rc.1',
+ relayProtocolVersion: 1
+ },
+ createdAt: '2026-07-14T00:00:00.000Z',
+ tuples: [
+ {
+ ...runtime,
+ contentId,
+ archive: {
+ name: sshRelayRuntimeArchiveName(runtime.tupleId, contentId),
+ size: 100,
+ expandedSize,
+ fileCount: fileEntries.length,
+ sha256: digest('a')
+ },
+ metadataAssets: {
+ sbom: {
+ name: 'orca-ssh-relay-runtime-linux-x64-glibc.spdx.json',
+ size: 50,
+ sha256: digest('b')
+ },
+ provenance: {
+ name: 'orca-ssh-relay-runtime-linux-x64-glibc.provenance.json',
+ size: 50,
+ sha256: digest('c')
+ }
+ },
+ nativeVerification: {
+ policy: 'linux-hash-only-v1',
+ tool: { name: 'sha256sum', version: '9.4' },
+ verifiedAt: '2026-07-14T00:00:00.000Z',
+ files: [
+ { path: 'bin/node', sha256: digest('1') },
+ { path: 'node_modules/node-pty/build/Release/pty.node', sha256: digest('5') },
+ {
+ path: 'node_modules/@parcel/watcher-linux-x64-glibc/watcher.node',
+ sha256: digest('7')
+ }
+ ]
+ }
+ }
+ ],
+ signatures: [
+ {
+ algorithm: 'ed25519-v1',
+ keyId: digest('d'),
+ signature: Buffer.alloc(64).toString('base64')
+ }
+ ]
+ }
+}
diff --git a/src/main/ssh/ssh-relay-manifest-signature.test.ts b/src/main/ssh/ssh-relay-manifest-signature.test.ts
new file mode 100644
index 00000000000..474888430dd
--- /dev/null
+++ b/src/main/ssh/ssh-relay-manifest-signature.test.ts
@@ -0,0 +1,143 @@
+import { createHash } from 'node:crypto'
+
+import nacl from 'tweetnacl'
+import { describe, expect, it } from 'vitest'
+
+import { createSshRelayArtifactTestManifest } from './ssh-relay-artifact-test-manifest'
+import {
+ canonicalUnsignedSshRelayManifestBytes,
+ signSshRelayArtifactManifest,
+ sshRelayManifestKeyId,
+ verifySshRelayArtifactManifest
+} from './ssh-relay-manifest-signature'
+
+const keyPair = nacl.sign.keyPair.fromSeed(Uint8Array.from({ length: 32 }, (_, index) => index))
+
+function signedManifest() {
+ const manifest = createSshRelayArtifactTestManifest()
+ manifest.signatures = [signSshRelayArtifactManifest(manifest, keyPair.secretKey)]
+ return manifest
+}
+
+function unsignedManifest() {
+ const { signatures: _signatures, ...unsigned } = createSshRelayArtifactTestManifest()
+ return unsigned
+}
+
+describe('SSH relay manifest signatures', () => {
+ it('canonicalizes object keys independently of insertion order', () => {
+ const manifest = createSshRelayArtifactTestManifest()
+ const reordered = {
+ signatures: manifest.signatures,
+ tuples: manifest.tuples,
+ createdAt: manifest.createdAt,
+ build: manifest.build,
+ schemaVersion: manifest.schemaVersion
+ }
+
+ expect(canonicalUnsignedSshRelayManifestBytes(reordered)).toEqual(
+ canonicalUnsignedSshRelayManifestBytes(manifest)
+ )
+ })
+
+ it('has a fixed canonical unsigned test vector and ignores array insertion order', () => {
+ const manifest = createSshRelayArtifactTestManifest()
+ const reordered = structuredClone(manifest)
+ reordered.tuples[0].entries.reverse()
+ reordered.tuples[0].nativeVerification.files.reverse()
+ reordered.signatures[0].signature = Buffer.alloc(64, 1).toString('base64')
+
+ const canonical = canonicalUnsignedSshRelayManifestBytes(manifest)
+ expect(canonicalUnsignedSshRelayManifestBytes(reordered)).toEqual(canonical)
+ expect(createHash('sha256').update(canonical).digest('hex')).toBe(
+ 'e78bf4416628a91055035dc7926035cbf633f29d3618be34e041c6dc5e0794fb'
+ )
+ })
+
+ it('creates the first signature from validated unsigned content', () => {
+ const unsigned = unsignedManifest()
+ const signature = signSshRelayArtifactManifest(unsigned, keyPair.secretKey)
+ const manifest = { ...unsigned, signatures: [signature] }
+
+ expect(
+ verifySshRelayArtifactManifest(manifest, [
+ { keyId: sshRelayManifestKeyId(keyPair.publicKey), publicKey: keyPair.publicKey }
+ ])
+ ).toEqual(manifest)
+ })
+
+ it('rejects invalid key sizes and inconsistent unsigned content before signing', () => {
+ expect(() => sshRelayManifestKeyId(new Uint8Array(31))).toThrow(/32 bytes/i)
+ expect(() => signSshRelayArtifactManifest(unsignedManifest(), new Uint8Array(63))).toThrow(
+ /64 bytes/i
+ )
+
+ const inconsistent = unsignedManifest()
+ inconsistent.tuples[0].contentId = `sha256:${'f'.repeat(64)}`
+ expect(() => signSshRelayArtifactManifest(inconsistent, keyPair.secretKey)).toThrow(
+ /content identity/i
+ )
+ })
+
+ it('verifies a signed manifest with the embedded accepted key', () => {
+ const manifest = signedManifest()
+
+ const verified = verifySshRelayArtifactManifest(manifest, [
+ { keyId: sshRelayManifestKeyId(keyPair.publicKey), publicKey: keyPair.publicKey }
+ ])
+
+ expect(verified.tuples[0].contentId).toBe(manifest.tuples[0].contentId)
+ })
+
+ it('rejects signed content mutation', () => {
+ const manifest = signedManifest()
+ manifest.build.relayProtocolVersion += 1
+
+ expect(() =>
+ verifySshRelayArtifactManifest(manifest, [
+ { keyId: sshRelayManifestKeyId(keyPair.publicKey), publicKey: keyPair.publicKey }
+ ])
+ ).toThrow(/signature/i)
+ })
+
+ it('fails closed for unknown, duplicate, malformed, or mismatched keys', () => {
+ const manifest = signedManifest()
+ expect(() => verifySshRelayArtifactManifest(manifest, [])).toThrow(/unknown signing key/i)
+
+ const duplicate = signedManifest()
+ duplicate.signatures.push(structuredClone(duplicate.signatures[0]))
+ expect(() => verifySshRelayArtifactManifest(duplicate, [])).toThrow(/duplicate signature/i)
+
+ const malformed = signedManifest()
+ malformed.signatures[0].signature = Buffer.alloc(63).toString('base64')
+ expect(() => verifySshRelayArtifactManifest(malformed, [])).toThrow(/64 bytes/i)
+
+ const otherPair = nacl.sign.keyPair()
+ const mismatch = signedManifest()
+ expect(() =>
+ verifySshRelayArtifactManifest(mismatch, [
+ { keyId: mismatch.signatures[0].keyId, publicKey: otherPair.publicKey }
+ ])
+ ).toThrow(/public key id/i)
+ })
+
+ it('accepts dual signatures only when every signing key is accepted', () => {
+ const manifest = signedManifest()
+ const nextKeyPair = nacl.sign.keyPair.fromSeed(
+ Uint8Array.from({ length: 32 }, (_, index) => 31 - index)
+ )
+ manifest.signatures.push(signSshRelayArtifactManifest(manifest, nextKeyPair.secretKey))
+
+ expect(() =>
+ verifySshRelayArtifactManifest(manifest, [
+ { keyId: sshRelayManifestKeyId(keyPair.publicKey), publicKey: keyPair.publicKey }
+ ])
+ ).toThrow(/unknown signing key/i)
+ expect(
+ verifySshRelayArtifactManifest(manifest, [
+ { keyId: sshRelayManifestKeyId(keyPair.publicKey), publicKey: keyPair.publicKey },
+ { keyId: sshRelayManifestKeyId(nextKeyPair.publicKey), publicKey: nextKeyPair.publicKey }
+ ])
+ ).toEqual(manifest)
+ })
+})
diff --git a/src/main/ssh/ssh-relay-manifest-signature.ts b/src/main/ssh/ssh-relay-manifest-signature.ts
new file mode 100644
index 00000000000..f6131d6167b
--- /dev/null
+++ b/src/main/ssh/ssh-relay-manifest-signature.ts
@@ -0,0 +1,198 @@
+import { createHash } from 'node:crypto'
+
+import nacl from 'tweetnacl'
+
+import {
+ parseSshRelayArtifactManifest,
+ parseSshRelayUnsignedArtifactManifest,
+ type SshRelayArtifactManifest,
+ type SshRelayManifestSignature,
+ type SshRelayRuntimeTuple,
+ type SshRelayUnsignedArtifactManifest
+} from './ssh-relay-artifact-schema'
+import type {
+ SshRelayDigest,
+ SshRelayRuntimeCompatibility,
+ SshRelayRuntimeEntry
+} from './ssh-relay-runtime-identity'
+
+export type SshRelayManifestAcceptedKey = {
+ keyId: SshRelayDigest
+ publicKey: Uint8Array
+}
+
+function compareAscii(left: string, right: string): number {
+ return left < right ? -1 : left > right ? 1 : 0
+}
+
+function canonicalCompatibility(compatibility: SshRelayRuntimeCompatibility): object {
+ if (compatibility.kind === 'linux') {
+ return {
+ kind: compatibility.kind,
+ minimumKernelVersion: compatibility.minimumKernelVersion,
+ libc: {
+ family: compatibility.libc.family,
+ minimumVersion: compatibility.libc.minimumVersion,
+ minimumLibstdcxxVersion: compatibility.libc.minimumLibstdcxxVersion,
+ minimumGlibcxxVersion: compatibility.libc.minimumGlibcxxVersion
+ }
+ }
+ }
+ if (compatibility.kind === 'darwin') {
+ return { kind: compatibility.kind, minimumVersion: compatibility.minimumVersion }
+ }
+ return {
+ kind: compatibility.kind,
+ minimumBuild: compatibility.minimumBuild,
+ minimumOpenSshVersion: compatibility.minimumOpenSshVersion,
+ minimumPowerShellVersion: compatibility.minimumPowerShellVersion,
+ minimumDotNetFrameworkRelease: compatibility.minimumDotNetFrameworkRelease
+ }
+}
+
+function canonicalEntry(entry: SshRelayRuntimeEntry): object {
+ if (entry.type === 'directory') {
+ return { path: entry.path, type: entry.type, mode: entry.mode }
+ }
+ return {
+ path: entry.path,
+ type: entry.type,
+ role: entry.role,
+ size: entry.size,
+ mode: entry.mode,
+ sha256: entry.sha256
+ }
+}
+
+function canonicalTuple(tuple: SshRelayRuntimeTuple): object {
+ return {
+ tupleId: tuple.tupleId,
+ os: tuple.os,
+ architecture: tuple.architecture,
+ compatibility: canonicalCompatibility(tuple.compatibility),
+ nodeVersion: tuple.nodeVersion,
+ dependencies: {
+ nodePtyVersion: tuple.dependencies.nodePtyVersion,
+ parcelWatcherVersion: tuple.dependencies.parcelWatcherVersion
+ },
+ entries: [...tuple.entries]
+ .sort((left, right) => compareAscii(left.path, right.path))
+ .map(canonicalEntry),
+ contentId: tuple.contentId,
+ archive: {
+ name: tuple.archive.name,
+ size: tuple.archive.size,
+ expandedSize: tuple.archive.expandedSize,
+ fileCount: tuple.archive.fileCount,
+ sha256: tuple.archive.sha256
+ },
+ metadataAssets: {
+ sbom: {
+ name: tuple.metadataAssets.sbom.name,
+ size: tuple.metadataAssets.sbom.size,
+ sha256: tuple.metadataAssets.sbom.sha256
+ },
+ provenance: {
+ name: tuple.metadataAssets.provenance.name,
+ size: tuple.metadataAssets.provenance.size,
+ sha256: tuple.metadataAssets.provenance.sha256
+ }
+ },
+ nativeVerification: {
+ policy: tuple.nativeVerification.policy,
+ tool: {
+ name: tuple.nativeVerification.tool.name,
+ version: tuple.nativeVerification.tool.version
+ },
+ verifiedAt: tuple.nativeVerification.verifiedAt,
+ files: [...tuple.nativeVerification.files]
+ .sort((left, right) => compareAscii(left.path, right.path))
+ .map((file) => ({ path: file.path, sha256: file.sha256 }))
+ }
+ }
+}
+
+export function canonicalUnsignedSshRelayManifestBytes(input: unknown): Buffer {
+ const manifest = parseSshRelayUnsignedArtifactManifest(input)
+ // Why: signatures authenticate a fixed validated projection, never caller insertion order or
+ // signature-array contents that could vary during key rotation.
+ const unsignedProjection = {
+ schemaVersion: manifest.schemaVersion,
+ build: {
+ tag: manifest.build.tag,
+ channel: manifest.build.channel,
+ version: manifest.build.version,
+ relayProtocolVersion: manifest.build.relayProtocolVersion
+ },
+ createdAt: manifest.createdAt,
+ tuples: [...manifest.tuples]
+ .sort((left, right) => compareAscii(left.tupleId, right.tupleId))
+ .map(canonicalTuple)
+ }
+ return Buffer.from(JSON.stringify(unsignedProjection), 'utf8')
+}
+
+export function sshRelayManifestKeyId(publicKey: Uint8Array): SshRelayDigest {
+ if (publicKey.byteLength !== nacl.sign.publicKeyLength) {
+ throw new Error('SSH relay manifest Ed25519 public key must be 32 bytes')
+ }
+ return `sha256:${createHash('sha256').update(publicKey).digest('hex')}`
+}
+
+export function signSshRelayArtifactManifest(
+ manifest: SshRelayArtifactManifest | SshRelayUnsignedArtifactManifest,
+ secretKey: Uint8Array
+): SshRelayManifestSignature {
+ if (secretKey.byteLength !== nacl.sign.secretKeyLength) {
+ throw new Error('SSH relay manifest Ed25519 secret key must be 64 bytes')
+ }
+ const publicKey = nacl.sign.keyPair.fromSecretKey(secretKey).publicKey
+ const signature = nacl.sign.detached(canonicalUnsignedSshRelayManifestBytes(manifest), secretKey)
+ return {
+ algorithm: 'ed25519-v1',
+ keyId: sshRelayManifestKeyId(publicKey),
+ signature: Buffer.from(signature).toString('base64')
+ }
+}
+
+function acceptedKeyMap(keys: readonly SshRelayManifestAcceptedKey[]): Map {
+ const accepted = new Map()
+ for (const key of keys) {
+ const derivedKeyId = sshRelayManifestKeyId(key.publicKey)
+ if (key.keyId !== derivedKeyId) {
+ throw new Error(`SSH relay manifest public key ID does not match key bytes: ${key.keyId}`)
+ }
+ if (accepted.has(key.keyId)) {
+ throw new Error(`Duplicate accepted SSH relay manifest key: ${key.keyId}`)
+ }
+ accepted.set(key.keyId, key.publicKey)
+ }
+ return accepted
+}
+
+export function verifySshRelayArtifactManifest(
+ input: unknown,
+ acceptedKeys: readonly SshRelayManifestAcceptedKey[]
+): SshRelayArtifactManifest {
+ const manifest = parseSshRelayArtifactManifest(input)
+ const unsignedBytes = canonicalUnsignedSshRelayManifestBytes(manifest)
+ const keys = acceptedKeyMap(acceptedKeys)
+ let validAcceptedSignatures = 0
+
+ for (const signature of manifest.signatures) {
+ const publicKey = keys.get(signature.keyId)
+ if (!publicKey) {
+ throw new Error(`Unknown signing key in SSH relay manifest: ${signature.keyId}`)
+ }
+ const signatureBytes = Buffer.from(signature.signature, 'base64')
+ if (!nacl.sign.detached.verify(unsignedBytes, signatureBytes, publicKey)) {
+ throw new Error(`Invalid SSH relay manifest signature from key: ${signature.keyId}`)
+ }
+ validAcceptedSignatures += 1
+ }
+
+ if (validAcceptedSignatures === 0) {
+ throw new Error('SSH relay manifest requires at least one valid accepted signature')
+ }
+ return manifest
+}
diff --git a/src/main/ssh/ssh-relay-release-asset.test.ts b/src/main/ssh/ssh-relay-release-asset.test.ts
new file mode 100644
index 00000000000..1ec020bb24d
--- /dev/null
+++ b/src/main/ssh/ssh-relay-release-asset.test.ts
@@ -0,0 +1,34 @@
+import { describe, expect, it } from 'vitest'
+
+import {
+ parseSshRelayReleaseTag,
+ sshRelayRuntimeArchiveName,
+ sshRelayRuntimeDownloadUrl
+} from './ssh-relay-release-asset'
+
+const contentId = `sha256:${'a'.repeat(64)}` as const
+
+describe('SSH relay release asset identity', () => {
+ it.each([
+ ['v1.2.3', 'stable'],
+ ['v1.2.3-rc.4', 'rc'],
+ ['v1.2.3-rc.4.perf', 'perf']
+ ] as const)('accepts exact %s tags', (tag, channel) => {
+ expect(parseSshRelayReleaseTag(tag).channel).toBe(channel)
+ })
+
+ it.each(['latest', 'v1.2', '1.2.3', 'v1.2.3-beta.1', 'v1.2.3-rc.1.other'])(
+ 'rejects mutable or unsupported tag %s',
+ (tag) => expect(() => parseSshRelayReleaseTag(tag)).toThrow(/release tag/i)
+ )
+
+ it('derives a content-qualified archive name and exact direct URL', () => {
+ const name = sshRelayRuntimeArchiveName('linux-x64-glibc', contentId)
+
+ expect(name).toBe(`orca-ssh-relay-runtime-v1-linux-x64-glibc-${'a'.repeat(64)}.tar.xz`)
+ expect(sshRelayRuntimeDownloadUrl('v1.2.3-rc.4', name)).toBe(
+ `https://github.com/stablyai/orca/releases/download/v1.2.3-rc.4/${name}`
+ )
+ expect(sshRelayRuntimeDownloadUrl('v1.2.3-rc.4', name)).not.toContain('/latest/')
+ })
+})
diff --git a/src/main/ssh/ssh-relay-release-asset.ts b/src/main/ssh/ssh-relay-release-asset.ts
new file mode 100644
index 00000000000..1305507a11b
--- /dev/null
+++ b/src/main/ssh/ssh-relay-release-asset.ts
@@ -0,0 +1,46 @@
+import type { SshRelayDigest, SshRelayRuntimeTupleId } from './ssh-relay-runtime-identity'
+
+const STABLE_TAG = /^v(\d+\.\d+\.\d+)$/
+const RC_TAG = /^v(\d+\.\d+\.\d+-rc\.\d+)$/
+const PERF_TAG = /^v(\d+\.\d+\.\d+-rc\.\d+\.perf)$/
+const CONTENT_ID = /^sha256:([0-9a-f]{64})$/
+
+export type SshRelayReleaseIdentity = {
+ tag: string
+ version: string
+ channel: 'stable' | 'rc' | 'perf'
+}
+
+export function parseSshRelayReleaseTag(tag: string): SshRelayReleaseIdentity {
+ for (const [pattern, channel] of [
+ [STABLE_TAG, 'stable'],
+ [RC_TAG, 'rc'],
+ [PERF_TAG, 'perf']
+ ] as const) {
+ const match = pattern.exec(tag)
+ if (match) {
+ return { tag, version: match[1], channel }
+ }
+ }
+ throw new Error(`Unsupported SSH relay release tag: ${tag}`)
+}
+
+export function sshRelayRuntimeArchiveName(
+ tupleId: SshRelayRuntimeTupleId,
+ contentId: SshRelayDigest
+): string {
+ const match = CONTENT_ID.exec(contentId)
+ if (!match) {
+ throw new Error(`Invalid SSH relay runtime content identity: ${contentId}`)
+ }
+ const extension = tupleId.startsWith('win32-') ? 'zip' : 'tar.xz'
+ return `orca-ssh-relay-runtime-v1-${tupleId}-${match[1]}.${extension}`
+}
+
+export function sshRelayRuntimeDownloadUrl(tag: string, archiveName: string): string {
+ parseSshRelayReleaseTag(tag)
+ if (!/^orca-ssh-relay-runtime-v1-[A-Za-z0-9.-]+\.(?:tar\.xz|zip)$/.test(archiveName)) {
+ throw new Error(`Invalid SSH relay runtime archive name: ${archiveName}`)
+ }
+ return `https://github.com/stablyai/orca/releases/download/${encodeURIComponent(tag)}/${encodeURIComponent(archiveName)}`
+}
diff --git a/src/main/ssh/ssh-relay-runtime-identity.test.ts b/src/main/ssh/ssh-relay-runtime-identity.test.ts
new file mode 100644
index 00000000000..d86759021b0
--- /dev/null
+++ b/src/main/ssh/ssh-relay-runtime-identity.test.ts
@@ -0,0 +1,59 @@
+import { describe, expect, it } from 'vitest'
+
+import { createSshRelayArtifactTestManifest } from './ssh-relay-artifact-test-manifest'
+import { computeSshRelayRuntimeContentId } from './ssh-relay-runtime-identity'
+
+function changedEntryContentId(role: string): string {
+ const tuple = structuredClone(createSshRelayArtifactTestManifest().tuples[0])
+ const entry = tuple.entries.find(
+ (candidate) => candidate.type === 'file' && candidate.role === role
+ )
+ if (!entry || entry.type !== 'file') {
+ throw new Error(`test fixture missing ${role}`)
+ }
+ entry.sha256 = `sha256:${'f'.repeat(64)}`
+ return computeSshRelayRuntimeContentId(tuple)
+}
+
+describe('SSH relay runtime content identity', () => {
+ it('matches a fixed canonical test vector', () => {
+ const tuple = createSshRelayArtifactTestManifest().tuples[0]
+
+ expect(computeSshRelayRuntimeContentId(tuple)).toBe(
+ 'sha256:5afe9c8094ec61a5eec6f7be6d1035faacee7362871985c74cc6ee6aceea8677'
+ )
+ })
+
+ it.each(['node', 'node-pty-native', 'parcel-watcher-native', 'relay-watcher'])(
+ 'changes when only %s bytes change',
+ (role) => {
+ const tuple = createSshRelayArtifactTestManifest().tuples[0]
+ expect(changedEntryContentId(role)).not.toBe(computeSshRelayRuntimeContentId(tuple))
+ }
+ )
+
+ it('changes when an executable mode changes', () => {
+ const tuple = structuredClone(createSshRelayArtifactTestManifest().tuples[0])
+ const node = tuple.entries.find((entry) => entry.type === 'file' && entry.role === 'node')
+ if (!node || node.type !== 'file') {
+ throw new Error('test fixture missing node')
+ }
+ node.mode = 0o644
+
+ expect(computeSshRelayRuntimeContentId(tuple)).not.toBe(
+ createSshRelayArtifactTestManifest().tuples[0].contentId
+ )
+ })
+
+ it('is independent of entry ordering and release metadata', () => {
+ const manifest = createSshRelayArtifactTestManifest()
+ const tuple = structuredClone(manifest.tuples[0])
+ tuple.entries.reverse()
+ tuple.archive.name = 'ignored-by-runtime-identity.tar.xz'
+ tuple.archive.sha256 = `sha256:${'f'.repeat(64)}`
+ tuple.metadataAssets.sbom.sha256 = `sha256:${'e'.repeat(64)}`
+ tuple.nativeVerification.verifiedAt = '2030-01-01T00:00:00.000Z'
+
+ expect(computeSshRelayRuntimeContentId(tuple)).toBe(manifest.tuples[0].contentId)
+ })
+})
diff --git a/src/main/ssh/ssh-relay-runtime-identity.ts b/src/main/ssh/ssh-relay-runtime-identity.ts
new file mode 100644
index 00000000000..bf88d063aa8
--- /dev/null
+++ b/src/main/ssh/ssh-relay-runtime-identity.ts
@@ -0,0 +1,145 @@
+import { createHash } from 'node:crypto'
+
+export type SshRelayDigest = `sha256:${string}`
+export type SshRelayRuntimeTupleId =
+ | 'linux-x64-glibc'
+ | 'linux-arm64-glibc'
+ | 'linux-x64-musl'
+ | 'linux-arm64-musl'
+ | 'darwin-x64'
+ | 'darwin-arm64'
+ | 'win32-x64'
+ | 'win32-arm64'
+
+export type SshRelayRuntimeFileRole =
+ | 'node'
+ | 'relay'
+ | 'relay-watcher'
+ | 'node-pty-native'
+ | 'parcel-watcher-native'
+ | 'native-runtime'
+ | 'runtime-javascript'
+ | 'license'
+
+export type SshRelayRuntimeEntry =
+ | { path: string; type: 'directory'; mode: 0o755 }
+ | {
+ path: string
+ type: 'file'
+ role: SshRelayRuntimeFileRole
+ size: number
+ mode: 0o644 | 0o755
+ sha256: SshRelayDigest
+ }
+
+export type SshRelayLinuxCompatibility = {
+ kind: 'linux'
+ minimumKernelVersion: string
+ libc:
+ | {
+ family: 'glibc'
+ minimumVersion: string
+ minimumLibstdcxxVersion: string
+ minimumGlibcxxVersion: string
+ }
+ | {
+ family: 'musl'
+ minimumVersion: string
+ minimumLibstdcxxVersion: null
+ minimumGlibcxxVersion: null
+ }
+}
+
+export type SshRelayRuntimeCompatibility =
+ | SshRelayLinuxCompatibility
+ | { kind: 'darwin'; minimumVersion: string }
+ | {
+ kind: 'windows'
+ minimumBuild: number
+ minimumOpenSshVersion: string
+ minimumPowerShellVersion: string
+ minimumDotNetFrameworkRelease: number
+ }
+
+export type SshRelayRuntimeIdentityInput = {
+ tupleId: SshRelayRuntimeTupleId
+ os: 'linux' | 'darwin' | 'win32'
+ architecture: 'x64' | 'arm64'
+ compatibility: SshRelayRuntimeCompatibility
+ nodeVersion: string
+ dependencies: {
+ nodePtyVersion: string
+ parcelWatcherVersion: string
+ }
+ entries: SshRelayRuntimeEntry[]
+}
+
+function canonicalCompatibility(compatibility: SshRelayRuntimeCompatibility): object {
+ if (compatibility.kind === 'linux') {
+ return {
+ kind: compatibility.kind,
+ minimumKernelVersion: compatibility.minimumKernelVersion,
+ libc: {
+ family: compatibility.libc.family,
+ minimumVersion: compatibility.libc.minimumVersion,
+ minimumLibstdcxxVersion: compatibility.libc.minimumLibstdcxxVersion,
+ minimumGlibcxxVersion: compatibility.libc.minimumGlibcxxVersion
+ }
+ }
+ }
+ if (compatibility.kind === 'darwin') {
+ return { kind: compatibility.kind, minimumVersion: compatibility.minimumVersion }
+ }
+ return {
+ kind: compatibility.kind,
+ minimumBuild: compatibility.minimumBuild,
+ minimumOpenSshVersion: compatibility.minimumOpenSshVersion,
+ minimumPowerShellVersion: compatibility.minimumPowerShellVersion,
+ minimumDotNetFrameworkRelease: compatibility.minimumDotNetFrameworkRelease
+ }
+}
+
+export function canonicalSshRelayRuntimeIdentityBytes(
+ runtime: SshRelayRuntimeIdentityInput
+): Buffer {
+ const entries = [...runtime.entries]
+ .sort((left, right) => (left.path < right.path ? -1 : left.path > right.path ? 1 : 0))
+ .map((entry) =>
+ entry.type === 'directory'
+ ? { path: entry.path, type: entry.type, mode: entry.mode }
+ : {
+ path: entry.path,
+ type: entry.type,
+ role: entry.role,
+ size: entry.size,
+ mode: entry.mode,
+ sha256: entry.sha256
+ }
+ )
+
+ // Why: runtime identity excludes archive packing, timestamps, SBOM, and signing metadata so the
+ // same executable tree has one stable content address across reproducible release rebuilds.
+ const projection = {
+ identitySchemaVersion: 1,
+ tupleId: runtime.tupleId,
+ os: runtime.os,
+ architecture: runtime.architecture,
+ compatibility: canonicalCompatibility(runtime.compatibility),
+ nodeVersion: runtime.nodeVersion,
+ dependencies: {
+ nodePtyVersion: runtime.dependencies.nodePtyVersion,
+ parcelWatcherVersion: runtime.dependencies.parcelWatcherVersion
+ },
+ entries
+ }
+ return Buffer.from(JSON.stringify(projection), 'utf8')
+}
+
+export function computeSshRelayRuntimeContentId(
+ runtime: SshRelayRuntimeIdentityInput
+): SshRelayDigest {
+ const hex = createHash('sha256')
+ .update(canonicalSshRelayRuntimeIdentityBytes(runtime))
+ .digest('hex')
+ return `sha256:${hex}`
+}
|